Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
480d8e642d | ||
|
|
414b74b95f | ||
|
|
227f5ff922 | ||
|
|
72ae2f4b06 | ||
|
|
3397ca121b | ||
|
|
1e038901b3 | ||
|
|
6c11495892 | ||
|
|
a1a2e9d571 | ||
|
|
f4c4beb6b5 | ||
|
|
1004a39667 | ||
|
|
a7cc533ed4 | ||
|
|
40877be160 | ||
|
|
8d119ac00e | ||
|
|
aebef4c623 | ||
|
|
b81b7b0e15 | ||
|
|
3f445eead5 | ||
|
|
3225509713 | ||
|
|
ecb1721ff4 |
No files matched your search
@@ -5,5 +5,3 @@ dist/
|
||||
*.log
|
||||
.env
|
||||
.idea/
|
||||
|
||||
.antigravitycli/
|
||||
@@ -275,7 +275,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "chronoseal-replay"
|
||||
version = "0.1.0"
|
||||
version = "1.0.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
@@ -290,12 +290,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "chronoseal-server"
|
||||
version = "0.6.0"
|
||||
version = "1.0.2"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"base64",
|
||||
"clap",
|
||||
"clap_complete",
|
||||
"dashmap",
|
||||
"ed25519-dalek",
|
||||
"hex",
|
||||
"r2d2",
|
||||
@@ -319,7 +320,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "chronoseal-wasm"
|
||||
version = "0.6.0"
|
||||
version = "1.0.1"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"blake3",
|
||||
@@ -509,6 +510,20 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dashmap"
|
||||
version = "6.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
"hashbrown 0.14.5",
|
||||
"lock_api",
|
||||
"once_cell",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.7.10"
|
||||
@@ -1946,7 +1961,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "shared"
|
||||
version = "0.6.0"
|
||||
version = "1.0.1"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"blake3",
|
||||
|
||||
@@ -4,7 +4,8 @@ resolver = "2"
|
||||
members = [
|
||||
"shared",
|
||||
"server",
|
||||
"wasm"
|
||||
"wasm",
|
||||
"chronoseal-replay"
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
|
||||
@@ -24,4 +24,9 @@ ENV CHRONOSEAL_DB_PATH=/var/lib/chronoseal/chronoseal.sqlite
|
||||
ENV CHRONOSEAL_FRONTEND_DIR=/usr/share/chronoseal/frontend
|
||||
ENV CHRONOSEAL_PID_FILE=/run/chronoseal.pid
|
||||
|
||||
RUN useradd -r -s /bin/false chronoseal
|
||||
USER chronoseal
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=3s CMD chronoseal health || exit 1
|
||||
|
||||
CMD ["chronoseal", "run"]
|
||||
@@ -20,7 +20,7 @@
|
||||
<img src="https://img.shields.io/badge/rust-stable%20%E2%89%A5%201.87-orange.svg" alt="Rust stable >= 1.87">
|
||||
</a>
|
||||
<a href="https://github.com/thakares/chronoseal-rs/blob/main/docs/REFRACTORING-v0.6.0.md">
|
||||
<img src="https://img.shields.io/badge/version-v0.6.0-green.svg" alt="v0.6.0">
|
||||
<img src="https://img.shields.io/badge/version-v1.0.2-green.svg" alt="v1.0.2">
|
||||
</a>
|
||||
<img src="https://img.shields.io/badge/wasm-rust--compiled-blueviolet.svg" alt="WASM">
|
||||
</p>
|
||||
@@ -68,6 +68,20 @@ ChronoSeal is a cost-raising attestation layer. It is not a CAPTCHA replacement,
|
||||
- Runtime storage abstraction with `sqlite-in-memory`, `sqlite-in-disk`, and `valkey` modes.
|
||||
- CLI-first lifecycle, status, health, metrics, stats, config validation, key generation, and shell completions.
|
||||
- Privacy-oriented design based on ephemeral session state rather than long-term identity tracking.
|
||||
- Security response headers (CSP, X-Frame-Options, Referrer-Policy, Permissions-Policy, X-Content-Type-Options).
|
||||
- Fingerprint validation with explicit bounds checking for aspect ratio, device pixel ratio, and hardware concurrency.
|
||||
- DashMap-based concurrent rate limiter internals.
|
||||
- Non-root Docker container execution.
|
||||
- VM stack-depth protection.
|
||||
- WASM and hashing panic-resistance safeguards.
|
||||
- Progressive Web App (PWA) assets including favicon and web manifest support.
|
||||
|
||||
✅ ~9 MiB native daemon <br/>
|
||||
✅ ~728 KiB WASM runtime <br/>
|
||||
✅ Full RELRO <br/>
|
||||
✅ PIE enabled <br/>
|
||||
✅ Stack canaries <br/>
|
||||
✅ NX enabled <br/>
|
||||
|
||||
## How It Works
|
||||
|
||||
@@ -280,6 +294,13 @@ bash scripts/build.sh
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
### Container Security
|
||||
|
||||
ChronoSeal containers run as a dedicated non-root user by default.
|
||||
|
||||
This reduces the impact of potential container compromise and follows container security best practices.
|
||||
|
||||
|
||||
## CLI Reference
|
||||
|
||||
Run:
|
||||
@@ -486,6 +507,18 @@ Rejected response:
|
||||
}
|
||||
```
|
||||
|
||||
#### Fingerprint Validation
|
||||
|
||||
ChronoSeal validates browser fingerprint inputs before processing.
|
||||
|
||||
| Field | Accepted Range |
|
||||
|---|---|
|
||||
| aspectRatio | finite positive value |
|
||||
| devicePixelRatio | > 0 |
|
||||
| hardwareConcurrency | 1..=256 |
|
||||
|
||||
Invalid values including NaN, Infinity, negative values, malformed numeric strings, and out-of-range CPU counts are rejected.
|
||||
|
||||
Detailed API semantics are documented in [docs/API.md](docs/API.md).
|
||||
|
||||
## Browser Integration
|
||||
@@ -536,7 +569,32 @@ Set storage mode with `db_type` or `CHRONOSEAL_DB_TYPE`.
|
||||
| `sqlite-in-disk` | SQLite database persisted at `db_path`. |
|
||||
| `valkey` | Valkey-compatible backend mode. |
|
||||
|
||||
For Valkey mode, the server reads `CHRONOSEAL_VALKEY_ADDR` and defaults to `127.0.0.1:6666` when it is not set. If the Valkey connection fails, the current implementation falls back to in-memory SQLite and logs a warning.
|
||||
For Valkey mode, the server reads `CHRONOSEAL_VALKEY_ADDR` (defaulting to `127.0.0.1:6666`) and establishes a thread-safe connection pool using `r2d2` and the `redis` client crate. It leverages native Valkey sets for session ID indexing and native key expiration for automatic session cleanup. If the Valkey connection fails, the server falls back to in-memory SQLite and logs a warning.
|
||||
|
||||
### Valkey / Redis Server Setup
|
||||
|
||||
To quickly run a local Valkey/Redis instance for testing or production:
|
||||
|
||||
```bash
|
||||
# Option A: Start a local Valkey/Redis server on port 6666
|
||||
valkey-server --port 6666 --bind 127.0.0.1
|
||||
# Or
|
||||
redis-server --port 6666 --bind 127.0.0.1
|
||||
|
||||
# Option B: Spin up via Docker
|
||||
docker run -d --name chronoseal-valkey -p 6666:6379 valkey/valkey:latest
|
||||
```
|
||||
|
||||
Configure ChronoSeal to use it:
|
||||
```bash
|
||||
export CHRONOSEAL_DB_TYPE=valkey
|
||||
export CHRONOSEAL_VALKEY_ADDR=127.0.0.1:6666
|
||||
```
|
||||
|
||||
If your Valkey or Redis server requires credentials or secure TLS:
|
||||
* **Password Only**: `redis://:your_password@127.0.0.1:6666`
|
||||
* **Username & Password**: `redis://your_username:your_password@127.0.0.1:6666`
|
||||
* **Secure Connection (SSL/TLS)**: `rediss://your_username:your_password@secure-host.example.com:6379`
|
||||
|
||||
## Operations
|
||||
|
||||
@@ -605,6 +663,9 @@ It helps defend against:
|
||||
- session cloning using only a stolen `session_id`
|
||||
- simple scripted clients that do not run the WASM runtime
|
||||
- basic browser automation with weak interaction simulation
|
||||
- malformed browser fingerprint payloads
|
||||
- invalid numeric fingerprint values
|
||||
- protocol abuse through oversized fingerprint attributes
|
||||
|
||||
It does not claim to stop:
|
||||
|
||||
@@ -655,6 +716,20 @@ Build release artifacts:
|
||||
bash scripts/build.sh
|
||||
```
|
||||
|
||||
### Validation Tests
|
||||
|
||||
```bash
|
||||
cargo test -p chronoseal-server fingerprint
|
||||
cargo test -p chronoseal-server
|
||||
```
|
||||
|
||||
The fingerprint validation suite verifies:
|
||||
|
||||
- aspect ratio bounds
|
||||
- device pixel ratio bounds
|
||||
- hardware concurrency bounds
|
||||
- malformed numeric input handling
|
||||
|
||||
Generate shell completion:
|
||||
|
||||
```bash
|
||||
@@ -725,6 +800,69 @@ Print the effective config:
|
||||
chronoseal config check --format yaml
|
||||
```
|
||||
|
||||
|
||||
## What's New in v1.0.2
|
||||
|
||||
### Security Hardening
|
||||
|
||||
- Added security response headers.
|
||||
- Hardened browser fingerprint validation.
|
||||
- Improved WASM-side error handling.
|
||||
- Improved hashing safety.
|
||||
- Added VM stack-depth protection.
|
||||
|
||||
### Runtime Improvements
|
||||
|
||||
- Refactored rate limiter internals using DashMap.
|
||||
- Improved cleanup task efficiency.
|
||||
- Improved configuration validation.
|
||||
- Improved deployment hardening.
|
||||
|
||||
### Frontend Improvements
|
||||
|
||||
- Added favicon and web manifest assets.
|
||||
- Added CSP defense-in-depth support.
|
||||
- Reduced protocol-state logging in browser consoles.
|
||||
|
||||
### Quality
|
||||
|
||||
- 38/38 server tests passing.
|
||||
- Additional fingerprint validation test coverage.
|
||||
|
||||
## 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:
|
||||
|
||||
```text
|
||||
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.
|
||||
|
||||
## Further Reading
|
||||
|
||||
- [Architecture](docs/ARCHITECTURE.md)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "chronoseal-replay"
|
||||
version = "0.1.0"
|
||||
version = "1.0.1"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -117,7 +117,11 @@ fn test_entropy() -> EntropyData {
|
||||
}
|
||||
}
|
||||
|
||||
fn do_handshake(client: &reqwest::blocking::Client, base_url: &str, sk: &SigningKey) -> Result<InitResponse> {
|
||||
fn do_handshake(
|
||||
client: &reqwest::blocking::Client,
|
||||
base_url: &str,
|
||||
sk: &SigningKey,
|
||||
) -> Result<InitResponse> {
|
||||
let pk_hex = hex::encode(sk.verifying_key().to_bytes());
|
||||
let init_req = InitRequest { public_key: pk_hex };
|
||||
let resp = client
|
||||
@@ -126,7 +130,10 @@ fn do_handshake(client: &reqwest::blocking::Client, base_url: &str, sk: &Signing
|
||||
.send()?;
|
||||
|
||||
if !resp.status().is_success() {
|
||||
return Err(anyhow!("Handshake failed with HTTP status: {}", resp.status()));
|
||||
return Err(anyhow!(
|
||||
"Handshake failed with HTTP status: {}",
|
||||
resp.status()
|
||||
));
|
||||
}
|
||||
|
||||
let init_resp: InitResponse = resp.json()?;
|
||||
@@ -137,11 +144,17 @@ fn run_built_in_scenarios(client: &reqwest::blocking::Client, base_url: &str) ->
|
||||
let mut failures = 0;
|
||||
|
||||
let scenarios = [
|
||||
("valid_progression", run_valid_progression as fn(&reqwest::blocking::Client, &str) -> Result<()>),
|
||||
(
|
||||
"valid_progression",
|
||||
run_valid_progression as fn(&reqwest::blocking::Client, &str) -> Result<()>,
|
||||
),
|
||||
("stale_replay", run_stale_replay),
|
||||
("invalid_signature", run_invalid_signature),
|
||||
("invalid_vm_stack", run_invalid_vm_stack),
|
||||
("invalid_mutation_commitment", run_invalid_mutation_commitment),
|
||||
(
|
||||
"invalid_mutation_commitment",
|
||||
run_invalid_mutation_commitment,
|
||||
),
|
||||
("drifted_timestamp", run_drifted_timestamp),
|
||||
("concurrent_heartbeat", run_concurrent_heartbeat),
|
||||
("rate_limit_trigger", run_rate_limit_trigger),
|
||||
@@ -197,7 +210,8 @@ fn run_valid_progression(client: &reqwest::blocking::Client, base_url: &str) ->
|
||||
init.mutation_rounds,
|
||||
)?;
|
||||
|
||||
let commitment = shared::gene::commitment_hex_with_context(&candidate, &init.session_id, mutation_step);
|
||||
let commitment =
|
||||
shared::gene::commitment_hex_with_context(&candidate, &init.session_id, mutation_step);
|
||||
let timestamp = current_time_ms();
|
||||
let entropy = test_entropy();
|
||||
|
||||
@@ -215,13 +229,14 @@ fn run_valid_progression(client: &reqwest::blocking::Client, base_url: &str) ->
|
||||
|
||||
sign_request(&sk, &mut req)?;
|
||||
|
||||
let resp = client
|
||||
.post(format!("{}/hb", base_url))
|
||||
.json(&req)
|
||||
.send()?;
|
||||
let resp = client.post(format!("{}/hb", base_url)).json(&req).send()?;
|
||||
|
||||
if !resp.status().is_success() {
|
||||
return Err(anyhow!("Step {} /hb returned HTTP error: {}", step, resp.status()));
|
||||
return Err(anyhow!(
|
||||
"Step {} /hb returned HTTP error: {}",
|
||||
step,
|
||||
resp.status()
|
||||
));
|
||||
}
|
||||
|
||||
let hb_resp: HeartbeatResponse = resp.json()?;
|
||||
@@ -230,9 +245,15 @@ fn run_valid_progression(client: &reqwest::blocking::Client, base_url: &str) ->
|
||||
}
|
||||
|
||||
// Verify it was a successful validation (not a silent rejection)
|
||||
let next_salt = hb_resp.next_salt.ok_or_else(|| anyhow!("Step {} was silently rejected", step))?;
|
||||
let next_step = hb_resp.next_mutation_step.ok_or_else(|| anyhow!("Step {} missing next mutation step", step))?;
|
||||
let next_order = hb_resp.next_mutation_order_b64.ok_or_else(|| anyhow!("Step {} missing next mutation order", step))?;
|
||||
let next_salt = hb_resp
|
||||
.next_salt
|
||||
.ok_or_else(|| anyhow!("Step {} was silently rejected", step))?;
|
||||
let next_step = hb_resp
|
||||
.next_mutation_step
|
||||
.ok_or_else(|| anyhow!("Step {} missing next mutation step", step))?;
|
||||
let next_order = hb_resp
|
||||
.next_mutation_order_b64
|
||||
.ok_or_else(|| anyhow!("Step {} missing next mutation order", step))?;
|
||||
|
||||
println!("Step {} successful. Salt rotated: {}", step, next_salt);
|
||||
|
||||
@@ -265,12 +286,21 @@ fn run_stale_replay(client: &reqwest::blocking::Client, base_url: &str) -> Resul
|
||||
let sk = SigningKey::generate(&mut csprng);
|
||||
let init = do_handshake(client, base_url, &sk)?;
|
||||
|
||||
let opcodes = base64::Engine::decode(&base64::engine::general_purpose::STANDARD, &init.opcodes_b64)?;
|
||||
let opcodes = base64::Engine::decode(
|
||||
&base64::engine::general_purpose::STANDARD,
|
||||
&init.opcodes_b64,
|
||||
)?;
|
||||
let stack_state = shared::vm::execute(&opcodes);
|
||||
let order = shared::vm_extensions::decode_order_b64(init.mutation_step, &init.mutation_order_b64)?;
|
||||
let order =
|
||||
shared::vm_extensions::decode_order_b64(init.mutation_step, &init.mutation_order_b64)?;
|
||||
let gene_state = shared::gene::new_state(init.gene_size as usize).unwrap();
|
||||
let candidate = shared::vm_extensions::apply_program_clone_with_rounds(&gene_state, &order.program, init.mutation_rounds)?;
|
||||
let commitment = shared::gene::commitment_hex_with_context(&candidate, &init.session_id, init.mutation_step);
|
||||
let candidate = shared::vm_extensions::apply_program_clone_with_rounds(
|
||||
&gene_state,
|
||||
&order.program,
|
||||
init.mutation_rounds,
|
||||
)?;
|
||||
let commitment =
|
||||
shared::gene::commitment_hex_with_context(&candidate, &init.session_id, init.mutation_step);
|
||||
|
||||
let mut req = HeartbeatRequest {
|
||||
session_id: init.session_id.clone(),
|
||||
@@ -296,7 +326,9 @@ fn run_stale_replay(client: &reqwest::blocking::Client, base_url: &str) -> Resul
|
||||
let resp2 = client.post(format!("{}/hb", base_url)).json(&req).send()?;
|
||||
let hb2: HeartbeatResponse = resp2.json()?;
|
||||
if hb2.next_salt.is_some() {
|
||||
return Err(anyhow!("Replayed heartbeat was successfully accepted (broken replay protection)"));
|
||||
return Err(anyhow!(
|
||||
"Replayed heartbeat was successfully accepted (broken replay protection)"
|
||||
));
|
||||
}
|
||||
|
||||
println!("Stale replay correctly rejected.");
|
||||
@@ -308,12 +340,21 @@ fn run_invalid_signature(client: &reqwest::blocking::Client, base_url: &str) ->
|
||||
let sk = SigningKey::generate(&mut csprng);
|
||||
let init = do_handshake(client, base_url, &sk)?;
|
||||
|
||||
let opcodes = base64::Engine::decode(&base64::engine::general_purpose::STANDARD, &init.opcodes_b64)?;
|
||||
let opcodes = base64::Engine::decode(
|
||||
&base64::engine::general_purpose::STANDARD,
|
||||
&init.opcodes_b64,
|
||||
)?;
|
||||
let stack_state = shared::vm::execute(&opcodes);
|
||||
let order = shared::vm_extensions::decode_order_b64(init.mutation_step, &init.mutation_order_b64)?;
|
||||
let order =
|
||||
shared::vm_extensions::decode_order_b64(init.mutation_step, &init.mutation_order_b64)?;
|
||||
let gene_state = shared::gene::new_state(init.gene_size as usize).unwrap();
|
||||
let candidate = shared::vm_extensions::apply_program_clone_with_rounds(&gene_state, &order.program, init.mutation_rounds)?;
|
||||
let commitment = shared::gene::commitment_hex_with_context(&candidate, &init.session_id, init.mutation_step);
|
||||
let candidate = shared::vm_extensions::apply_program_clone_with_rounds(
|
||||
&gene_state,
|
||||
&order.program,
|
||||
init.mutation_rounds,
|
||||
)?;
|
||||
let commitment =
|
||||
shared::gene::commitment_hex_with_context(&candidate, &init.session_id, init.mutation_step);
|
||||
|
||||
let mut req = HeartbeatRequest {
|
||||
session_id: init.session_id.clone(),
|
||||
@@ -344,10 +385,16 @@ fn run_invalid_vm_stack(client: &reqwest::blocking::Client, base_url: &str) -> R
|
||||
let sk = SigningKey::generate(&mut csprng);
|
||||
let init = do_handshake(client, base_url, &sk)?;
|
||||
|
||||
let order = shared::vm_extensions::decode_order_b64(init.mutation_step, &init.mutation_order_b64)?;
|
||||
let order =
|
||||
shared::vm_extensions::decode_order_b64(init.mutation_step, &init.mutation_order_b64)?;
|
||||
let gene_state = shared::gene::new_state(init.gene_size as usize).unwrap();
|
||||
let candidate = shared::vm_extensions::apply_program_clone_with_rounds(&gene_state, &order.program, init.mutation_rounds)?;
|
||||
let commitment = shared::gene::commitment_hex_with_context(&candidate, &init.session_id, init.mutation_step);
|
||||
let candidate = shared::vm_extensions::apply_program_clone_with_rounds(
|
||||
&gene_state,
|
||||
&order.program,
|
||||
init.mutation_rounds,
|
||||
)?;
|
||||
let commitment =
|
||||
shared::gene::commitment_hex_with_context(&candidate, &init.session_id, init.mutation_step);
|
||||
|
||||
let mut req = HeartbeatRequest {
|
||||
session_id: init.session_id.clone(),
|
||||
@@ -375,12 +422,18 @@ fn run_invalid_vm_stack(client: &reqwest::blocking::Client, base_url: &str) -> R
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_invalid_mutation_commitment(client: &reqwest::blocking::Client, base_url: &str) -> Result<()> {
|
||||
fn run_invalid_mutation_commitment(
|
||||
client: &reqwest::blocking::Client,
|
||||
base_url: &str,
|
||||
) -> Result<()> {
|
||||
let mut csprng = OsRng;
|
||||
let sk = SigningKey::generate(&mut csprng);
|
||||
let init = do_handshake(client, base_url, &sk)?;
|
||||
|
||||
let opcodes = base64::Engine::decode(&base64::engine::general_purpose::STANDARD, &init.opcodes_b64)?;
|
||||
let opcodes = base64::Engine::decode(
|
||||
&base64::engine::general_purpose::STANDARD,
|
||||
&init.opcodes_b64,
|
||||
)?;
|
||||
let stack_state = shared::vm::execute(&opcodes);
|
||||
|
||||
let mut req = HeartbeatRequest {
|
||||
@@ -411,12 +464,21 @@ fn run_drifted_timestamp(client: &reqwest::blocking::Client, base_url: &str) ->
|
||||
let sk = SigningKey::generate(&mut csprng);
|
||||
let init = do_handshake(client, base_url, &sk)?;
|
||||
|
||||
let opcodes = base64::Engine::decode(&base64::engine::general_purpose::STANDARD, &init.opcodes_b64)?;
|
||||
let opcodes = base64::Engine::decode(
|
||||
&base64::engine::general_purpose::STANDARD,
|
||||
&init.opcodes_b64,
|
||||
)?;
|
||||
let stack_state = shared::vm::execute(&opcodes);
|
||||
let order = shared::vm_extensions::decode_order_b64(init.mutation_step, &init.mutation_order_b64)?;
|
||||
let order =
|
||||
shared::vm_extensions::decode_order_b64(init.mutation_step, &init.mutation_order_b64)?;
|
||||
let gene_state = shared::gene::new_state(init.gene_size as usize).unwrap();
|
||||
let candidate = shared::vm_extensions::apply_program_clone_with_rounds(&gene_state, &order.program, init.mutation_rounds)?;
|
||||
let commitment = shared::gene::commitment_hex_with_context(&candidate, &init.session_id, init.mutation_step);
|
||||
let candidate = shared::vm_extensions::apply_program_clone_with_rounds(
|
||||
&gene_state,
|
||||
&order.program,
|
||||
init.mutation_rounds,
|
||||
)?;
|
||||
let commitment =
|
||||
shared::gene::commitment_hex_with_context(&candidate, &init.session_id, init.mutation_step);
|
||||
|
||||
let mut req = HeartbeatRequest {
|
||||
session_id: init.session_id.clone(),
|
||||
@@ -446,12 +508,21 @@ fn run_concurrent_heartbeat(client: &reqwest::blocking::Client, base_url: &str)
|
||||
let sk = SigningKey::generate(&mut csprng);
|
||||
let init = do_handshake(client, base_url, &sk)?;
|
||||
|
||||
let opcodes = base64::Engine::decode(&base64::engine::general_purpose::STANDARD, &init.opcodes_b64)?;
|
||||
let opcodes = base64::Engine::decode(
|
||||
&base64::engine::general_purpose::STANDARD,
|
||||
&init.opcodes_b64,
|
||||
)?;
|
||||
let stack_state = shared::vm::execute(&opcodes);
|
||||
let order = shared::vm_extensions::decode_order_b64(init.mutation_step, &init.mutation_order_b64)?;
|
||||
let order =
|
||||
shared::vm_extensions::decode_order_b64(init.mutation_step, &init.mutation_order_b64)?;
|
||||
let gene_state = shared::gene::new_state(init.gene_size as usize).unwrap();
|
||||
let candidate = shared::vm_extensions::apply_program_clone_with_rounds(&gene_state, &order.program, init.mutation_rounds)?;
|
||||
let commitment = shared::gene::commitment_hex_with_context(&candidate, &init.session_id, init.mutation_step);
|
||||
let candidate = shared::vm_extensions::apply_program_clone_with_rounds(
|
||||
&gene_state,
|
||||
&order.program,
|
||||
init.mutation_rounds,
|
||||
)?;
|
||||
let commitment =
|
||||
shared::gene::commitment_hex_with_context(&candidate, &init.session_id, init.mutation_step);
|
||||
|
||||
let mut req = HeartbeatRequest {
|
||||
session_id: init.session_id.clone(),
|
||||
@@ -471,9 +542,7 @@ fn run_concurrent_heartbeat(client: &reqwest::blocking::Client, base_url: &str)
|
||||
let req_clone = req.clone();
|
||||
let url_clone = format!("{}/hb", base_url);
|
||||
|
||||
let handle = std::thread::spawn(move || {
|
||||
client_clone.post(&url_clone).json(&req_clone).send()
|
||||
});
|
||||
let handle = std::thread::spawn(move || client_clone.post(&url_clone).json(&req_clone).send());
|
||||
|
||||
let resp2 = client.post(format!("{}/hb", base_url)).json(&req).send()?;
|
||||
let resp1_res = handle.join().map_err(|_| anyhow!("Thread panicked"))?;
|
||||
@@ -485,7 +554,10 @@ fn run_concurrent_heartbeat(client: &reqwest::blocking::Client, base_url: &str)
|
||||
// One must succeed and one must fail (silent rejection) because of CAS check
|
||||
let successes = (hb1.next_salt.is_some() as usize) + (hb2.next_salt.is_some() as usize);
|
||||
if successes != 1 {
|
||||
return Err(anyhow!("Expected exactly one concurrent heartbeat to succeed. Got: {}", successes));
|
||||
return Err(anyhow!(
|
||||
"Expected exactly one concurrent heartbeat to succeed. Got: {}",
|
||||
successes
|
||||
));
|
||||
}
|
||||
|
||||
println!("Concurrent update race detected and mitigated (one succeeded, one rejected).");
|
||||
@@ -497,12 +569,21 @@ fn run_rate_limit_trigger(client: &reqwest::blocking::Client, base_url: &str) ->
|
||||
let sk = SigningKey::generate(&mut csprng);
|
||||
let init = do_handshake(client, base_url, &sk)?;
|
||||
|
||||
let opcodes = base64::Engine::decode(&base64::engine::general_purpose::STANDARD, &init.opcodes_b64)?;
|
||||
let opcodes = base64::Engine::decode(
|
||||
&base64::engine::general_purpose::STANDARD,
|
||||
&init.opcodes_b64,
|
||||
)?;
|
||||
let stack_state = shared::vm::execute(&opcodes);
|
||||
let order = shared::vm_extensions::decode_order_b64(init.mutation_step, &init.mutation_order_b64)?;
|
||||
let order =
|
||||
shared::vm_extensions::decode_order_b64(init.mutation_step, &init.mutation_order_b64)?;
|
||||
let gene_state = shared::gene::new_state(init.gene_size as usize).unwrap();
|
||||
let candidate = shared::vm_extensions::apply_program_clone_with_rounds(&gene_state, &order.program, init.mutation_rounds)?;
|
||||
let commitment = shared::gene::commitment_hex_with_context(&candidate, &init.session_id, init.mutation_step);
|
||||
let candidate = shared::vm_extensions::apply_program_clone_with_rounds(
|
||||
&gene_state,
|
||||
&order.program,
|
||||
init.mutation_rounds,
|
||||
)?;
|
||||
let commitment =
|
||||
shared::gene::commitment_hex_with_context(&candidate, &init.session_id, init.mutation_step);
|
||||
|
||||
let mut req = HeartbeatRequest {
|
||||
session_id: init.session_id.clone(),
|
||||
@@ -534,14 +615,20 @@ fn run_rate_limit_trigger(client: &reqwest::blocking::Client, base_url: &str) ->
|
||||
}
|
||||
|
||||
if !rate_limited {
|
||||
return Err(anyhow!("Rate limiter was not triggered after 35 rapid requests"));
|
||||
return Err(anyhow!(
|
||||
"Rate limiter was not triggered after 35 rapid requests"
|
||||
));
|
||||
}
|
||||
|
||||
println!("Rate limiter correctly triggered.");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_file_scenario(_client: &reqwest::blocking::Client, _base_url: &str, file_path: &str) -> Result<()> {
|
||||
fn run_file_scenario(
|
||||
_client: &reqwest::blocking::Client,
|
||||
_base_url: &str,
|
||||
file_path: &str,
|
||||
) -> Result<()> {
|
||||
let scenario_content = std::fs::read_to_string(file_path)?;
|
||||
let scenario: serde_json::Value = serde_json::from_str(&scenario_content)?;
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ RestrictSUIDSGID=yes
|
||||
LockPersonality=yes
|
||||
SystemCallArchitectures=native
|
||||
ReadWritePaths=/run/chronoseal.pid
|
||||
ReadWritePaths=/var/lib/chronoseal
|
||||
|
||||
# Logging
|
||||
StandardOutput=journal
|
||||
|
||||
@@ -161,7 +161,7 @@ Content-Type: application/json
|
||||
| `stack_state.ip` | number | yes | VM instruction pointer as an unsigned 16-bit value |
|
||||
| `fingerprint.aspectRatio` | string | yes | Screen aspect ratio; server accepts numeric strings in range `0.5..=3.0` |
|
||||
| `fingerprint.devicePixelRatio` | string | yes | Device pixel ratio; server accepts numeric strings in range `(0, 5]` |
|
||||
| `fingerprint.hardwareConcurrency` | number | yes | Positive hardware concurrency value |
|
||||
| `fingerprint.hardwareConcurrency` | number | yes | Hardware concurrency value; server accepts integers in range `1..=256` |
|
||||
| `mutation_step` | number | yes | Mutation step currently expected by the server |
|
||||
| `gene_commitment` | string | yes | Context-bound commitment produced by the WASM mutation preview |
|
||||
| `signature` | string | yes | Ed25519 signature over the canonical payload |
|
||||
|
||||
@@ -102,7 +102,7 @@ Important files:
|
||||
| `crypto.rs` | canonical signing payload and Ed25519 signature verification |
|
||||
| `storage.rs` | `DbPool`, SQLite, Valkey compatibility, session persistence, stats |
|
||||
| `trust.rs` | mouse entropy validation |
|
||||
| `fingerprint.rs` | browser signal validation |
|
||||
| `fingerprint.rs` | browser signal validation, bounds enforcement, and fingerprint sanity checks |
|
||||
| `ratelimit.rs` | per-session rate limiting |
|
||||
| `cleanup.rs` | expired session removal |
|
||||
|
||||
@@ -154,7 +154,7 @@ The daemon builds a single Axum application with:
|
||||
Shared runtime state is held in `AppState`:
|
||||
|
||||
- `db_pool`: storage backend handle
|
||||
- `rate_limiter`: process-local rate limiter
|
||||
- `rate_limiter`: process-local DashMap-backed concurrent rate limiter
|
||||
- `config`: runtime configuration snapshot behind an `RwLock`
|
||||
|
||||
Configuration is resolved in this order:
|
||||
@@ -291,6 +291,7 @@ The current validation order is:
|
||||
11. Enforce timestamp drift bounds.
|
||||
12. Validate mouse entropy.
|
||||
13. Validate browser fingerprint fields.
|
||||
13a. Validate fingerprint bounds and numeric sanity constraints.
|
||||
14. Compute the next hash-chain value.
|
||||
15. Generate the next mutation order.
|
||||
16. Generate the next salt.
|
||||
@@ -369,6 +370,14 @@ Current checks include:
|
||||
- timestamp drift bound
|
||||
- basic fingerprint field validation
|
||||
|
||||
Current validation includes:
|
||||
|
||||
- aspect ratio bounds enforcement
|
||||
- device pixel ratio validation
|
||||
- hardware concurrency validation (1..=256)
|
||||
- rejection of NaN and infinite numeric values
|
||||
- rejection of malformed numeric strings
|
||||
|
||||
The checks are intentionally bounded and configurable. They should be treated as one layer in the attestation pipeline, not as the primary security primitive.
|
||||
|
||||
## Storage Architecture
|
||||
@@ -379,7 +388,7 @@ Storage is abstracted by `DbPool`.
|
||||
|---|---|---|
|
||||
| SQLite memory | `sqlite-in-memory` | default, process-local, ephemeral |
|
||||
| SQLite disk | `sqlite-in-disk` | persisted SQLite file at `db_path` |
|
||||
| Valkey | `valkey` | Valkey-compatible session store |
|
||||
| Valkey | `valkey` | Valkey-compatible session store utilizing thread-safe connection pooling |
|
||||
|
||||
The storage layer must support:
|
||||
|
||||
@@ -389,7 +398,7 @@ The storage layer must support:
|
||||
- delete expired sessions
|
||||
- report statistics
|
||||
|
||||
`valkey` mode reads `CHRONOSEAL_VALKEY_ADDR`, defaulting to `127.0.0.1:6666`. If connection setup fails, the current implementation logs a warning and falls back to in-memory SQLite.
|
||||
`valkey` mode reads `CHRONOSEAL_VALKEY_ADDR`, defaulting to `127.0.0.1:6666`. It establishes a connection pool using `r2d2` and the `redis` client crate. Session IDs are indexed using native Valkey sets (`sessions:ids`) to minimize overhead and avoid lock contention, while individual sessions are persisted with a native TTL (`SET ... EX`) matching their expiration times. If connection setup fails, it logs a warning and falls back to in-memory SQLite.
|
||||
|
||||
## Metrics and Observability
|
||||
|
||||
@@ -406,6 +415,14 @@ The metrics endpoint reports storage-derived counters including:
|
||||
|
||||
The daemon uses structured tracing and can log to journald through normal systemd operation. Operators should avoid debug logging in production because internal identifiers may appear in logs.
|
||||
|
||||
ChronoSeal applies security response headers including:
|
||||
|
||||
- Content-Security-Policy
|
||||
- X-Frame-Options
|
||||
- X-Content-Type-Options
|
||||
- Referrer-Policy
|
||||
- Permissions-Policy
|
||||
|
||||
## Trust Boundaries
|
||||
|
||||
### Browser Boundary
|
||||
@@ -503,6 +520,7 @@ SQLite disk or Valkey storage
|
||||
Recommended deployment properties:
|
||||
|
||||
- run under systemd with a dedicated service user
|
||||
- container deployments run as a dedicated non-root user by default
|
||||
- bind to localhost behind a reverse proxy unless direct exposure is required
|
||||
- serve over HTTPS
|
||||
- keep debug logs disabled
|
||||
@@ -510,6 +528,18 @@ Recommended deployment properties:
|
||||
- use `sqlite-in-memory` for ephemeral local sessions
|
||||
- use `sqlite-in-disk` or `valkey` when sessions must survive process restarts
|
||||
|
||||
## Security Hardening (v1.0.2)
|
||||
|
||||
Recent hardening improvements include:
|
||||
|
||||
- fingerprint bounds validation
|
||||
- VM stack depth protection
|
||||
- panic-resistant hashing paths
|
||||
- panic-resistant WASM helpers
|
||||
- DashMap-backed concurrent rate limiting
|
||||
- security response headers
|
||||
- non-root container execution
|
||||
|
||||
## Limitations
|
||||
|
||||
ChronoSeal is not:
|
||||
|
||||
@@ -16,7 +16,7 @@ ChronoSeal is a **self-hosted, cryptographic attestation daemon**. This document
|
||||
|
||||
## Detailed Analysis
|
||||
|
||||
### 1. ChronoSeal (v0.6.0)
|
||||
### 1. ChronoSeal (v1.0.2)
|
||||
|
||||
**Strengths:**
|
||||
- Strongest **cryptographic foundation** (Ed25519 signatures + Blake3 hash chain + Synthetic Gene Mutation Engine)
|
||||
@@ -118,3 +118,12 @@ It is particularly well-suited for:
|
||||
- Developers who value auditability
|
||||
|
||||
---
|
||||
|
||||
|
||||
## v1.0.2 Security Hardening Additions
|
||||
|
||||
- Fingerprint validation bounds enforcement
|
||||
- Security response headers
|
||||
- DashMap-backed concurrent rate limiting
|
||||
- WASM panic hardening
|
||||
- Non-root container execution
|
||||
@@ -68,6 +68,165 @@ Release binary:
|
||||
```text
|
||||
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`:
|
||||
|
||||
```bash
|
||||
checksec file target/release/chronoseal
|
||||
```
|
||||
|
||||
Expected protections:
|
||||
|
||||
```text
|
||||
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:
|
||||
|
||||
```bash
|
||||
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:
|
||||
|
||||
```bash
|
||||
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:
|
||||
|
||||
```bash
|
||||
RUSTFLAGS="--remap-path-prefix=$HOME=~" \
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
or:
|
||||
|
||||
```bash
|
||||
RUSTFLAGS="--remap-path-prefix=$(pwd)=." \
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
Recommended release profile:
|
||||
|
||||
```toml
|
||||
[profile.release]
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
panic = "abort"
|
||||
strip = "symbols"
|
||||
```
|
||||
|
||||
### Runtime Verification
|
||||
|
||||
Start the daemon locally:
|
||||
|
||||
```bash
|
||||
./chronoseal run --bind 127.0.0.1:8080
|
||||
```
|
||||
|
||||
Expected startup output:
|
||||
|
||||
```text
|
||||
INFO chronoseal daemon started bind=127.0.0.1:8080
|
||||
```
|
||||
|
||||
Verify core endpoints:
|
||||
|
||||
```bash
|
||||
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:
|
||||
|
||||
```text
|
||||
could not write PID file
|
||||
Permission denied
|
||||
```
|
||||
|
||||
For local development:
|
||||
|
||||
```bash
|
||||
chronoseal run --pid-file /tmp/chronoseal.pid
|
||||
```
|
||||
|
||||
For production systemd deployments, prefer:
|
||||
|
||||
```ini
|
||||
RuntimeDirectory=chronoseal
|
||||
```
|
||||
|
||||
and:
|
||||
|
||||
```text
|
||||
/run/chronoseal/chronoseal.pid
|
||||
```
|
||||
|
||||
managed by systemd.
|
||||
|
||||
### Additional Validation
|
||||
|
||||
Inspect runtime dependencies:
|
||||
|
||||
```bash
|
||||
ldd target/release/chronoseal
|
||||
```
|
||||
|
||||
Verify ELF program headers:
|
||||
|
||||
```bash
|
||||
readelf -l target/release/chronoseal
|
||||
```
|
||||
|
||||
Look for:
|
||||
|
||||
```text
|
||||
GNU_RELRO
|
||||
GNU_STACK
|
||||
```
|
||||
|
||||
Confirm binary size:
|
||||
|
||||
```bash
|
||||
ls -lh target/release/chronoseal
|
||||
```
|
||||
|
||||
These checks should be performed before publishing release artifacts, container images, or distribution packages.
|
||||
|
||||
## Native Install
|
||||
|
||||
@@ -178,13 +337,63 @@ sudo mkdir -p /var/lib/chronoseal
|
||||
sudo chown -R chronoseal:chronoseal /var/lib/chronoseal
|
||||
```
|
||||
|
||||
For Valkey:
|
||||
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)**:
|
||||
```bash
|
||||
sudo apt-get install -y valkey-server
|
||||
```
|
||||
* **Redis (Debian/Ubuntu)**:
|
||||
```bash
|
||||
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:
|
||||
```bash
|
||||
# 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:
|
||||
```bash
|
||||
# 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:
|
||||
|
||||
```bash
|
||||
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**:
|
||||
```bash
|
||||
export CHRONOSEAL_VALKEY_ADDR=redis://:your_password@127.0.0.1:6666
|
||||
```
|
||||
* **Username & Password**:
|
||||
```bash
|
||||
export CHRONOSEAL_VALKEY_ADDR=redis://your_username:your_password@127.0.0.1:6666
|
||||
```
|
||||
* **Secure Connection (SSL/TLS)**: Use the `rediss://` scheme prefix:
|
||||
```bash
|
||||
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
|
||||
@@ -328,3 +537,44 @@ Avoid debug logging in production because internal identifiers may be written to
|
||||
- Protect SQLite and log directories with correct ownership.
|
||||
- Monitor `/health`, `/stats`, and `/metrics`.
|
||||
- Verify `chronoseal config check` after 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:
|
||||
|
||||
```text
|
||||
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.
|
||||
@@ -31,16 +31,16 @@ The optimal values depend on your threat model and expected client hardware.
|
||||
| Profile | `gene_size` | `mutation_rounds` | Security Level | Recommended Usage |
|
||||
| ----------------- | ----------- | ----------------- | ---------------- | -------------------------------- |
|
||||
| Default | 512 | 4 | Moderate | Development and testing |
|
||||
| Recommended | 2048 | 16 | Strong | Most production deployments |
|
||||
| High Security | 4096 | 32 | Very Strong | Sensitive applications |
|
||||
| Maximum Practical | 8192 | 64 | Extremely Strong | High-value targets |
|
||||
| Experimental | 65536 | 65536 | Research Only | Benchmarking and experimentation |
|
||||
| Recommended | 2048 | 4 | Strong | Most production deployments |
|
||||
| High Security | 4096 | 8 | Very Strong | Sensitive applications |
|
||||
| Maximum Practical | 4096 | 10 | Extremely Strong | High-value targets |
|
||||
| Experimental | 4096 | 10 | Research Only | Benchmarking and experimentation |
|
||||
|
||||
### Recommended Production Configuration
|
||||
|
||||
```toml
|
||||
gene_size = 2048
|
||||
mutation_rounds = 16
|
||||
mutation_rounds = 4
|
||||
```
|
||||
|
||||
This configuration provides a strong balance between security and runtime overhead for most deployments.
|
||||
@@ -55,7 +55,7 @@ Edit your configuration file:
|
||||
# Mutation Engine Settings
|
||||
|
||||
gene_size = 2048
|
||||
mutation_rounds = 16
|
||||
mutation_rounds = 4
|
||||
```
|
||||
|
||||
Common configuration locations:
|
||||
@@ -137,7 +137,7 @@ Browser developer tools can also be used to monitor:
|
||||
|
||||
```toml
|
||||
gene_size = 2048
|
||||
mutation_rounds = 16
|
||||
mutation_rounds = 4
|
||||
```
|
||||
|
||||
Deploy and observe normal usage patterns.
|
||||
@@ -158,11 +158,10 @@ Increase one parameter at a time.
|
||||
Recommended progression:
|
||||
|
||||
```text
|
||||
2048 / 16
|
||||
4096 / 16
|
||||
4096 / 32
|
||||
8192 / 32
|
||||
8192 / 64
|
||||
2048 / 4
|
||||
4096 / 4
|
||||
4096 / 8
|
||||
4096 / 10
|
||||
```
|
||||
|
||||
This makes it easier to identify performance bottlenecks.
|
||||
@@ -194,9 +193,9 @@ Future deployments may choose to dynamically increase mutation strength based on
|
||||
Example policy:
|
||||
|
||||
```text
|
||||
New session → 2048 / 16
|
||||
Suspicious session → 4096 / 32
|
||||
Elevated-risk action → 8192 / 64
|
||||
New session → 2048 / 4
|
||||
Suspicious session → 4096 / 8
|
||||
Elevated-risk action → 4096 / 10
|
||||
```
|
||||
|
||||
---
|
||||
@@ -213,7 +212,7 @@ wasm-pack build wasm --target web --release
|
||||
|
||||
### General Guidance
|
||||
|
||||
* Keep `mutation_rounds` below 64 for most deployments.
|
||||
* Keep `mutation_rounds` at or below 10 for all deployments.
|
||||
* Prefer increasing `gene_size` before dramatically increasing rounds.
|
||||
* Benchmark on representative client hardware.
|
||||
* Monitor browser CPU utilization during load testing.
|
||||
@@ -260,7 +259,7 @@ For most production deployments:
|
||||
|
||||
```toml
|
||||
gene_size = 2048
|
||||
mutation_rounds = 16
|
||||
mutation_rounds = 4
|
||||
```
|
||||
|
||||
This configuration provides a strong balance between security, performance, and compatibility across desktop and mobile devices.
|
||||
@@ -281,3 +280,13 @@ chronoseal config check
|
||||
chronoseal stats
|
||||
chronoseal health
|
||||
```
|
||||
|
||||
|
||||
## v1.0.2 Limits
|
||||
|
||||
Current supported range:
|
||||
|
||||
```toml
|
||||
gene_size = 1..=4096
|
||||
mutation_rounds = 1..=10
|
||||
```
|
||||
@@ -11,7 +11,7 @@ ChronoSeal operates as a stateful, sequential challenge-response chain over HTTP
|
||||
```
|
||||
Client (JS/WASM) Server (chronoseald)
|
||||
| |
|
||||
| 1. POST /init { public_key: String } -----------------> |
|
||||
| 1. POST /init { public_key: String } ----------------> |
|
||||
| | (Generates VM Opcodes)
|
||||
| | (Computes initial hash chain head H_0)
|
||||
| | (Saves initial session record)
|
||||
@@ -19,7 +19,7 @@ Client (JS/WASM) Server (chronoseald)
|
||||
| |
|
||||
| [Client executes VM program & prepares gene preview] |
|
||||
| |
|
||||
| 2. POST /hb { HeartbeatRequest } ---------------------> |
|
||||
| 2. POST /hb { HeartbeatRequest } --------------------> |
|
||||
| | (Loads session & executes CAS check)
|
||||
| | (Verifies Ed25519 signature)
|
||||
| | (Validates VM stack-state parity)
|
||||
@@ -86,3 +86,13 @@ The client VM executes instructions sequentially. The instruction set consists o
|
||||
* `0x08`: Unary Bitwise Not (`!a`). Requires at least 1 element on the stack.
|
||||
* `0x09`: Hash Stack. Hashes all stack elements using BLAKE3 and reduces it to a single `u32` value, clearing the stack and pushing the hash.
|
||||
* *Any other opcode:* Terminates VM execution immediately.
|
||||
|
||||
|
||||
## v1.0.2 Protocol Hardening
|
||||
|
||||
- Fingerprint aspect ratio validation
|
||||
- Device pixel ratio validation
|
||||
- Hardware concurrency validation (1..=256)
|
||||
- Entropy event cap (500 events)
|
||||
- IP-based rate limiting
|
||||
- Silent rejection preserved for protocol failures
|
||||
@@ -31,3 +31,10 @@ ChronoSeal is a **cost-raising security layer**. It is designed to force automat
|
||||
1. **Chain Continuity:** A session state cannot bifurcate. Every heartbeat must advance the state head using the latest salt.
|
||||
2. **VM Parity:** Stack state must exactly match the execution output of the server's issued opcode sequence.
|
||||
3. **Dynamic Challenges:** Client gene updates must match the server-issued mutation program.
|
||||
|
||||
|
||||
## Additional Assumptions (v1.0.2)
|
||||
|
||||
- Fingerprints are sanity signals, not identity proofs.
|
||||
- Rate limiting assumes client IP visibility.
|
||||
- Security headers reduce browser attack surface.
|
||||
@@ -1,57 +1,65 @@
|
||||
# ChronoSeal Testing Strategy
|
||||
|
||||
ChronoSeal maintains a rigorous, security-first test suite focused on cryptographic correctness, deterministic server ↔ WASM parity, mutation engine integrity, replay resistance, tampering detection, behavioral validation, and storage reliability.
|
||||
ChronoSeal maintains a security-focused test suite designed to validate cryptographic correctness, deterministic server ↔ WASM parity, replay resistance, mutation engine integrity, browser fingerprint validation, behavioral trust checks, storage reliability, and protocol hardening.
|
||||
|
||||
As of **v0.6.1**, the project contains **89 passing tests** across the server, WASM, and shared protocol crates.
|
||||
As of **v1.0.2**, the project contains **100 passing tests** across the server, WASM, shared protocol, and property-testing suites.
|
||||
|
||||
| Crate | Tests |
|
||||
| ------------------- | -----: |
|
||||
| `chronoseal-server` | 30 |
|
||||
| `chronoseal-wasm` | 24 |
|
||||
| `shared` | 35 |
|
||||
| **Total** | **89** |
|
||||
| Crate | Tests |
|
||||
| ------------------------------- | ------: |
|
||||
| `chronoseal-server` | 38 |
|
||||
| `chronoseal-wasm` | 24 |
|
||||
| `shared` (unit tests) | 36 |
|
||||
| `shared` (property-based tests) | 2 |
|
||||
| `chronoseal-replay` | 0 |
|
||||
| **Total** | **100** |
|
||||
|
||||
---
|
||||
|
||||
## Test Philosophy
|
||||
# Test Philosophy
|
||||
|
||||
ChronoSeal testing prioritizes:
|
||||
ChronoSeal prioritizes testing of security invariants rather than raw coverage percentages.
|
||||
|
||||
* **Security invariants** over raw coverage metrics
|
||||
* **Deterministic parity** between server and browser WASM runtimes
|
||||
* **Negative-path testing** (tampering, replay, malformed input, edge cases)
|
||||
* **Fuzz-style and randomized testing** for mutation logic
|
||||
* **Performance regression detection**
|
||||
* **Long-term protocol stability**
|
||||
Primary goals:
|
||||
|
||||
Particular emphasis is placed on ensuring that browser-side WASM execution produces identical results to server-side validation.
|
||||
* Verify deterministic server ↔ WASM behavior
|
||||
* Detect protocol divergence early
|
||||
* Prevent replay attacks
|
||||
* Validate mutation engine correctness
|
||||
* Detect malformed and adversarial input handling
|
||||
* Prevent VM and protocol panics
|
||||
* Maintain storage backend compatibility
|
||||
* Protect browser attestation continuity guarantees
|
||||
|
||||
The project emphasizes negative-path testing and adversarial validation rather than only testing successful execution paths.
|
||||
|
||||
---
|
||||
|
||||
# Test Categories
|
||||
|
||||
## 1. Configuration & CLI
|
||||
## 1. Configuration & Runtime
|
||||
|
||||
Configuration tests verify:
|
||||
|
||||
* Database backend selection
|
||||
* TOML configuration parsing
|
||||
* Command-line override behavior
|
||||
* Default configuration values
|
||||
* Runtime initialization logic
|
||||
* TOML parsing
|
||||
* Runtime initialization
|
||||
* Database backend selection
|
||||
* CLI override behavior
|
||||
|
||||
Supported backends include:
|
||||
Covered functionality:
|
||||
|
||||
* `sqlite-in-memory`
|
||||
* `sqlite-in-disk`
|
||||
* `valkey`
|
||||
* SQLite in-memory backend
|
||||
* SQLite disk backend
|
||||
* Valkey compatibility mode
|
||||
* Runtime configuration validation
|
||||
|
||||
Example tests:
|
||||
|
||||
```text
|
||||
test_apply_run_args_overrides_db_type
|
||||
test_default_db_type_is_sqlite_in_memory
|
||||
test_apply_run_args_overrides_db_type
|
||||
test_toml_parses_db_type_kebab_case
|
||||
test_db_type_report_lists_backends
|
||||
test_init_db_pool_sqlite_in_memory
|
||||
test_init_db_pool_sqlite_in_disk
|
||||
test_init_db_pool_valkey_compat_mode
|
||||
@@ -59,17 +67,53 @@ test_init_db_pool_valkey_compat_mode
|
||||
|
||||
---
|
||||
|
||||
## 2. Session Lifecycle & Verification
|
||||
## 2. Browser Fingerprint Validation
|
||||
|
||||
Session tests validate:
|
||||
Introduced and expanded in v1.0.2.
|
||||
|
||||
Fingerprint validation protects the attestation pipeline from malformed or unrealistic browser metadata.
|
||||
|
||||
Validation coverage includes:
|
||||
|
||||
* Aspect ratio validation
|
||||
* Device pixel ratio validation
|
||||
* Hardware concurrency validation
|
||||
* Boundary value acceptance
|
||||
* NaN rejection
|
||||
* Infinity rejection
|
||||
* Malformed numeric value rejection
|
||||
|
||||
Example tests:
|
||||
|
||||
```text
|
||||
accepts_valid_fingerprint
|
||||
accepts_boundary_values
|
||||
rejects_invalid_aspect_ratios
|
||||
rejects_invalid_device_pixel_ratios
|
||||
rejects_invalid_hardware_concurrency
|
||||
```
|
||||
|
||||
Validation constraints currently include:
|
||||
|
||||
| Field | Allowed Range |
|
||||
| ------------------- | --------------------- |
|
||||
| aspectRatio | finite positive value |
|
||||
| devicePixelRatio | greater than zero |
|
||||
| hardwareConcurrency | 1..=256 |
|
||||
|
||||
---
|
||||
|
||||
## 3. Session Lifecycle & Protocol Verification
|
||||
|
||||
Session tests verify:
|
||||
|
||||
* Session creation
|
||||
* Session expiration
|
||||
* Public key validation
|
||||
* Expiration handling
|
||||
* Replay attack prevention
|
||||
* Replay attack resistance
|
||||
* Mutation commitment verification
|
||||
* Mutation step enforcement
|
||||
* Commitment verification
|
||||
* Long-running deterministic parity
|
||||
* Deterministic long-running parity
|
||||
|
||||
Example tests:
|
||||
|
||||
@@ -86,39 +130,12 @@ test_deterministic_server_client_parity_across_many_heartbeats
|
||||
|
||||
---
|
||||
|
||||
## 3. Mutation Engine (Core Focus)
|
||||
## 4. Heartbeat Validation
|
||||
|
||||
The Synthetic Gene Mutation Engine is one of the most security-critical components in ChronoSeal.
|
||||
|
||||
Testing focuses on:
|
||||
|
||||
* Deterministic server/client parity
|
||||
* Mutation order execution
|
||||
* Gene state integrity
|
||||
* Preview → Commit → Discard lifecycle
|
||||
* Randomized mutation programs
|
||||
* Edge-case validation
|
||||
* Performance regression detection
|
||||
|
||||
Example tests:
|
||||
|
||||
```text
|
||||
test_server_client_parity_across_random_orders
|
||||
test_generate_order_is_deterministic_for_seeded_rng
|
||||
test_invalid_positions_wrap_deterministically
|
||||
test_mutation_chain
|
||||
test_fuzz_style_random_program_bytes_do_not_diverge
|
||||
test_performance_smoke_mutation_execution
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Heartbeat Handler
|
||||
|
||||
Heartbeat validation tests verify:
|
||||
Heartbeat tests validate:
|
||||
|
||||
* Successful state advancement
|
||||
* Silent rejection behavior
|
||||
* Silent rejection semantics
|
||||
* Commitment validation
|
||||
* Rate limiting
|
||||
* Next-state mutation generation
|
||||
@@ -133,16 +150,17 @@ test_handler_rate_limit_returns_no_mutation_data
|
||||
|
||||
---
|
||||
|
||||
## 5. Trust & Behavioral Validation
|
||||
## 5. Behavioral Trust Validation
|
||||
|
||||
Behavioral validation tests verify:
|
||||
Trust validation focuses on lightweight behavioral signals.
|
||||
|
||||
* Minimum mouse activity
|
||||
Coverage includes:
|
||||
|
||||
* Minimum event count
|
||||
* Minimum movement distance
|
||||
* Pause detection
|
||||
* Speed thresholds
|
||||
* Optional activity requirements
|
||||
* Fingerprint-related validation paths
|
||||
* Maximum speed thresholds
|
||||
* Activity requirement toggles
|
||||
|
||||
Example tests:
|
||||
|
||||
@@ -159,43 +177,75 @@ test_validate_mouse_require_activity_toggle
|
||||
|
||||
## 6. Storage Layer
|
||||
|
||||
Storage tests verify:
|
||||
Storage tests verify backend correctness and concurrency behavior.
|
||||
|
||||
* SQLite in-memory operation
|
||||
* SQLite disk-backed operation
|
||||
Covered backends:
|
||||
|
||||
* SQLite in-memory
|
||||
* SQLite disk
|
||||
* Valkey compatibility mode
|
||||
* Session CRUD behavior
|
||||
* Expiration cleanup
|
||||
* Runtime statistics reporting
|
||||
|
||||
These tests ensure storage implementations remain interchangeable without affecting protocol behavior.
|
||||
Example tests:
|
||||
|
||||
```text
|
||||
test_sqlite_pool_concurrency
|
||||
test_valkey_pool_concurrency
|
||||
test_valkey_store_operations
|
||||
```
|
||||
|
||||
Validation includes:
|
||||
|
||||
* Session persistence
|
||||
* Session updates
|
||||
* Concurrent access
|
||||
* Statistics collection
|
||||
* Backend compatibility
|
||||
|
||||
---
|
||||
|
||||
## 7. VM Core
|
||||
## 7. Rate Limiting
|
||||
|
||||
The VM core is tested extensively across both WASM and shared crates.
|
||||
Rate limiter tests verify:
|
||||
|
||||
Coverage includes:
|
||||
* Request counting
|
||||
* Window expiration
|
||||
* Stale entry eviction
|
||||
|
||||
* ADD
|
||||
* SUB
|
||||
* MUL
|
||||
* XOR
|
||||
* AND
|
||||
* OR
|
||||
* NOT
|
||||
* HASH
|
||||
* ROT
|
||||
* PUSH
|
||||
Example tests:
|
||||
|
||||
Edge cases include:
|
||||
```text
|
||||
test_rate_limiter
|
||||
test_rate_limiter_eviction
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. VM Core
|
||||
|
||||
The VM implementation is tested across both WASM and shared crates.
|
||||
|
||||
Covered operations:
|
||||
|
||||
```text
|
||||
ADD
|
||||
SUB
|
||||
MUL
|
||||
XOR
|
||||
AND
|
||||
OR
|
||||
NOT
|
||||
HASH
|
||||
ROT
|
||||
PUSH
|
||||
```
|
||||
|
||||
Validation includes:
|
||||
|
||||
* Stack underflow
|
||||
* Truncated instructions
|
||||
* Unknown opcodes
|
||||
* Wrapping arithmetic
|
||||
* Invalid instruction streams
|
||||
* Stack underflow detection
|
||||
* Invalid opcode rejection
|
||||
* Truncated instruction rejection
|
||||
* Instruction safety
|
||||
|
||||
Example tests:
|
||||
|
||||
@@ -215,115 +265,84 @@ test_rejects_truncated_instruction
|
||||
|
||||
---
|
||||
|
||||
# Server Test Coverage (`chronoseal-server`)
|
||||
## 9. Synthetic Gene Mutation Engine
|
||||
|
||||
The server crate currently contains **30 tests** covering:
|
||||
The mutation engine is a critical security component.
|
||||
|
||||
* Configuration
|
||||
* Runtime initialization
|
||||
* Session management
|
||||
* Heartbeat validation
|
||||
* Rate limiting
|
||||
* Trust validation
|
||||
Coverage includes:
|
||||
|
||||
The server tests focus heavily on protocol enforcement and security validation.
|
||||
|
||||
---
|
||||
|
||||
# WASM Test Coverage (`chronoseal-wasm`)
|
||||
|
||||
The WASM crate currently contains **24 tests** covering:
|
||||
|
||||
* VM execution
|
||||
* Browser-side mutation lifecycle
|
||||
* Gene initialization
|
||||
* Mutation preview
|
||||
* Mutation commit/discard behavior
|
||||
* Deterministic parity with shared logic
|
||||
* Mutation order execution
|
||||
* Deterministic parity
|
||||
* Randomized mutation programs
|
||||
* Preview lifecycle
|
||||
* Commit lifecycle
|
||||
* Discard lifecycle
|
||||
* Environment validation
|
||||
* Gene integrity
|
||||
|
||||
Example tests:
|
||||
|
||||
```text
|
||||
test_mutation_chain
|
||||
test_generate_order_is_deterministic_for_seeded_rng
|
||||
test_server_client_parity_across_random_orders
|
||||
test_preview_commitment_matches_shared_engine
|
||||
test_commit_applies_preview
|
||||
test_discard_preview_keeps_committed_state
|
||||
test_table_driven_parity_across_many_generated_orders
|
||||
test_fuzz_style_random_program_bytes_do_not_diverge
|
||||
```
|
||||
|
||||
These tests ensure browser-generated commitments remain consistent with server expectations.
|
||||
|
||||
---
|
||||
|
||||
# Shared Crate Coverage (`shared`)
|
||||
## 10. Property-Based Testing
|
||||
|
||||
The shared crate currently contains **35 tests** and represents the core protocol implementation used by both server and browser runtimes.
|
||||
ChronoSeal uses `proptest` to validate protocol invariants under arbitrary input.
|
||||
|
||||
Coverage includes:
|
||||
|
||||
### Synthetic Gene Engine
|
||||
Property tests:
|
||||
|
||||
```text
|
||||
test_new_state_with_default_size
|
||||
test_new_state_rejects_invalid_sizes
|
||||
test_commitment_changes_when_gene_or_environment_changes
|
||||
test_encode_decode_environment_roundtrip
|
||||
test_table_driven_randomized_environment_roundtrip
|
||||
test_vm_execute_never_panics
|
||||
test_gene_environment_roundtrip_never_panics
|
||||
```
|
||||
|
||||
### Mutation Engine
|
||||
|
||||
```text
|
||||
test_opcode_insert
|
||||
test_opcode_delete
|
||||
test_opcode_mutate_point
|
||||
test_opcode_apply_mutagen
|
||||
test_opcode_finalize_gene_hash
|
||||
test_mutation_chain
|
||||
```
|
||||
|
||||
### Validation & Hardening
|
||||
|
||||
```text
|
||||
test_rejects_stack_underflow
|
||||
test_rejects_truncated_instruction
|
||||
test_rejects_unknown_opcode
|
||||
test_zero_length_gene_is_rejected
|
||||
```
|
||||
|
||||
### Deterministic Parity
|
||||
|
||||
```text
|
||||
test_server_client_parity_across_random_orders
|
||||
test_generate_order_is_deterministic_for_seeded_rng
|
||||
test_invalid_positions_wrap_deterministically
|
||||
```
|
||||
|
||||
### Fuzz & Regression Testing
|
||||
|
||||
```text
|
||||
test_fuzz_style_random_program_bytes_do_not_diverge
|
||||
test_performance_smoke_mutation_execution
|
||||
```
|
||||
These tests continuously exercise malformed and randomized inputs to ensure graceful handling and panic resistance.
|
||||
|
||||
---
|
||||
|
||||
# Running the Test Suite
|
||||
|
||||
Run the full workspace:
|
||||
Run all tests:
|
||||
|
||||
```bash
|
||||
cargo test --workspace
|
||||
```
|
||||
|
||||
Run individual crates:
|
||||
Run server tests:
|
||||
|
||||
```bash
|
||||
cargo test -p shared
|
||||
cargo test -p chronoseal-wasm
|
||||
cargo test -p chronoseal-server
|
||||
```
|
||||
|
||||
Display test output:
|
||||
Run fingerprint tests only:
|
||||
|
||||
```bash
|
||||
cargo test -p chronoseal-server fingerprint
|
||||
```
|
||||
|
||||
Run WASM tests:
|
||||
|
||||
```bash
|
||||
cargo test -p chronoseal-wasm
|
||||
```
|
||||
|
||||
Run shared tests:
|
||||
|
||||
```bash
|
||||
cargo test -p shared
|
||||
```
|
||||
|
||||
Show output:
|
||||
|
||||
```bash
|
||||
cargo test -- --nocapture
|
||||
@@ -333,62 +352,44 @@ cargo test -- --nocapture
|
||||
|
||||
# Critical Security Tests
|
||||
|
||||
The following tests protect ChronoSeal's core protocol guarantees and should be treated as **release-blocking** if they fail:
|
||||
The following tests are considered release-blocking:
|
||||
|
||||
```text
|
||||
test_mutation_commitment_tamper_is_rejected
|
||||
test_replay_attack_is_rejected
|
||||
test_mutation_commitment_tamper_is_rejected
|
||||
test_handler_tampered_commitment_is_silent_failure
|
||||
test_server_client_parity_across_random_orders
|
||||
test_deterministic_server_client_parity_across_many_heartbeats
|
||||
test_fuzz_style_random_program_bytes_do_not_diverge
|
||||
test_server_client_parity_across_random_orders
|
||||
test_vm_execute_never_panics
|
||||
test_gene_environment_roundtrip_never_panics
|
||||
rejects_invalid_aspect_ratios
|
||||
rejects_invalid_device_pixel_ratios
|
||||
rejects_invalid_hardware_concurrency
|
||||
```
|
||||
|
||||
These tests directly validate resistance to replay attacks, protocol divergence, mutation tampering, and commitment forgery.
|
||||
|
||||
---
|
||||
|
||||
# Contributing New Tests
|
||||
|
||||
When adding new functionality:
|
||||
|
||||
1. Prefer placing protocol logic tests in `shared/`
|
||||
2. Ensure server ↔ WASM parity is validated
|
||||
3. Include negative-path test cases
|
||||
4. Add randomized testing where appropriate
|
||||
5. Update this document when introducing major new categories
|
||||
|
||||
---
|
||||
|
||||
# Future Improvements
|
||||
|
||||
Planned enhancements include:
|
||||
|
||||
* Property-based testing using `proptest`
|
||||
* Browser-driven end-to-end integration tests
|
||||
* Valkey concurrency and failover testing
|
||||
* Automated benchmark execution in CI
|
||||
* Expanded mutation-engine fuzzing
|
||||
* CI-enforced performance regression thresholds
|
||||
These tests directly protect protocol integrity, replay resistance, mutation validation, deterministic execution, and fingerprint hardening.
|
||||
|
||||
---
|
||||
|
||||
# Conclusion
|
||||
|
||||
ChronoSeal's testing strategy is centered on preserving deterministic behavior, cryptographic correctness, and protocol integrity.
|
||||
ChronoSeal's testing strategy focuses on preserving deterministic behavior, protocol integrity, cryptographic correctness, browser ↔ server parity, and resistance to malformed or adversarial input.
|
||||
|
||||
The current suite of **89 tests** provides broad coverage across:
|
||||
The current suite of **100 passing tests** provides comprehensive coverage across:
|
||||
|
||||
* Session security
|
||||
* Heartbeat validation
|
||||
* Mutation engine correctness
|
||||
* Deterministic server/WASM parity
|
||||
* Trust validation
|
||||
* Storage abstraction
|
||||
* Configuration
|
||||
* Runtime initialization
|
||||
* Browser fingerprint validation
|
||||
* Session lifecycle management
|
||||
* Heartbeat verification
|
||||
* Mutation engine execution
|
||||
* VM safety
|
||||
* Behavioral validation
|
||||
* Storage backends
|
||||
* Replay resistance
|
||||
* Protocol hardening
|
||||
* Property-based protocol hardening
|
||||
|
||||
Maintaining and expanding this test suite remains a core project priority as ChronoSeal evolves.
|
||||
Maintaining and expanding this test suite remains a core project priority.
|
||||
|
||||
**Last Updated:** May 2026 (v0.6.1)
|
||||
**Last Updated:** June 2026 (v1.0.2)
|
||||
|
||||
@@ -106,6 +106,23 @@ Expected result:
|
||||
- ChronoSeal does not claim complete prevention
|
||||
- additional application-level controls are required
|
||||
|
||||
## Attacker Classification Boundaries
|
||||
|
||||
### Protected
|
||||
* **Commodity Scrapers:** Simple HTTP clients (`curl`, Python `requests`, Go HTTP clients) that cannot execute JavaScript or WebAssembly.
|
||||
* **Simple Replay Attackers:** Intercepted heartbeat payloads cannot be reused because of the strict hash-chain sequencing and salt rotation.
|
||||
* **Signature Forgers:** Heartbeats without the session's private key will fail Ed25519 verification.
|
||||
|
||||
### Partially Protected
|
||||
* **Headless Automation (Puppeteer, Playwright):** Attackers must load the WASM runtime, execute the VM instructions, calculate gene mutations, and simulate realistic human mouse interactions. This significantly increases CPU and system memory overhead, reducing the scale of bot operations.
|
||||
* **Stealth Automation Frameworks:** Advanced frameworks must maintain state sync across multiple heartbeat cycles, exposing them to timing detection.
|
||||
|
||||
### Unprotected
|
||||
* **WASM Key Extraction:** A reverse engineer with full browser process control can extract the private key from WASM memory.
|
||||
* **Malware Operators:** Keyloggers, screen scrapers, or memory dumpers operating at the OS level are outside the application trust boundary.
|
||||
* **MITM Interceptors (without TLS):** Plaintext traffic can be intercepted. (TLS termination is assumed).
|
||||
* **Insiders / Storage Tampering:** Attackers with direct write access to the SQLite database or Valkey instance can forge or hijack active session states.
|
||||
|
||||
## Attack Vectors and Mitigations
|
||||
|
||||
### Replay
|
||||
@@ -238,3 +255,17 @@ Recommended:
|
||||
## Disclosure
|
||||
|
||||
See [../SECURITY.md](../SECURITY.md) for the vulnerability disclosure policy.
|
||||
|
||||
|
||||
## Additional Mitigations (v1.0.2)
|
||||
|
||||
### Fingerprint Abuse
|
||||
- Bounds enforcement
|
||||
- Numeric sanity validation
|
||||
|
||||
### Resource Exhaustion
|
||||
- Entropy event cap
|
||||
- Rate limiting
|
||||
|
||||
### Browser Hardening
|
||||
- Security response headers
|
||||
@@ -54,3 +54,13 @@ To deny attackers a feedback oracle, the ChronoSeal heartbeat endpoint (`POST /h
|
||||
* **Error Cause:** The client submitted more requests than allowed by the server's rate-limiting config (e.g., `rate_limit_count` per `rate_limit_window_secs`).
|
||||
* **Diagnostic Signal:** The server returns `200 OK` with `{"status": "ok"}` but no next state data.
|
||||
* **Remediation:** Reduce heartbeat frequency or adjust rate limit parameters in the daemon configuration.
|
||||
|
||||
|
||||
## Additional v1.0.2 Failure Modes
|
||||
|
||||
- Invalid aspect ratio
|
||||
- Invalid device pixel ratio
|
||||
- Invalid hardware concurrency
|
||||
- NaN or infinite fingerprint values
|
||||
- Entropy event count exceeds 500
|
||||
- Client IP rate limited
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 594 B |
|
After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" id="Layer_1" x="0px" y="0px" width="296.99997mm" viewBox="0 0 1122.5196 793.7008" enable-background="new 0 0 1254 1254" xml:space="preserve" height="210mm" sodipodi:docname="logo1.svg" inkscape:export-filename="logo1.png" inkscape:export-xdpi="96" inkscape:export-ydpi="96" inkscape:version="1.4.4 (dcaf3e7d9e, 2026-05-05)" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg"><metadata><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:dc="http://purl.org/dc/elements/1.1/"><rdf:Description><dc:creator>RealFaviconGenerator</dc:creator><dc:source>https://realfavicongenerator.net</dc:source></rdf:Description></rdf:RDF></metadata><sodipodi:namedview id="namedview1" pagecolor="#ffffff" bordercolor="#000000" borderopacity="0.25" inkscape:showpageshadow="2" inkscape:pageopacity="0.0" inkscape:pagecheckerboard="0" inkscape:deskcolor="#d1d1d1" inkscape:document-units="mm" inkscape:zoom="1.1654266" inkscape:cx="561.16791" inkscape:cy="396.85039" inkscape:window-width="2048" inkscape:window-height="1205" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1" inkscape:current-layer="Layer_1"></sodipodi:namedview><defs id="defs44"></defs><path fill="none" opacity="1" stroke="none" d="m 791.89377,632.01366 c 1.04068,-1.03091 1.90903,-2.38956 3.14896,-3.04166 11.48425,-6.03955 22.98721,-12.04745 34.57742,-17.88034 8.52002,-4.28775 14.37558,-10.49713 14.23218,-20.46771 -0.14127,-9.82202 -6.25309,-15.51495 -14.66742,-19.5827 -61.76651,-29.85993 -123.47361,-59.84277 -185.21183,-89.76129 -10.48242,-5.07981 -20.81335,-10.5555 -31.58477,-14.93764 -19.71186,-8.01936 -39.87799,-8.40881 -59.18305,0.84148 -71.77527,34.39209 -143.33667,69.23047 -215.00567,103.84463 -8.40815,4.06091 -14.32166,9.84101 -14.39978,19.74008 -0.079,10.00498 5.52282,16.22797 14.08978,20.54743 12.3277,6.21567 24.51239,12.71484 37.34228,19.41369 7.51429,3.93381 14.42273,7.5896 21.37854,11.15274 31.68775,16.23208 63.40558,32.40548 95.07782,48.66778 14.943,7.6726 29.74964,15.6123 44.73053,23.2091 8.44464,4.2823 16.79547,9.1534 25.75525,11.9324 18.81989,5.8372 37.10712,3.9335 54.83185,-5.5578 25.51599,-13.6634 51.41541,-26.6141 77.2135,-39.7465 30.82071,-15.6894 61.7009,-31.26196 92.58359,-46.82907 1.56006,-0.78641 3.38776,-1.0419 5.09082,-1.54462 z" id="path2"></path><path fill="#4b4d51" opacity="1" stroke="none" d="m 791.89376,550.60089 c 1.04068,-1.03091 1.90903,-2.38956 3.14896,-3.04166 11.48425,-6.03955 22.98721,-12.04745 34.57742,-17.88034 8.52002,-4.28775 14.37558,-10.49713 14.23218,-20.46771 -0.14127,-9.82202 -6.25309,-15.51495 -14.66742,-19.5827 -61.76651,-29.85993 -123.47361,-59.84277 -185.21183,-89.76129 -10.48242,-5.07981 -20.81335,-10.5555 -31.58477,-14.93764 -19.71186,-8.01936 -39.87799,-8.40881 -59.18305,0.84148 -71.77522,34.39209 -143.33662,69.23047 -215.00562,103.84463 -8.4082,4.06091 -14.3217,9.84101 -14.3998,19.74008 -0.079,10.00498 5.5228,16.22797 14.0898,20.54743 12.3277,6.21567 24.5123,12.71484 37.3422,19.41369 7.5143,3.93381 14.4228,7.5896 21.3786,11.15274 31.6877,16.23212 63.4056,32.40552 95.0778,48.66782 14.943,7.67261 29.7496,15.61227 44.73052,23.20911 8.44464,4.28232 16.79547,9.15341 25.75525,11.93237 18.81989,5.83722 37.10712,3.93353 54.83185,-5.55777 25.51599,-13.66336 51.41541,-26.61407 77.2135,-39.74655 30.82071,-15.68933 61.7009,-31.26196 92.58359,-46.82907 1.56006,-0.78641 3.38776,-1.0419 5.09082,-1.54462 z" id="path45" style="fill:#e1e1e4;fill-opacity:1"></path><path fill="#4b4d51" opacity="1" stroke="none" d="m 791.89377,469.18814 c 1.04068,-1.03091 1.90903,-2.38956 3.14896,-3.04166 11.48425,-6.03955 22.98721,-12.04745 34.57742,-17.88034 8.52002,-4.28775 14.37558,-10.49713 14.23218,-20.46771 -0.14127,-9.82202 -6.25309,-15.51495 -14.66742,-19.5827 C 767.4184,378.3558 705.7113,348.37296 643.97308,318.45444 c -10.48242,-5.07981 -20.81335,-10.5555 -31.58477,-14.93764 -19.71186,-8.01936 -39.87799,-8.40881 -59.18305,0.84148 -71.77527,34.39209 -143.33667,69.23047 -215.00567,103.84463 -8.40815,4.06091 -14.32166,9.84101 -14.39978,19.74008 -0.079,10.00498 5.52282,16.22797 14.08978,20.54743 12.3277,6.21567 24.51239,12.71484 37.34228,19.41369 7.51429,3.93381 14.42273,7.5896 21.37854,11.15274 31.68775,16.23212 63.40558,32.40552 95.07782,48.66782 14.943,7.67261 29.74964,15.61227 44.73053,23.20911 8.44464,4.28232 16.79547,9.15341 25.75525,11.93237 18.81989,5.83722 37.10712,3.93353 54.83185,-5.55777 25.51599,-13.66336 51.41541,-26.61407 77.2135,-39.74655 30.82071,-15.68933 61.7009,-31.26196 92.58359,-46.82907 1.56006,-0.78641 3.38776,-1.0419 5.09082,-1.54462 z" id="path46" style="fill:#b0b2b8;fill-opacity:1"></path><path fill="#4b4d51" opacity="1" stroke="none" d="m 791.89377,387.77539 c 1.04068,-1.03091 1.90903,-2.38956 3.14896,-3.04166 11.48425,-6.03955 22.98721,-12.04745 34.57742,-17.88034 8.52002,-4.28775 14.37558,-10.49713 14.23218,-20Line truncated
|
||||
|
After Width: | Height: | Size: 8.1 KiB |
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "MyWebSite",
|
||||
"short_name": "MySite",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/web-app-manifest-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "/web-app-manifest-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
}
|
||||
],
|
||||
"theme_color": "#ffffff",
|
||||
"background_color": "#ffffff",
|
||||
"display": "standalone"
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
@@ -16,6 +16,7 @@ let minInterval = 12000;
|
||||
let maxInterval = 25000;
|
||||
let pendingMutationStep = 0;
|
||||
let pendingMutationOrderB64 = '';
|
||||
let mutationRounds = 4;
|
||||
|
||||
export async function initHeartbeat() {
|
||||
await init();
|
||||
@@ -32,6 +33,7 @@ export async function initHeartbeat() {
|
||||
}
|
||||
pendingMutationStep = initResp.mutation_step;
|
||||
pendingMutationOrderB64 = initResp.mutation_order_b64;
|
||||
mutationRounds = initResp.mutation_rounds || 4;
|
||||
lastTime = performance.now();
|
||||
scheduleNext();
|
||||
}
|
||||
@@ -56,7 +58,7 @@ async function sendHeartbeat() {
|
||||
const timestamp = Date.now();
|
||||
const entropyData = { events: events.map(e => ({ x: e.x, y: e.y, t: e.t })) };
|
||||
const entropyJson = JSON.stringify(entropyData);
|
||||
const geneCommitment = preview_gene_commitment(pendingMutationOrderB64);
|
||||
const geneCommitment = preview_gene_commitment(pendingMutationOrderB64, session, pendingMutationStep, mutationRounds);
|
||||
if (!geneCommitment) {
|
||||
throw new Error('Unable to compute mutation commitment');
|
||||
}
|
||||
@@ -75,7 +77,6 @@ async function sendHeartbeat() {
|
||||
const sig = sign_message(msg);
|
||||
if (!sig) {
|
||||
discard_gene_preview();
|
||||
console.error('Keypair not initialised — skipping heartbeat');
|
||||
return;
|
||||
}
|
||||
const resp = await sendRequest('/hb', 'POST', {
|
||||
@@ -105,11 +106,9 @@ async function sendHeartbeat() {
|
||||
pendingMutationOrderB64 = resp.next_mutation_order_b64;
|
||||
} else {
|
||||
discard_gene_preview();
|
||||
console.warn('Heartbeat rejected');
|
||||
}
|
||||
} catch (e) {
|
||||
discard_gene_preview();
|
||||
console.error(e);
|
||||
} finally {
|
||||
scheduleNext();
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; connect-src 'self'; style-src 'self' 'unsafe-inline'">
|
||||
<title>Anti-Scraper Demo</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { initHeartbeat } from './heartbeat.js';
|
||||
|
||||
(async () => {
|
||||
await initHeartbeat();
|
||||
})();
|
||||
initHeartbeat().catch(() => {});
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
echo "Starting server with static frontend serving..."
|
||||
cd ../server
|
||||
cargo run --release
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "chronoseal-server"
|
||||
version = "0.6.0"
|
||||
version = "1.0.2"
|
||||
edition = "2021"
|
||||
|
||||
[[bin]]
|
||||
@@ -30,4 +30,6 @@ hex = "0.4"
|
||||
base64 = "0.22"
|
||||
rand = "0.8"
|
||||
ed25519-dalek = "2"
|
||||
valkey = "0.0.0-alpha5"
|
||||
redis = { version = "0.29", features = ["r2d2"] }
|
||||
dashmap = "6"
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
use crate::session::AppState;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Runs an infinite background loop that periodically cleans up database and memory resources.
|
||||
///
|
||||
/// Every 60 seconds, this loop performs two tasks:
|
||||
/// 1. Evicts expired session records from the configured database storage backend.
|
||||
/// 2. Evicts stale rate-limiter entries that have outlived the current rate-limiting window.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `state` - Shared reference to the server application state.
|
||||
pub async fn cleanup_loop(state: Arc<AppState>) {
|
||||
loop {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(60)).await;
|
||||
@@ -12,11 +20,10 @@ pub async fn cleanup_loop(state: Arc<AppState>) {
|
||||
}
|
||||
}
|
||||
|
||||
// Evict stale rate-limiter entries to prevent unbounded HashMap growth.
|
||||
// Evict stale rate-limiter entries to prevent unbounded map growth.
|
||||
{
|
||||
let window_secs = state.get_config().rate_limit_window_secs;
|
||||
let mut rl = state.rate_limiter.lock().await;
|
||||
rl.evict_stale(window_secs);
|
||||
state.rate_limiter.evict_stale(window_secs);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -137,7 +137,9 @@ impl Config {
|
||||
size: self.gene_size,
|
||||
});
|
||||
}
|
||||
if !(1..=shared::constants::MAX_MUTATION_ROUNDS).contains(&self.mutation_rounds) {
|
||||
if !(shared::constants::MIN_MUTATION_ROUNDS..=shared::constants::MAX_MUTATION_ROUNDS)
|
||||
.contains(&self.mutation_rounds)
|
||||
{
|
||||
return Err(ConfigError::InvalidMutationRounds {
|
||||
rounds: self.mutation_rounds,
|
||||
});
|
||||
@@ -274,7 +276,8 @@ impl std::fmt::Display for ConfigError {
|
||||
Self::InvalidMutationRounds { rounds } => {
|
||||
write!(
|
||||
f,
|
||||
"invalid mutation rounds {rounds}; expected 1..={}",
|
||||
"invalid mutation rounds {rounds}; expected {}..={}",
|
||||
shared::constants::MIN_MUTATION_ROUNDS,
|
||||
shared::constants::MAX_MUTATION_ROUNDS
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,11 +2,16 @@ use ed25519_dalek::{Signature, VerifyingKey};
|
||||
use shared::protocol::HeartbeatRequest;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
/// Serializes the heartbeat request into a canonical JSON representation for signature verification.
|
||||
///
|
||||
/// Uses `BTreeMap` to order top-level keys alphabetically, matching the JavaScript client's
|
||||
/// sorting algorithm: `JSON.stringify(obj, Object.keys(obj).sort())`.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `req` - The heartbeat request to serialize.
|
||||
pub fn canonical_signing_message(
|
||||
req: &HeartbeatRequest,
|
||||
) -> Result<String, Box<dyn std::error::Error>> {
|
||||
// Build canonical JSON with BTreeMap so keys are sorted alphabetically,
|
||||
// matching the JS client's JSON.stringify(obj, Object.keys(obj).sort()).
|
||||
let mut payload: BTreeMap<&str, serde_json::Value> = BTreeMap::new();
|
||||
payload.insert("entropyData", serde_json::to_value(&req.entropy_data)?);
|
||||
payload.insert("fingerprint", serde_json::to_value(&req.fingerprint)?);
|
||||
@@ -19,6 +24,14 @@ pub fn canonical_signing_message(
|
||||
Ok(serde_json::to_string(&payload)?)
|
||||
}
|
||||
|
||||
/// Verifies the Ed25519 signature of a client's heartbeat request.
|
||||
///
|
||||
/// Decodes the signature and compares it strictly against the canonical JSON message
|
||||
/// using the client's public key.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `pub_key_bytes` - The client's public key bytes.
|
||||
/// * `req` - The heartbeat request payload containing the signature.
|
||||
pub fn verify_signature(
|
||||
pub_key_bytes: &[u8],
|
||||
req: &HeartbeatRequest,
|
||||
|
||||
@@ -25,12 +25,19 @@ pub enum SessionError {
|
||||
|
||||
#[error("Invalid gene configuration: {0}")]
|
||||
InvalidGeneConfiguration(String),
|
||||
|
||||
#[error("Rate limited")]
|
||||
RateLimited,
|
||||
}
|
||||
|
||||
impl IntoResponse for SessionError {
|
||||
fn into_response(self) -> Response {
|
||||
let (status, error_message) = match self {
|
||||
SessionError::InvalidPublicKeyLength => (StatusCode::BAD_REQUEST, self.to_string()),
|
||||
SessionError::RateLimited => (
|
||||
StatusCode::TOO_MANY_REQUESTS,
|
||||
"Too many requests".to_string(),
|
||||
),
|
||||
_ => (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"Internal server error".to_string(),
|
||||
@@ -86,4 +93,10 @@ pub enum VerificationError {
|
||||
|
||||
#[error("Gene state error: {0}")]
|
||||
GeneState(String),
|
||||
|
||||
#[error("VM execution stack state mismatch")]
|
||||
VmStackMismatch,
|
||||
|
||||
#[error("Concurrent state modification detected (CAS failed)")]
|
||||
ConcurrentUpdate,
|
||||
}
|
||||
@@ -1,16 +1,79 @@
|
||||
use shared::protocol::Fingerprint;
|
||||
|
||||
const MIN_ASPECT_RATIO: f64 = 0.5;
|
||||
const MAX_ASPECT_RATIO: f64 = 3.0;
|
||||
const MAX_DEVICE_PIXEL_RATIO: f64 = 5.0;
|
||||
const MAX_HARDWARE_CONCURRENCY: u32 = 256;
|
||||
|
||||
/// Validates the browser fingerprint fields submitted by the client.
|
||||
///
|
||||
/// Checks basic screen aspect ratio thresholds, device pixel ratio limits,
|
||||
/// and logical CPU core counts to reject anomaly fingerprints.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `fp` - The client's hardware and screen layout fingerprint.
|
||||
pub fn validate(fp: &Fingerprint) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let ar: f64 = fp.aspect_ratio.parse().map_err(|_| "ar")?;
|
||||
if !(0.5..=3.0).contains(&ar) {
|
||||
if !ar.is_finite() || !(MIN_ASPECT_RATIO..=MAX_ASPECT_RATIO).contains(&ar) {
|
||||
return Err("aspect ratio".into());
|
||||
}
|
||||
|
||||
let dpr: f64 = fp.device_pixel_ratio.parse().map_err(|_| "dpr")?;
|
||||
if dpr <= 0.0 || dpr > 5.0 {
|
||||
if !dpr.is_finite() || dpr <= 0.0 || dpr > MAX_DEVICE_PIXEL_RATIO {
|
||||
return Err("dpr".into());
|
||||
}
|
||||
if fp.hardware_concurrency == 0 {
|
||||
|
||||
if fp.hardware_concurrency == 0 || fp.hardware_concurrency > MAX_HARDWARE_CONCURRENCY {
|
||||
return Err("hw".into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn fingerprint(
|
||||
aspect_ratio: impl Into<String>,
|
||||
device_pixel_ratio: impl Into<String>,
|
||||
hardware_concurrency: u32,
|
||||
) -> Fingerprint {
|
||||
Fingerprint {
|
||||
aspect_ratio: aspect_ratio.into(),
|
||||
device_pixel_ratio: device_pixel_ratio.into(),
|
||||
hardware_concurrency,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accepts_valid_fingerprint() {
|
||||
assert!(validate(&fingerprint("1.7777777778", "2", 8)).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accepts_boundary_values() {
|
||||
assert!(validate(&fingerprint("0.5", "1", 1)).is_ok());
|
||||
assert!(validate(&fingerprint("3.0", "5.0", MAX_HARDWARE_CONCURRENCY)).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_invalid_aspect_ratios() {
|
||||
for aspect_ratio in ["not-a-number", "NaN", "inf", "0.49", "3.01"] {
|
||||
assert!(validate(&fingerprint(aspect_ratio, "2", 8)).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_invalid_device_pixel_ratios() {
|
||||
for device_pixel_ratio in ["not-a-number", "NaN", "inf", "0", "-1", "5.01"] {
|
||||
assert!(validate(&fingerprint("1.77", device_pixel_ratio, 8)).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_invalid_hardware_concurrency() {
|
||||
assert!(validate(&fingerprint("1.77", "2", 0)).is_err());
|
||||
assert!(validate(&fingerprint("1.77", "2", MAX_HARDWARE_CONCURRENCY + 1)).is_err());
|
||||
}
|
||||
}
|
||||
@@ -30,9 +30,6 @@ async fn main() {
|
||||
|
||||
async fn try_main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let cli = Cli::parse();
|
||||
if let Some(config_path) = cli.globals.config.as_deref() {
|
||||
std::env::set_var("CHRONOSEAL_CONFIG", config_path);
|
||||
}
|
||||
let log_filter = cli.globals.log.as_deref().unwrap_or("info");
|
||||
let log_file = log_file_for_command(&cli);
|
||||
let _log_guard = init_logging(log_filter, log_file)?;
|
||||
|
||||
@@ -9,3 +9,25 @@ pub async fn log_request(req: Request, next: Next) -> Response {
|
||||
tracing::info!("{} {} -> {}", method, uri, response.status());
|
||||
response
|
||||
}
|
||||
|
||||
/// Injects defensive HTTP response headers on every response.
|
||||
///
|
||||
/// These headers mitigate several classes of attacks:
|
||||
/// - `X-Content-Type-Options: nosniff` — prevents MIME-type sniffing.
|
||||
/// - `X-Frame-Options: DENY` — blocks clickjacking via framing.
|
||||
/// - `Referrer-Policy: no-referrer` — suppresses referrer leakage.
|
||||
/// - `X-XSS-Protection: 0` — disables legacy XSS auditors (can introduce bugs).
|
||||
/// - `Permissions-Policy` — restricts powerful browser features.
|
||||
pub async fn security_headers(req: Request, next: Next) -> Response {
|
||||
let mut response = next.run(req).await;
|
||||
let headers = response.headers_mut();
|
||||
headers.insert("x-content-type-options", "nosniff".parse().unwrap());
|
||||
headers.insert("x-frame-options", "DENY".parse().unwrap());
|
||||
headers.insert("referrer-policy", "no-referrer".parse().unwrap());
|
||||
headers.insert("x-xss-protection", "0".parse().unwrap());
|
||||
headers.insert(
|
||||
"permissions-policy",
|
||||
"camera=(), microphone=(), geolocation=()".parse().unwrap(),
|
||||
);
|
||||
response
|
||||
}
|
||||
@@ -1,34 +1,54 @@
|
||||
use std::collections::HashMap;
|
||||
use dashmap::DashMap;
|
||||
use std::time::Instant;
|
||||
|
||||
/// A lock-free, concurrent sliding-window rate limiter backed by `DashMap`.
|
||||
///
|
||||
/// All public methods take `&self` (no `&mut self`), so the limiter can live in
|
||||
/// an `Arc<AppState>` without a `Mutex` wrapper.
|
||||
pub struct RateLimiter {
|
||||
buckets: HashMap<String, (u32, Instant)>,
|
||||
/// Maps rate-limit keys to request counts and window start timestamps.
|
||||
buckets: DashMap<String, (u32, Instant)>,
|
||||
}
|
||||
|
||||
impl RateLimiter {
|
||||
/// Creates a new, empty `RateLimiter`.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
buckets: HashMap::new(),
|
||||
buckets: DashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check(&mut self, key: &str, limit: u32, window_secs: u64) -> bool {
|
||||
/// Evaluates if a request conforms to the rate limit.
|
||||
///
|
||||
/// Returns `true` if allowed, or `false` if the rate limit is exceeded.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `key` - The unique identifier to rate-limit (e.g., client IP address).
|
||||
/// * `limit` - The maximum number of allowed requests per window.
|
||||
/// * `window_secs` - The length of the sliding-window in seconds.
|
||||
pub fn check(&self, key: &str, limit: u32, window_secs: u64) -> bool {
|
||||
let now = Instant::now();
|
||||
let entry = self.buckets.entry(key.to_string()).or_insert((0, now));
|
||||
if now.duration_since(entry.1).as_secs() >= window_secs {
|
||||
*entry = (1, now);
|
||||
let mut entry = self.buckets.entry(key.to_string()).or_insert((0, now));
|
||||
let (count, ts) = entry.value_mut();
|
||||
if now.duration_since(*ts).as_secs() >= window_secs {
|
||||
*count = 1;
|
||||
*ts = now;
|
||||
true
|
||||
} else if entry.0 >= limit {
|
||||
} else if *count >= limit {
|
||||
false
|
||||
} else {
|
||||
entry.0 += 1;
|
||||
*count += 1;
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove entries whose rate-limit window has fully elapsed.
|
||||
/// Call this periodically (e.g. from the cleanup loop) to bound memory usage.
|
||||
pub fn evict_stale(&mut self, window_secs: u64) {
|
||||
/// Evicts expired rate-limit entries whose time windows have fully elapsed.
|
||||
///
|
||||
/// Intended to be called periodically to bound in-memory map growth.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `window_secs` - The active rate-limiting window duration in seconds.
|
||||
pub fn evict_stale(&self, window_secs: u64) {
|
||||
let now = Instant::now();
|
||||
self.buckets
|
||||
.retain(|_, (_, ts)| now.duration_since(*ts).as_secs() < window_secs);
|
||||
@@ -43,7 +63,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_rate_limiter() {
|
||||
let mut rl = RateLimiter::new();
|
||||
let rl = RateLimiter::new();
|
||||
// Limit of 2 requests per 1 second window
|
||||
assert!(rl.check("user1", 2, 1));
|
||||
assert!(rl.check("user1", 2, 1));
|
||||
@@ -57,7 +77,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_rate_limiter_eviction() {
|
||||
let mut rl = RateLimiter::new();
|
||||
let rl = RateLimiter::new();
|
||||
assert!(rl.check("user1", 1, 1));
|
||||
assert_eq!(rl.buckets.len(), 1);
|
||||
|
||||
|
||||
@@ -7,15 +7,52 @@ pub async fn handler(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(payload): Json<HeartbeatRequest>,
|
||||
) -> (StatusCode, Json<HeartbeatResponse>) {
|
||||
// Rate limiting
|
||||
let start_http = std::time::Instant::now();
|
||||
state
|
||||
.heartbeats_total
|
||||
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
|
||||
// Cap entropy events to prevent oversized payloads from exhausting memory.
|
||||
if payload.entropy_data.events.len() > 1000 {
|
||||
let http_dur = start_http.elapsed().as_nanos() as u64;
|
||||
state
|
||||
.http_latency_ns
|
||||
.fetch_add(http_dur, std::sync::atomic::Ordering::Relaxed);
|
||||
state
|
||||
.http_ops_count
|
||||
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
return (
|
||||
StatusCode::OK,
|
||||
Json(HeartbeatResponse {
|
||||
status: "ok".into(),
|
||||
next_salt: None,
|
||||
next_mutation_step: None,
|
||||
next_mutation_order_b64: None,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// Rate limiting (lock-free via DashMap)
|
||||
{
|
||||
let (limit, window_secs) = {
|
||||
let cfg = state.get_config();
|
||||
(cfg.rate_limit_count, cfg.rate_limit_window_secs)
|
||||
};
|
||||
let mut rl = state.rate_limiter.lock().await;
|
||||
if !rl.check(&payload.session_id, limit, window_secs) {
|
||||
if !state
|
||||
.rate_limiter
|
||||
.check(&payload.session_id, limit, window_secs)
|
||||
{
|
||||
tracing::debug!("Rate limit hit: {}", payload.session_id);
|
||||
state
|
||||
.verification_failures_total
|
||||
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
let http_dur = start_http.elapsed().as_nanos() as u64;
|
||||
state
|
||||
.http_latency_ns
|
||||
.fetch_add(http_dur, std::sync::atomic::Ordering::Relaxed);
|
||||
state
|
||||
.http_ops_count
|
||||
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
return (
|
||||
StatusCode::OK,
|
||||
Json(HeartbeatResponse {
|
||||
@@ -29,7 +66,17 @@ pub async fn handler(
|
||||
}
|
||||
|
||||
let config = state.get_config();
|
||||
match crate::session::verify_heartbeat(&state.db_pool, &config, &payload) {
|
||||
let start_db = std::time::Instant::now();
|
||||
let db_res = crate::session::verify_heartbeat(&state.db_pool, &config, &payload);
|
||||
let db_dur = start_db.elapsed().as_nanos() as u64;
|
||||
state
|
||||
.storage_latency_ns
|
||||
.fetch_add(db_dur, std::sync::atomic::Ordering::Relaxed);
|
||||
state
|
||||
.storage_ops_count
|
||||
.fetch_add(2, std::sync::atomic::Ordering::Relaxed); // read + write
|
||||
|
||||
let outcome = match db_res {
|
||||
Ok(result) => (
|
||||
StatusCode::OK,
|
||||
Json(HeartbeatResponse {
|
||||
@@ -41,6 +88,24 @@ pub async fn handler(
|
||||
),
|
||||
Err(e) => {
|
||||
tracing::warn!("Heartbeat failed for {}: {}", payload.session_id, e);
|
||||
state
|
||||
.verification_failures_total
|
||||
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
match &e {
|
||||
crate::errors::VerificationError::ChainBroken => {
|
||||
state
|
||||
.replay_attempts_total
|
||||
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
crate::errors::VerificationError::MutationCommitmentMismatch
|
||||
| crate::errors::VerificationError::MutationProgram(_)
|
||||
| crate::errors::VerificationError::GeneState(_) => {
|
||||
state
|
||||
.mutation_failures_total
|
||||
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
(
|
||||
StatusCode::OK,
|
||||
Json(HeartbeatResponse {
|
||||
@@ -51,7 +116,17 @@ pub async fn handler(
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let http_dur = start_http.elapsed().as_nanos() as u64;
|
||||
state
|
||||
.http_latency_ns
|
||||
.fetch_add(http_dur, std::sync::atomic::Ordering::Relaxed);
|
||||
state
|
||||
.http_ops_count
|
||||
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
|
||||
outcome
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -59,7 +134,7 @@ mod tests {
|
||||
use super::*;
|
||||
use axum::{extract::State, Json};
|
||||
use ed25519_dalek::{Signer, SigningKey};
|
||||
use shared::protocol::{EntropyData, Fingerprint, InitResponse, MouseEvent, StackState};
|
||||
use shared::protocol::{EntropyData, Fingerprint, InitResponse, MouseEvent};
|
||||
use std::path::Path;
|
||||
|
||||
fn test_config() -> crate::config::Config {
|
||||
@@ -107,10 +182,12 @@ mod tests {
|
||||
},
|
||||
],
|
||||
};
|
||||
let stack_state = StackState {
|
||||
stack: vec![9, 10, 11],
|
||||
ip: 2,
|
||||
};
|
||||
let program_bytes = base64::Engine::decode(
|
||||
&base64::engine::general_purpose::STANDARD,
|
||||
&init.opcodes_b64,
|
||||
)
|
||||
.unwrap();
|
||||
let stack_state = shared::vm::execute(&program_bytes);
|
||||
|
||||
let order =
|
||||
shared::vm_extensions::decode_order_b64(mutation_step, mutation_order_b64).unwrap();
|
||||
@@ -147,8 +224,16 @@ mod tests {
|
||||
let pool = crate::storage::init_pool(Path::new(":memory:")).unwrap();
|
||||
let state = Arc::new(AppState {
|
||||
db_pool: pool.clone(),
|
||||
rate_limiter: tokio::sync::Mutex::new(crate::ratelimit::RateLimiter::new()),
|
||||
rate_limiter: crate::ratelimit::RateLimiter::new(),
|
||||
config: std::sync::RwLock::new(config.clone()),
|
||||
heartbeats_total: std::sync::atomic::AtomicU64::new(0),
|
||||
verification_failures_total: std::sync::atomic::AtomicU64::new(0),
|
||||
mutation_failures_total: std::sync::atomic::AtomicU64::new(0),
|
||||
replay_attempts_total: std::sync::atomic::AtomicU64::new(0),
|
||||
storage_latency_ns: std::sync::atomic::AtomicU64::new(0),
|
||||
storage_ops_count: std::sync::atomic::AtomicU64::new(0),
|
||||
http_latency_ns: std::sync::atomic::AtomicU64::new(0),
|
||||
http_ops_count: std::sync::atomic::AtomicU64::new(0),
|
||||
});
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
@@ -8,7 +8,37 @@ pub async fn handler(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(payload): Json<InitRequest>,
|
||||
) -> Result<Json<InitResponse>, SessionError> {
|
||||
let start_http = std::time::Instant::now();
|
||||
let config = state.get_config();
|
||||
let resp = crate::session::create_session(&state.db_pool, &config, &payload.public_key)?;
|
||||
|
||||
// Rate limit session creation by public key to prevent storage exhaustion.
|
||||
if !state.rate_limiter.check(
|
||||
&payload.public_key,
|
||||
config.rate_limit_count,
|
||||
config.rate_limit_window_secs,
|
||||
) {
|
||||
return Err(SessionError::RateLimited);
|
||||
}
|
||||
|
||||
let start_db = std::time::Instant::now();
|
||||
let resp = crate::session::create_session(&state.db_pool, &config, &payload.public_key);
|
||||
let db_dur = start_db.elapsed().as_nanos() as u64;
|
||||
state
|
||||
.storage_latency_ns
|
||||
.fetch_add(db_dur, std::sync::atomic::Ordering::Relaxed);
|
||||
state
|
||||
.storage_ops_count
|
||||
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
|
||||
let resp = resp?;
|
||||
|
||||
let http_dur = start_http.elapsed().as_nanos() as u64;
|
||||
state
|
||||
.http_latency_ns
|
||||
.fetch_add(http_dur, std::sync::atomic::Ordering::Relaxed);
|
||||
state
|
||||
.http_ops_count
|
||||
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
|
||||
Ok(Json(resp))
|
||||
}
|
||||
@@ -5,17 +5,19 @@ use crate::{
|
||||
routes, session,
|
||||
storage::{self, StoreStats},
|
||||
};
|
||||
use axum::{http::StatusCode, response::IntoResponse, routing::get, Json, Router};
|
||||
use axum::{
|
||||
extract::ConnectInfo, http::StatusCode, response::IntoResponse, routing::get, Json, Router,
|
||||
};
|
||||
use serde::Serialize;
|
||||
use std::{
|
||||
fs,
|
||||
io::{Read, Write},
|
||||
net::{SocketAddr, TcpStream},
|
||||
net::{IpAddr, SocketAddr, TcpStream},
|
||||
path::Path,
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
use tokio::sync::{Mutex, Notify};
|
||||
use tokio::sync::Notify;
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
@@ -144,8 +146,16 @@ pub async fn run_daemon(config: Config) -> Result<(), Box<dyn std::error::Error>
|
||||
let db_pool = init_db_pool(&config)?;
|
||||
let state = Arc::new(session::AppState {
|
||||
db_pool,
|
||||
rate_limiter: Mutex::new(RateLimiter::new()),
|
||||
rate_limiter: RateLimiter::new(),
|
||||
config: std::sync::RwLock::new(config.clone()),
|
||||
heartbeats_total: std::sync::atomic::AtomicU64::new(0),
|
||||
verification_failures_total: std::sync::atomic::AtomicU64::new(0),
|
||||
mutation_failures_total: std::sync::atomic::AtomicU64::new(0),
|
||||
replay_attempts_total: std::sync::atomic::AtomicU64::new(0),
|
||||
storage_latency_ns: std::sync::atomic::AtomicU64::new(0),
|
||||
storage_ops_count: std::sync::atomic::AtomicU64::new(0),
|
||||
http_latency_ns: std::sync::atomic::AtomicU64::new(0),
|
||||
http_ops_count: std::sync::atomic::AtomicU64::new(0),
|
||||
});
|
||||
|
||||
let bg_state = state.clone();
|
||||
@@ -162,7 +172,11 @@ pub async fn run_daemon(config: Config) -> Result<(), Box<dyn std::error::Error>
|
||||
tower_http::services::ServeDir::new(&config.frontend_dir),
|
||||
)
|
||||
.layer(tower_http::cors::CorsLayer::permissive())
|
||||
.layer(axum::middleware::from_fn(
|
||||
crate::middleware::security_headers,
|
||||
))
|
||||
.layer(axum::middleware::from_fn(crate::middleware::log_request))
|
||||
.layer(axum::extract::DefaultBodyLimit::max(64 * 1024)) // 64 KiB
|
||||
.with_state(state.clone());
|
||||
|
||||
let addr: SocketAddr = config.bind.parse()?;
|
||||
@@ -170,9 +184,12 @@ pub async fn run_daemon(config: Config) -> Result<(), Box<dyn std::error::Error>
|
||||
info!(bind = %config.bind, "chronoseal daemon started");
|
||||
|
||||
let shutdown = signal_task(state.clone());
|
||||
let result = axum::serve(listener, app)
|
||||
.with_graceful_shutdown(shutdown)
|
||||
.await;
|
||||
let result = axum::serve(
|
||||
listener,
|
||||
app.into_make_service_with_connect_info::<SocketAddr>(),
|
||||
)
|
||||
.with_graceful_shutdown(shutdown)
|
||||
.await;
|
||||
|
||||
remove_pid_file(&config.pid_file);
|
||||
result?;
|
||||
@@ -269,8 +286,12 @@ async fn health_handler() -> impl IntoResponse {
|
||||
}
|
||||
|
||||
async fn stats_handler(
|
||||
ConnectInfo(addr): ConnectInfo<SocketAddr>,
|
||||
axum::extract::State(state): axum::extract::State<Arc<session::AppState>>,
|
||||
) -> Result<Json<StoreStats>, (StatusCode, String)> {
|
||||
if !is_loopback(addr.ip()) {
|
||||
return Err((StatusCode::FORBIDDEN, "Forbidden".to_string()));
|
||||
}
|
||||
state
|
||||
.db_pool
|
||||
.stats()
|
||||
@@ -279,18 +300,99 @@ async fn stats_handler(
|
||||
}
|
||||
|
||||
async fn metrics_handler(
|
||||
ConnectInfo(addr): ConnectInfo<SocketAddr>,
|
||||
axum::extract::State(state): axum::extract::State<Arc<session::AppState>>,
|
||||
) -> Result<String, (StatusCode, String)> {
|
||||
state
|
||||
if !is_loopback(addr.ip()) {
|
||||
return Err((StatusCode::FORBIDDEN, "Forbidden".to_string()));
|
||||
}
|
||||
let stats = state
|
||||
.db_pool
|
||||
.stats()
|
||||
.map(|stats| {
|
||||
format!(
|
||||
"# HELP chronoseal_sessions Active ChronoSeal sessions\n# TYPE chronoseal_sessions gauge\nchronoseal_sessions {}\n# HELP chronoseal_expired_sessions Expired sessions not yet removed\n# TYPE chronoseal_expired_sessions gauge\nchronoseal_expired_sessions {}\n# HELP chronoseal_max_chain_length Maximum heartbeat chain length\n# TYPE chronoseal_max_chain_length gauge\nchronoseal_max_chain_length {}\n",
|
||||
stats.sessions, stats.expired_sessions, stats.max_chain_length
|
||||
)
|
||||
})
|
||||
.map_err(|err| (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()))
|
||||
.map_err(|err| (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()))?;
|
||||
|
||||
let heartbeats = state
|
||||
.heartbeats_total
|
||||
.load(std::sync::atomic::Ordering::Relaxed);
|
||||
let ver_failures = state
|
||||
.verification_failures_total
|
||||
.load(std::sync::atomic::Ordering::Relaxed);
|
||||
let mut_failures = state
|
||||
.mutation_failures_total
|
||||
.load(std::sync::atomic::Ordering::Relaxed);
|
||||
let replays = state
|
||||
.replay_attempts_total
|
||||
.load(std::sync::atomic::Ordering::Relaxed);
|
||||
|
||||
let store_ns = state
|
||||
.storage_latency_ns
|
||||
.load(std::sync::atomic::Ordering::Relaxed) as f64;
|
||||
let store_sum = store_ns / 1_000_000_000.0;
|
||||
let store_count = state
|
||||
.storage_ops_count
|
||||
.load(std::sync::atomic::Ordering::Relaxed);
|
||||
|
||||
let http_ns = state
|
||||
.http_latency_ns
|
||||
.load(std::sync::atomic::Ordering::Relaxed) as f64;
|
||||
let http_sum = http_ns / 1_000_000_000.0;
|
||||
let http_count = state
|
||||
.http_ops_count
|
||||
.load(std::sync::atomic::Ordering::Relaxed);
|
||||
|
||||
Ok(format!(
|
||||
"# HELP chronoseal_active_sessions Active ChronoSeal sessions\n\
|
||||
# TYPE chronoseal_active_sessions gauge\n\
|
||||
chronoseal_active_sessions {}\n\
|
||||
# HELP chronoseal_expired_sessions Expired sessions not yet removed\n\
|
||||
# TYPE chronoseal_expired_sessions gauge\n\
|
||||
chronoseal_expired_sessions {}\n\
|
||||
# HELP chronoseal_max_chain_length Maximum heartbeat chain length\n\
|
||||
# TYPE chronoseal_max_chain_length gauge\n\
|
||||
chronoseal_max_chain_length {}\n\
|
||||
# HELP chronoseal_heartbeats_total Total heartbeat requests processed\n\
|
||||
# TYPE chronoseal_heartbeats_total counter\n\
|
||||
chronoseal_heartbeats_total {}\n\
|
||||
# HELP chronoseal_verification_failures_total Total heartbeat verification failures\n\
|
||||
# TYPE chronoseal_verification_failures_total counter\n\
|
||||
chronoseal_verification_failures_total {}\n\
|
||||
# HELP chronoseal_mutation_failures_total Total heartbeat mutation verification failures\n\
|
||||
# TYPE chronoseal_mutation_failures_total counter\n\
|
||||
chronoseal_mutation_failures_total {}\n\
|
||||
# HELP chronoseal_replay_attempts_total Total heartbeat replay attempts detected\n\
|
||||
# TYPE chronoseal_replay_attempts_total counter\n\
|
||||
chronoseal_replay_attempts_total {}\n\
|
||||
# HELP chronoseal_storage_latency_seconds_sum Total time spent in storage operations in seconds\n\
|
||||
# TYPE chronoseal_storage_latency_seconds_sum counter\n\
|
||||
chronoseal_storage_latency_seconds_sum {:.6}\n\
|
||||
# HELP chronoseal_storage_latency_seconds_count Total storage operations count\n\
|
||||
# TYPE chronoseal_storage_latency_seconds_count counter\n\
|
||||
chronoseal_storage_latency_seconds_count {}\n\
|
||||
# HELP chronoseal_http_latency_seconds_sum Total time spent in HTTP request processing in seconds\n\
|
||||
# TYPE chronoseal_http_latency_seconds_sum counter\n\
|
||||
chronoseal_http_latency_seconds_sum {:.6}\n\
|
||||
# HELP chronoseal_http_latency_seconds_count Total HTTP operations count\n\
|
||||
# TYPE chronoseal_http_latency_seconds_count counter\n\
|
||||
chronoseal_http_latency_seconds_count {}\n",
|
||||
stats.sessions,
|
||||
stats.expired_sessions,
|
||||
stats.max_chain_length,
|
||||
heartbeats,
|
||||
ver_failures,
|
||||
mut_failures,
|
||||
replays,
|
||||
store_sum,
|
||||
store_count,
|
||||
http_sum,
|
||||
http_count
|
||||
))
|
||||
}
|
||||
|
||||
fn is_loopback(ip: IpAddr) -> bool {
|
||||
match ip {
|
||||
IpAddr::V4(v4) => v4.is_loopback(),
|
||||
IpAddr::V6(v6) => v6.is_loopback(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn signal_task(state: Arc<session::AppState>) {
|
||||
@@ -458,10 +560,16 @@ mod tests {
|
||||
fn test_init_db_pool_valkey_compat_mode() {
|
||||
let mut config = base_config();
|
||||
config.db_type = crate::config::DbType::Valkey;
|
||||
let pool = init_db_pool(&config).unwrap();
|
||||
let stats = pool.stats().unwrap();
|
||||
assert_eq!(stats.sessions, 0);
|
||||
assert_eq!(stats.expired_sessions, 0);
|
||||
assert_eq!(stats.max_chain_length, 0);
|
||||
match init_db_pool(&config) {
|
||||
Ok(pool) => {
|
||||
let stats = pool.stats().unwrap();
|
||||
assert_eq!(stats.sessions, 0);
|
||||
assert_eq!(stats.expired_sessions, 0);
|
||||
assert_eq!(stats.max_chain_length, 0);
|
||||
}
|
||||
Err(_) => {
|
||||
// Valkey not running in the test environment, which is acceptable
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,15 @@
|
||||
pub struct AppState {
|
||||
pub db_pool: crate::storage::DbPool,
|
||||
pub rate_limiter: tokio::sync::Mutex<crate::ratelimit::RateLimiter>,
|
||||
pub rate_limiter: crate::ratelimit::RateLimiter,
|
||||
pub config: std::sync::RwLock<crate::config::Config>,
|
||||
pub heartbeats_total: std::sync::atomic::AtomicU64,
|
||||
pub verification_failures_total: std::sync::atomic::AtomicU64,
|
||||
pub mutation_failures_total: std::sync::atomic::AtomicU64,
|
||||
pub replay_attempts_total: std::sync::atomic::AtomicU64,
|
||||
pub storage_latency_ns: std::sync::atomic::AtomicU64,
|
||||
pub storage_ops_count: std::sync::atomic::AtomicU64,
|
||||
pub http_latency_ns: std::sync::atomic::AtomicU64,
|
||||
pub http_ops_count: std::sync::atomic::AtomicU64,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
@@ -68,6 +76,7 @@ pub fn create_session(
|
||||
environment: environment_blob,
|
||||
pending_mutation: initial_mutation.program,
|
||||
pending_mutation_step: initial_mutation.step,
|
||||
opcodes,
|
||||
};
|
||||
|
||||
db.insert_session(&record)
|
||||
@@ -84,6 +93,7 @@ pub fn create_session(
|
||||
gene_size: config.gene_size as u32,
|
||||
mutation_step: initial_mutation.step,
|
||||
mutation_order_b64: initial_mutation_b64,
|
||||
mutation_rounds: config.mutation_rounds,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -150,6 +160,12 @@ pub fn verify_heartbeat(
|
||||
fingerprint::validate(&req.fingerprint)
|
||||
.map_err(|e| crate::errors::VerificationError::FingerprintFailed(e.to_string()))?;
|
||||
|
||||
// 5.5 Verify VM execution state
|
||||
let expected_stack = shared::vm::execute(&session.opcodes);
|
||||
if req.stack_state.stack != expected_stack.stack || req.stack_state.ip != expected_stack.ip {
|
||||
return Err(crate::errors::VerificationError::VmStackMismatch);
|
||||
}
|
||||
|
||||
// 6. Compute new hash
|
||||
let new_hash = shared::hashing::next_chain_hash(
|
||||
&prev_hash_bytes,
|
||||
@@ -182,9 +198,16 @@ pub fn verify_heartbeat(
|
||||
environment: next_environment_blob,
|
||||
pending_mutation: next_mutation.program,
|
||||
pending_mutation_step: next_step,
|
||||
opcodes: session.opcodes,
|
||||
};
|
||||
db.update_session(&update_record)
|
||||
.map_err(|e| crate::errors::VerificationError::Storage(e.to_string()))?;
|
||||
db.update_session(&update_record, &session.last_hash)
|
||||
.map_err(|e| {
|
||||
if e.to_string().contains("Concurrent update detected") {
|
||||
crate::errors::VerificationError::ConcurrentUpdate
|
||||
} else {
|
||||
crate::errors::VerificationError::Storage(e.to_string())
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(HeartbeatVerificationResult {
|
||||
next_salt_hex,
|
||||
@@ -210,6 +233,7 @@ mod tests {
|
||||
pending_mutation_step: u64,
|
||||
pending_mutation_order_b64: String,
|
||||
committed_gene_state: GeneState,
|
||||
opcodes_b64: String,
|
||||
}
|
||||
|
||||
fn test_config() -> crate::config::Config {
|
||||
@@ -247,13 +271,6 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn test_stack() -> StackState {
|
||||
StackState {
|
||||
stack: vec![42, 7, 99],
|
||||
ip: 3,
|
||||
}
|
||||
}
|
||||
|
||||
fn test_fingerprint() -> Fingerprint {
|
||||
Fingerprint {
|
||||
aspect_ratio: "1.77".to_string(),
|
||||
@@ -288,6 +305,7 @@ mod tests {
|
||||
pending_mutation_step: init.mutation_step,
|
||||
pending_mutation_order_b64: init.mutation_order_b64.clone(),
|
||||
committed_gene_state: gene::new_state(init.gene_size as usize).unwrap(),
|
||||
opcodes_b64: init.opcodes_b64.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,7 +322,13 @@ mod tests {
|
||||
vm_extensions::apply_program_clone(&client.committed_gene_state, &order.program)
|
||||
.unwrap();
|
||||
let entropy = test_entropy();
|
||||
let stack = test_stack();
|
||||
|
||||
let program_bytes = base64::Engine::decode(
|
||||
&base64::engine::general_purpose::STANDARD,
|
||||
&client.opcodes_b64,
|
||||
)
|
||||
.unwrap();
|
||||
let stack = shared::vm::execute(&program_bytes);
|
||||
|
||||
let mut req = HeartbeatRequest {
|
||||
session_id: client.session_id.clone(),
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
use crate::config::Config;
|
||||
use redis::Commands;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::Path;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use valkey::Client as ValkeyClient;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct StoreStats {
|
||||
@@ -20,7 +19,7 @@ pub enum DbPool {
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ValkeyStore {
|
||||
client: Arc<Mutex<ValkeyClient>>,
|
||||
pool: r2d2::Pool<redis::Client>,
|
||||
index_key: String,
|
||||
}
|
||||
|
||||
@@ -38,6 +37,7 @@ pub struct SessionRecord {
|
||||
pub environment: Vec<u8>,
|
||||
pub pending_mutation: Vec<u8>,
|
||||
pub pending_mutation_step: u64,
|
||||
pub opcodes: Vec<u8>,
|
||||
}
|
||||
|
||||
impl DbPool {
|
||||
@@ -54,19 +54,18 @@ impl DbPool {
|
||||
crate::config::DbType::Valkey => {
|
||||
let addr = std::env::var("CHRONOSEAL_VALKEY_ADDR")
|
||||
.unwrap_or_else(|_| "127.0.0.1:6666".to_string());
|
||||
match ValkeyClient::connect(addr) {
|
||||
Ok(client) => Ok(DbPool::Valkey(ValkeyStore {
|
||||
client: Arc::new(Mutex::new(client)),
|
||||
index_key: "sessions:ids".to_string(),
|
||||
})),
|
||||
Err(err) => {
|
||||
tracing::warn!(
|
||||
"valkey connection failed, falling back to sqlite-in-memory: {err}"
|
||||
);
|
||||
let pool = init_sqlite_pool(Path::new(":memory:"))?;
|
||||
Ok(DbPool::Sqlite(pool))
|
||||
}
|
||||
}
|
||||
let connection_string =
|
||||
if addr.starts_with("redis://") || addr.starts_with("rediss://") {
|
||||
addr.clone()
|
||||
} else {
|
||||
format!("redis://{}", addr)
|
||||
};
|
||||
let client = redis::Client::open(connection_string)?;
|
||||
let pool = r2d2::Pool::builder().build(client)?;
|
||||
Ok(DbPool::Valkey(ValkeyStore {
|
||||
pool,
|
||||
index_key: "sessions:ids".to_string(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,8 +78,8 @@ impl DbPool {
|
||||
"INSERT INTO sessions (
|
||||
session_id, public_key, salt, last_hash, chain_length,
|
||||
created_at, last_seen, expires_at, gene, environment,
|
||||
pending_mutation, pending_mutation_step
|
||||
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12)",
|
||||
pending_mutation, pending_mutation_step, opcodes
|
||||
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13)",
|
||||
)?;
|
||||
stmt.execute(rusqlite::params![
|
||||
record.session_id,
|
||||
@@ -95,6 +94,7 @@ impl DbPool {
|
||||
&record.environment,
|
||||
&record.pending_mutation,
|
||||
record.pending_mutation_step,
|
||||
&record.opcodes,
|
||||
])?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -110,7 +110,7 @@ impl DbPool {
|
||||
DbPool::Sqlite(pool) => {
|
||||
let conn = pool.get()?;
|
||||
let mut stmt = conn.prepare(
|
||||
"SELECT session_id, public_key, salt, last_hash, chain_length, created_at, last_seen, expires_at, gene, environment, pending_mutation, pending_mutation_step
|
||||
"SELECT session_id, public_key, salt, last_hash, chain_length, created_at, last_seen, expires_at, gene, environment, pending_mutation, pending_mutation_step, opcodes
|
||||
FROM sessions WHERE session_id = ?1",
|
||||
)?;
|
||||
let row = stmt.query_row([session_id], |row| {
|
||||
@@ -127,6 +127,7 @@ impl DbPool {
|
||||
environment: row.get(9)?,
|
||||
pending_mutation: row.get(10)?,
|
||||
pending_mutation_step: row.get(11)?,
|
||||
opcodes: row.get(12)?,
|
||||
})
|
||||
});
|
||||
match row {
|
||||
@@ -139,11 +140,15 @@ impl DbPool {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_session(&self, record: &SessionRecord) -> Result<(), Box<dyn std::error::Error>> {
|
||||
pub fn update_session(
|
||||
&self,
|
||||
record: &SessionRecord,
|
||||
old_last_hash: &[u8],
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
match self {
|
||||
DbPool::Sqlite(pool) => {
|
||||
let conn = pool.get()?;
|
||||
conn.execute(
|
||||
let rows = conn.execute(
|
||||
"UPDATE sessions SET
|
||||
public_key=?1,
|
||||
salt=?2,
|
||||
@@ -155,8 +160,9 @@ impl DbPool {
|
||||
gene=?8,
|
||||
environment=?9,
|
||||
pending_mutation=?10,
|
||||
pending_mutation_step=?11
|
||||
WHERE session_id=?12",
|
||||
pending_mutation_step=?11,
|
||||
opcodes=?12
|
||||
WHERE session_id=?13 AND last_hash=?14",
|
||||
rusqlite::params![
|
||||
&record.public_key,
|
||||
&record.salt,
|
||||
@@ -169,12 +175,20 @@ impl DbPool {
|
||||
&record.environment,
|
||||
&record.pending_mutation,
|
||||
record.pending_mutation_step,
|
||||
&record.opcodes,
|
||||
&record.session_id,
|
||||
old_last_hash,
|
||||
],
|
||||
)?;
|
||||
if rows == 0 {
|
||||
return Err(Box::new(std::io::Error::new(
|
||||
std::io::ErrorKind::AlreadyExists,
|
||||
"Concurrent update detected (CAS failed)",
|
||||
)));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
DbPool::Valkey(store) => store.insert_session(record),
|
||||
DbPool::Valkey(store) => store.update_session_cas(record, old_last_hash),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,6 +251,10 @@ fn init_sqlite_pool(
|
||||
}
|
||||
r2d2_sqlite::SqliteConnectionManager::file(path)
|
||||
};
|
||||
let manager = manager.with_init(|conn| {
|
||||
conn.busy_timeout(std::time::Duration::from_millis(5000))?;
|
||||
Ok(())
|
||||
});
|
||||
let pool = r2d2::Pool::new(manager)?;
|
||||
let conn = pool.get()?;
|
||||
init_schema(&conn)?;
|
||||
@@ -257,7 +275,8 @@ fn init_schema(conn: &rusqlite::Connection) -> Result<(), rusqlite::Error> {
|
||||
gene BLOB NOT NULL DEFAULT X'',
|
||||
environment BLOB NOT NULL DEFAULT X'',
|
||||
pending_mutation BLOB NOT NULL DEFAULT X'',
|
||||
pending_mutation_step INTEGER NOT NULL DEFAULT 0
|
||||
pending_mutation_step INTEGER NOT NULL DEFAULT 0,
|
||||
opcodes BLOB NOT NULL DEFAULT X''
|
||||
);",
|
||||
)?;
|
||||
ensure_column(
|
||||
@@ -280,6 +299,11 @@ fn init_schema(conn: &rusqlite::Connection) -> Result<(), rusqlite::Error> {
|
||||
"pending_mutation_step",
|
||||
"ALTER TABLE sessions ADD COLUMN pending_mutation_step INTEGER NOT NULL DEFAULT 0",
|
||||
)?;
|
||||
ensure_column(
|
||||
conn,
|
||||
"opcodes",
|
||||
"ALTER TABLE sessions ADD COLUMN opcodes BLOB NOT NULL DEFAULT X''",
|
||||
)?;
|
||||
conn.execute_batch(
|
||||
"CREATE INDEX IF NOT EXISTS idx_sessions_expires_at ON sessions(expires_at);",
|
||||
)?;
|
||||
@@ -313,67 +337,136 @@ impl ValkeyStore {
|
||||
&self,
|
||||
session_id: &str,
|
||||
) -> Result<Option<SessionRecord>, Box<dyn std::error::Error>> {
|
||||
let mut client = self.client.lock().unwrap();
|
||||
if let Some(payload) = client.get(&self.session_key(session_id))? {
|
||||
let record = serde_json::from_str(&payload)?;
|
||||
Ok(Some(record))
|
||||
} else {
|
||||
Ok(None)
|
||||
let mut conn = self.pool.get()?;
|
||||
let key = self.session_key(session_id);
|
||||
let payload: Option<String> = conn.get(&key)?;
|
||||
match payload {
|
||||
Some(p) => Ok(serde_json::from_str(&p)?),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn insert_session(&self, record: &SessionRecord) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut client = self.client.lock().unwrap();
|
||||
let mut conn = self.pool.get()?;
|
||||
let key = self.session_key(&record.session_id);
|
||||
let value = serde_json::to_string(record)?;
|
||||
client.set(&self.session_key(&record.session_id), &value)?;
|
||||
let existing = client.get(&self.index_key)?;
|
||||
let mut ids = existing.unwrap_or_default();
|
||||
if !ids.split('\n').any(|id| id == record.session_id) {
|
||||
if !ids.is_empty() {
|
||||
ids.push('\n');
|
||||
}
|
||||
ids.push_str(&record.session_id);
|
||||
client.set(&self.index_key, &ids)?;
|
||||
}
|
||||
let now = current_time_ms();
|
||||
let ttl_seconds = (record.expires_at.saturating_sub(now) / 1000).max(1);
|
||||
|
||||
redis::pipe()
|
||||
.atomic()
|
||||
.cmd("SET")
|
||||
.arg(&key)
|
||||
.arg(&value)
|
||||
.arg("EX")
|
||||
.arg(ttl_seconds)
|
||||
.cmd("ZADD")
|
||||
.arg(&self.index_key)
|
||||
.arg(record.expires_at)
|
||||
.arg(&record.session_id)
|
||||
.cmd("ZADD")
|
||||
.arg("sessions:chain_lengths")
|
||||
.arg(record.chain_length)
|
||||
.arg(&record.session_id)
|
||||
.query::<()>(&mut *conn)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn purge_expired_sessions(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut client = self.client.lock().unwrap();
|
||||
let ids = client.get(&self.index_key)?.unwrap_or_default();
|
||||
let now = current_time_ms();
|
||||
let mut remaining: Vec<String> = Vec::new();
|
||||
for id in ids.split('\n').filter(|id| !id.is_empty()) {
|
||||
if let Some(payload) = client.get(&self.session_key(id))? {
|
||||
if let Ok(record) = serde_json::from_str::<SessionRecord>(&payload) {
|
||||
if record.expires_at > now {
|
||||
remaining.push(id.to_string());
|
||||
}
|
||||
fn update_session_cas(
|
||||
&self,
|
||||
record: &SessionRecord,
|
||||
old_last_hash: &[u8],
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut conn = self.pool.get()?;
|
||||
let key = self.session_key(&record.session_id);
|
||||
|
||||
// Watch key for concurrent modification
|
||||
redis::cmd("WATCH").arg(&key).query::<()>(&mut *conn)?;
|
||||
|
||||
// Fetch current and verify last_hash matches
|
||||
let payload: Option<String> = conn.get(&key)?;
|
||||
match payload {
|
||||
Some(p) => {
|
||||
let current_record: SessionRecord = serde_json::from_str(&p)?;
|
||||
if current_record.last_hash != old_last_hash {
|
||||
redis::cmd("UNWATCH").query::<()>(&mut *conn)?;
|
||||
return Err(Box::new(std::io::Error::new(
|
||||
std::io::ErrorKind::AlreadyExists,
|
||||
"Concurrent update detected (CAS failed in Valkey)",
|
||||
)));
|
||||
}
|
||||
}
|
||||
None => {
|
||||
redis::cmd("UNWATCH").query::<()>(&mut *conn)?;
|
||||
return Err(Box::new(std::io::Error::new(
|
||||
std::io::ErrorKind::NotFound,
|
||||
"Session not found for update in Valkey",
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
let value = serde_json::to_string(record)?;
|
||||
let now = current_time_ms();
|
||||
let ttl_seconds = (record.expires_at.saturating_sub(now) / 1000).max(1);
|
||||
|
||||
let response: Option<()> = redis::pipe()
|
||||
.atomic()
|
||||
.cmd("SET")
|
||||
.arg(&key)
|
||||
.arg(&value)
|
||||
.arg("EX")
|
||||
.arg(ttl_seconds)
|
||||
.cmd("ZADD")
|
||||
.arg(&self.index_key)
|
||||
.arg(record.expires_at)
|
||||
.arg(&record.session_id)
|
||||
.cmd("ZADD")
|
||||
.arg("sessions:chain_lengths")
|
||||
.arg(record.chain_length)
|
||||
.arg(&record.session_id)
|
||||
.query(&mut *conn)?;
|
||||
|
||||
match response {
|
||||
Some(_) => Ok(()),
|
||||
None => Err(Box::new(std::io::Error::new(
|
||||
std::io::ErrorKind::AlreadyExists,
|
||||
"Transaction aborted due to concurrent modification",
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
fn purge_expired_sessions(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let now = current_time_ms();
|
||||
let mut conn = self.pool.get()?;
|
||||
// Fetch expired session IDs
|
||||
let expired_ids: Vec<String> = conn.zrangebyscore(&self.index_key, 0, now)?;
|
||||
if !expired_ids.is_empty() {
|
||||
redis::pipe()
|
||||
.atomic()
|
||||
.cmd("ZREM")
|
||||
.arg(&self.index_key)
|
||||
.arg(&expired_ids)
|
||||
.cmd("ZREM")
|
||||
.arg("sessions:chain_lengths")
|
||||
.arg(&expired_ids)
|
||||
.query::<()>(&mut *conn)?;
|
||||
}
|
||||
client.set(&self.index_key, &remaining.join("\n"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn stats(&self) -> Result<StoreStats, Box<dyn std::error::Error>> {
|
||||
let mut client = self.client.lock().unwrap();
|
||||
let ids = client.get(&self.index_key)?.unwrap_or_default();
|
||||
let now = current_time_ms();
|
||||
let mut sessions = 0;
|
||||
let mut expired_sessions = 0;
|
||||
let mut max_chain_length = 0;
|
||||
for id in ids.split('\n').filter(|id| !id.is_empty()) {
|
||||
if let Some(payload) = client.get(&self.session_key(id))? {
|
||||
if let Ok(record) = serde_json::from_str::<SessionRecord>(&payload) {
|
||||
sessions += 1;
|
||||
if record.expires_at < now {
|
||||
expired_sessions += 1;
|
||||
}
|
||||
max_chain_length = max_chain_length.max(record.chain_length);
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut conn = self.pool.get()?;
|
||||
let sessions: u64 = conn.zcard(&self.index_key)?;
|
||||
let expired_sessions: u64 = conn.zcount(&self.index_key, 0, now)?;
|
||||
|
||||
let max_chain_length_res: Vec<(String, u64)> =
|
||||
conn.zrevrange_withscores("sessions:chain_lengths", 0, 0)?;
|
||||
let max_chain_length = max_chain_length_res
|
||||
.first()
|
||||
.map(|(_, score)| *score)
|
||||
.unwrap_or(0);
|
||||
|
||||
Ok(StoreStats {
|
||||
sessions,
|
||||
expired_sessions,
|
||||
@@ -385,6 +478,212 @@ impl ValkeyStore {
|
||||
pub fn current_time_ms() -> u64 {
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.expect("system clock is before UNIX epoch; check system time")
|
||||
.as_millis() as u64
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod valkey_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_valkey_store_operations() {
|
||||
let addr = std::env::var("CHRONOSEAL_VALKEY_ADDR")
|
||||
.unwrap_or_else(|_| "127.0.0.1:6379".to_string());
|
||||
let connection_string = format!("redis://{}", addr);
|
||||
let client = match redis::Client::open(connection_string) {
|
||||
Ok(c) => c,
|
||||
Err(_) => return,
|
||||
};
|
||||
let pool = match r2d2::Pool::builder().build(client) {
|
||||
Ok(p) => p,
|
||||
Err(_) => return,
|
||||
};
|
||||
let mut conn = match pool.get() {
|
||||
Ok(c) => c,
|
||||
Err(_) => return,
|
||||
};
|
||||
let _: () = match redis::cmd("PING").query(&mut *conn) {
|
||||
Ok(res) => res,
|
||||
Err(_) => return,
|
||||
};
|
||||
|
||||
let store = ValkeyStore {
|
||||
pool,
|
||||
index_key: "test:sessions:ids".to_string(),
|
||||
};
|
||||
|
||||
let _: Result<(), _> = conn.del("test:sessions:ids");
|
||||
|
||||
let session_id = "test_session_123".to_string();
|
||||
let record = SessionRecord {
|
||||
session_id: session_id.clone(),
|
||||
public_key: vec![1, 2, 3],
|
||||
salt: vec![4, 5, 6],
|
||||
last_hash: vec![7, 8, 9],
|
||||
chain_length: 10,
|
||||
created_at: 1000,
|
||||
last_seen: 2000,
|
||||
expires_at: current_time_ms() + 10000,
|
||||
gene: vec![11],
|
||||
environment: vec![12],
|
||||
pending_mutation: vec![13],
|
||||
pending_mutation_step: 14,
|
||||
opcodes: vec![],
|
||||
};
|
||||
|
||||
store.insert_session(&record).unwrap();
|
||||
|
||||
let loaded = store.load_session(&session_id).unwrap().unwrap();
|
||||
assert_eq!(loaded.session_id, session_id);
|
||||
assert_eq!(loaded.chain_length, 10);
|
||||
|
||||
let stats = store.stats().unwrap();
|
||||
assert_eq!(stats.sessions, 1);
|
||||
assert_eq!(stats.max_chain_length, 10);
|
||||
|
||||
store.purge_expired_sessions().unwrap();
|
||||
let stats = store.stats().unwrap();
|
||||
assert_eq!(stats.sessions, 1);
|
||||
|
||||
let _: Result<(), _> = conn.del(store.session_key(&session_id));
|
||||
let _: Result<(), _> = conn.del(&store.index_key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_valkey_pool_concurrency() {
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
|
||||
let addr = std::env::var("CHRONOSEAL_VALKEY_ADDR")
|
||||
.unwrap_or_else(|_| "127.0.0.1:6379".to_string());
|
||||
let connection_string = format!("redis://{}", addr);
|
||||
let client = match redis::Client::open(connection_string) {
|
||||
Ok(c) => c,
|
||||
Err(_) => return,
|
||||
};
|
||||
let pool = match r2d2::Pool::builder().build(client) {
|
||||
Ok(p) => p,
|
||||
Err(_) => return,
|
||||
};
|
||||
let mut conn = match pool.get() {
|
||||
Ok(c) => c,
|
||||
Err(_) => return,
|
||||
};
|
||||
let _: () = match redis::cmd("PING").query(&mut *conn) {
|
||||
Ok(res) => res,
|
||||
Err(_) => return,
|
||||
};
|
||||
|
||||
let store = ValkeyStore {
|
||||
pool,
|
||||
index_key: "test:concurrent:sessions:ids".to_string(),
|
||||
};
|
||||
let _: Result<(), _> = conn.del("test:concurrent:sessions:ids");
|
||||
|
||||
let store_arc = Arc::new(store);
|
||||
let mut handles = Vec::new();
|
||||
|
||||
for t in 0..10 {
|
||||
let store_clone = store_arc.clone();
|
||||
let session_id = format!("valkey_concurrent_{}", t);
|
||||
let handle = thread::spawn(move || {
|
||||
let record = SessionRecord {
|
||||
session_id: session_id.clone(),
|
||||
public_key: vec![1, 2, 3],
|
||||
salt: vec![4, 5, 6],
|
||||
last_hash: vec![7, 8, 9],
|
||||
chain_length: 1,
|
||||
created_at: 1000,
|
||||
last_seen: 2000,
|
||||
expires_at: current_time_ms() + 10000,
|
||||
gene: vec![11],
|
||||
environment: vec![12],
|
||||
pending_mutation: vec![13],
|
||||
pending_mutation_step: 14,
|
||||
opcodes: vec![],
|
||||
};
|
||||
store_clone.insert_session(&record).unwrap();
|
||||
let loaded = store_clone.load_session(&session_id).unwrap().unwrap();
|
||||
assert_eq!(loaded.session_id, session_id);
|
||||
});
|
||||
handles.push(handle);
|
||||
}
|
||||
|
||||
for handle in handles {
|
||||
handle.join().unwrap();
|
||||
}
|
||||
|
||||
let stats = store_arc.stats().unwrap();
|
||||
assert_eq!(stats.sessions, 10);
|
||||
|
||||
// Cleanup
|
||||
let mut conn = store_arc.pool.get().unwrap();
|
||||
for t in 0..10 {
|
||||
let _: Result<(), _> =
|
||||
conn.del(store_arc.session_key(&format!("valkey_concurrent_{}", t)));
|
||||
}
|
||||
let _: Result<(), _> = conn.del(&store_arc.index_key);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod sqlite_tests {
|
||||
use super::*;
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
|
||||
#[test]
|
||||
fn test_sqlite_pool_concurrency() {
|
||||
let db_path = Path::new("target/test_sqlite_concurrency.db");
|
||||
if let Some(parent) = db_path.parent() {
|
||||
let _ = std::fs::create_dir_all(parent);
|
||||
}
|
||||
let _ = std::fs::remove_file(db_path);
|
||||
|
||||
let pool = init_pool(db_path).unwrap();
|
||||
let pool_arc = Arc::new(pool);
|
||||
let mut handles = Vec::new();
|
||||
|
||||
for t in 0..10 {
|
||||
let pool_clone = pool_arc.clone();
|
||||
let handle = thread::spawn(move || {
|
||||
let session_id = format!("concurrent_session_{}", t);
|
||||
let record = SessionRecord {
|
||||
session_id: session_id.clone(),
|
||||
public_key: vec![1, 2, 3],
|
||||
salt: vec![4, 5, 6],
|
||||
last_hash: vec![7, 8, 9],
|
||||
chain_length: 1,
|
||||
created_at: 1000,
|
||||
last_seen: 2000,
|
||||
expires_at: current_time_ms() + 10000,
|
||||
gene: vec![11],
|
||||
environment: vec![12],
|
||||
pending_mutation: vec![13],
|
||||
pending_mutation_step: 14,
|
||||
opcodes: vec![],
|
||||
};
|
||||
pool_clone.insert_session(&record).unwrap();
|
||||
let loaded = pool_clone.load_session(&session_id).unwrap().unwrap();
|
||||
assert_eq!(loaded.session_id, session_id);
|
||||
|
||||
let mut updated = loaded;
|
||||
updated.chain_length = 2;
|
||||
let old_hash = updated.last_hash.clone();
|
||||
pool_clone.update_session(&updated, &old_hash).unwrap();
|
||||
});
|
||||
handles.push(handle);
|
||||
}
|
||||
|
||||
for handle in handles {
|
||||
handle.join().unwrap();
|
||||
}
|
||||
|
||||
let stats = pool_arc.stats().unwrap();
|
||||
assert_eq!(stats.sessions, 10);
|
||||
|
||||
std::mem::drop(pool_arc);
|
||||
let _ = std::fs::remove_file(db_path);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,14 @@
|
||||
use crate::config::Config;
|
||||
use shared::protocol::EntropyData;
|
||||
|
||||
/// Validates the browser mouse cursor interaction path for bot/automation detection.
|
||||
///
|
||||
/// Evaluates mouse velocity and distance features, checks the total distance traversed,
|
||||
/// checks for cursor pauses (low movement over high time diff), and enforces average cursor speeds.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `data` - The client-supplied interaction entropy events.
|
||||
/// * `config` - The server configuration boundaries.
|
||||
pub fn validate_mouse(
|
||||
data: &EntropyData,
|
||||
config: &Config,
|
||||
|
||||
@@ -4,6 +4,13 @@ use shared::{
|
||||
vm_extensions::{self, ExecutionTrace, MutationError, MutationOrder},
|
||||
};
|
||||
|
||||
/// Generates a randomized VM opcode instruction program within a length range.
|
||||
///
|
||||
/// Builds a program of mathematical and stack ops (e.g. literals, ADD, SUB, XOR, HASH)
|
||||
/// with dynamic depth checking to ensure valid stacks and prevent out of bounds execution.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `len_range` - The inclusive range of instruction counts to generate.
|
||||
pub fn generate_random_program(len_range: std::ops::RangeInclusive<usize>) -> Vec<u8> {
|
||||
let mut rng = rand::thread_rng();
|
||||
let count = rng.gen_range(len_range);
|
||||
@@ -47,6 +54,11 @@ pub fn generate_random_program(len_range: std::ops::RangeInclusive<usize>) -> Ve
|
||||
ops
|
||||
}
|
||||
|
||||
/// Executes a raw VM mutation program bytecode slice against a `GeneState`.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `state` - The mutable gene state to mutate.
|
||||
/// * `program` - The raw VM instruction program.
|
||||
#[allow(dead_code)]
|
||||
pub fn execute_mutation_program(
|
||||
state: &mut GeneState,
|
||||
@@ -55,6 +67,11 @@ pub fn execute_mutation_program(
|
||||
vm_extensions::execute_program(state, program)
|
||||
}
|
||||
|
||||
/// Executes a `MutationOrder` program against a `GeneState`.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `state` - The mutable gene state.
|
||||
/// * `order` - The mutation order.
|
||||
#[allow(dead_code)]
|
||||
pub fn execute_mutation_order(
|
||||
state: &mut GeneState,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "shared"
|
||||
version = "0.6.0"
|
||||
version = "1.0.1"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -10,3 +10,4 @@ pub const MAX_MUTATION_ROUNDS: u8 = 10;
|
||||
pub const MAX_MUTATION_INSTRUCTION_BUDGET: usize = 2048;
|
||||
pub const HASH_OPCODE_INSTRUCTION_COST: usize = 16;
|
||||
pub const SOFT_CAP_DURATION_MS: u128 = 50;
|
||||
pub const MAX_STACK_DEPTH: usize = 64;
|
||||
@@ -55,6 +55,10 @@ impl std::fmt::Display for GeneError {
|
||||
|
||||
impl std::error::Error for GeneError {}
|
||||
|
||||
/// Creates a new, blank `GeneState` with the specified gene buffer size.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `gene_size` - The length of the gene byte buffer. Must be within `1..=MAX_GENE_SIZE`.
|
||||
pub fn new_state(gene_size: usize) -> Result<GeneState, GeneError> {
|
||||
if !(1..=MAX_GENE_SIZE).contains(&gene_size) {
|
||||
return Err(GeneError::InvalidGeneSize { size: gene_size });
|
||||
@@ -65,6 +69,7 @@ pub fn new_state(gene_size: usize) -> Result<GeneState, GeneError> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates a new `GeneState` with the default gene buffer size (`DEFAULT_GENE_SIZE`).
|
||||
pub fn default_state() -> GeneState {
|
||||
GeneState {
|
||||
gene: vec![0; DEFAULT_GENE_SIZE],
|
||||
@@ -72,6 +77,10 @@ pub fn default_state() -> GeneState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Validates the structural invariants of the given `GeneState`.
|
||||
///
|
||||
/// Ensures the gene size is within valid bounds and the environment records are
|
||||
/// properly sorted, non-empty, and free of duplicates.
|
||||
pub fn validate_state(state: &GeneState) -> Result<(), GeneError> {
|
||||
if !(1..=MAX_GENE_SIZE).contains(&state.gene.len()) {
|
||||
return Err(GeneError::InvalidGeneSize {
|
||||
@@ -81,6 +90,13 @@ pub fn validate_state(state: &GeneState) -> Result<(), GeneError> {
|
||||
validate_environment(&state.environment)
|
||||
}
|
||||
|
||||
/// Retrieves the quantity associated with a specific environment symbol.
|
||||
///
|
||||
/// Performs a binary search over the sorted environment records. Returns 0 if the symbol is missing.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `state` - The gene state to query.
|
||||
/// * `symbol` - The 16-bit key to search for.
|
||||
pub fn get_env_quantity(state: &GeneState, symbol: u16) -> u32 {
|
||||
match state
|
||||
.environment
|
||||
@@ -91,6 +107,14 @@ pub fn get_env_quantity(state: &GeneState, symbol: u16) -> u32 {
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the quantity of an environment symbol in a `GeneState`.
|
||||
///
|
||||
/// If quantity is 0, the record is removed. The environment is kept sorted alphabetically by symbol.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `state` - The mutable gene state to update.
|
||||
/// * `symbol` - The 16-bit key.
|
||||
/// * `quantity` - The quantity to assign.
|
||||
pub fn set_env_quantity(
|
||||
state: &mut GeneState,
|
||||
symbol: u16,
|
||||
@@ -125,6 +149,12 @@ pub fn set_env_quantity(
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a quantity to an environment symbol with saturating arithmetic.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `state` - The mutable gene state.
|
||||
/// * `symbol` - The 16-bit key.
|
||||
/// * `quantity` - The quantity to add.
|
||||
pub fn add_env_quantity(
|
||||
state: &mut GeneState,
|
||||
symbol: u16,
|
||||
@@ -136,6 +166,14 @@ pub fn add_env_quantity(
|
||||
Ok(next)
|
||||
}
|
||||
|
||||
/// Subtracts a quantity from an environment symbol with saturating arithmetic.
|
||||
///
|
||||
/// If the resulting quantity drops to 0, the symbol is removed.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `state` - The mutable gene state.
|
||||
/// * `symbol` - The 16-bit key.
|
||||
/// * `quantity` - The quantity to subtract.
|
||||
pub fn sub_env_quantity(
|
||||
state: &mut GeneState,
|
||||
symbol: u16,
|
||||
@@ -147,6 +185,12 @@ pub fn sub_env_quantity(
|
||||
Ok(next)
|
||||
}
|
||||
|
||||
/// Encodes the environment records list into a compact byte slice.
|
||||
///
|
||||
/// Each record is written as a little-endian `u16` symbol followed by a little-endian `u32` quantity.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `records` - The sorted environment records.
|
||||
pub fn encode_environment(records: &[EnvironmentRecord]) -> Result<Vec<u8>, GeneError> {
|
||||
validate_environment(records)?;
|
||||
let mut out = Vec::with_capacity(records.len() * 6);
|
||||
@@ -157,6 +201,12 @@ pub fn encode_environment(records: &[EnvironmentRecord]) -> Result<Vec<u8>, Gene
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// Decodes environment records from a byte slice.
|
||||
///
|
||||
/// Validates that the length is a multiple of 6 and that records conform to sorting and quantity invariants.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `blob` - The serialized byte slice.
|
||||
pub fn decode_environment(blob: &[u8]) -> Result<Vec<EnvironmentRecord>, GeneError> {
|
||||
if !blob.len().is_multiple_of(6) {
|
||||
return Err(GeneError::EnvironmentBlobLengthInvalid { len: blob.len() });
|
||||
@@ -180,6 +230,9 @@ pub fn decode_environment(blob: &[u8]) -> Result<Vec<EnvironmentRecord>, GeneErr
|
||||
Ok(records)
|
||||
}
|
||||
|
||||
/// Computes the raw Blake3 cryptographic commitment of the `GeneState`.
|
||||
///
|
||||
/// Includes gene length, gene buffer, environment record count, and individual record key/values.
|
||||
pub fn commitment(state: &GeneState) -> [u8; 32] {
|
||||
let mut h = blake3::Hasher::new();
|
||||
h.update(b"chronoseal/gene/v1");
|
||||
@@ -193,10 +246,19 @@ pub fn commitment(state: &GeneState) -> [u8; 32] {
|
||||
*h.finalize().as_bytes()
|
||||
}
|
||||
|
||||
/// Computes the hex-encoded cryptographic commitment of the `GeneState`.
|
||||
pub fn commitment_hex(state: &GeneState) -> String {
|
||||
hex::encode(commitment(state))
|
||||
}
|
||||
|
||||
/// Computes a context-bound Blake3 cryptographic commitment of the `GeneState`.
|
||||
///
|
||||
/// Integrates `session_id` and the current `step` index into the hash to bind the commitment.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `state` - The gene state.
|
||||
/// * `session_id` - The client session ID.
|
||||
/// * `step` - The mutation step index.
|
||||
pub fn commitment_with_context(state: &GeneState, session_id: &str, step: u64) -> [u8; 32] {
|
||||
let mut h = blake3::Hasher::new();
|
||||
h.update(b"chronoseal/gene/v1");
|
||||
@@ -206,10 +268,17 @@ pub fn commitment_with_context(state: &GeneState, session_id: &str, step: u64) -
|
||||
*h.finalize().as_bytes()
|
||||
}
|
||||
|
||||
/// Computes a context-bound, hex-encoded Blake3 cryptographic commitment of the `GeneState`.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `state` - The gene state.
|
||||
/// * `session_id` - The client session ID.
|
||||
/// * `step` - The mutation step index.
|
||||
pub fn commitment_hex_with_context(state: &GeneState, session_id: &str, step: u64) -> String {
|
||||
hex::encode(commitment_with_context(state, session_id, step))
|
||||
}
|
||||
|
||||
/// Helper function to validate sorting, uniqueness, and non-zero properties of environment records.
|
||||
fn validate_environment(records: &[EnvironmentRecord]) -> Result<(), GeneError> {
|
||||
if records.len() > MAX_ENV_RECORDS {
|
||||
return Err(GeneError::TooManyEnvironmentRecords { len: records.len() });
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
use crate::protocol::{EntropyData, StackState};
|
||||
use blake3::Hasher;
|
||||
|
||||
/// Initial hash for a brand-new session: Blake3(session_id || pub_key || salt)
|
||||
/// Computes the initial hash for a brand-new attestation session.
|
||||
///
|
||||
/// The hash is constructed as:
|
||||
/// `Blake3(session_id || pub_key || salt)`
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `session_id` - The unique hex-encoded identifier for the session.
|
||||
/// * `pub_key` - The client's Ed25519 public key.
|
||||
/// * `salt` - The initial server-issued salt.
|
||||
pub fn initial_hash(session_id: &str, pub_key: &[u8], salt: &[u8]) -> Vec<u8> {
|
||||
let mut h = Hasher::new();
|
||||
h.update(session_id.as_bytes());
|
||||
@@ -10,7 +18,18 @@ pub fn initial_hash(session_id: &str, pub_key: &[u8], salt: &[u8]) -> Vec<u8> {
|
||||
h.finalize().as_bytes().to_vec()
|
||||
}
|
||||
|
||||
/// Next hash in the chain: Blake3 with the salt mixed in (no keyed mode needed)
|
||||
/// Computes the next hash in the Blake3 attestation chain.
|
||||
///
|
||||
/// This mixes in the previous hash head, the client timestamp, the serialized entropy data,
|
||||
/// the VM stack state, and the server-issued salt. Uses `serde_json::to_vec` to avoid
|
||||
/// intermediate heap string allocations and UTF-8 verification checks.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `prev_hash` - The previous hash-chain head.
|
||||
/// * `timestamp` - The client-supplied heartbeat timestamp.
|
||||
/// * `entropy` - The collected browser interaction entropy.
|
||||
/// * `stack` - The final VM stack state after running the opcode program.
|
||||
/// * `salt` - The server-issued salt for rotation.
|
||||
pub fn next_chain_hash(
|
||||
prev_hash: &[u8],
|
||||
timestamp: u64,
|
||||
@@ -18,14 +37,13 @@ pub fn next_chain_hash(
|
||||
stack: &StackState,
|
||||
salt: &[u8],
|
||||
) -> Vec<u8> {
|
||||
let entropy_json = serde_json::to_string(entropy).unwrap();
|
||||
let stack_json = serde_json::to_string(stack).unwrap();
|
||||
let entropy_bytes = serde_json::to_vec(entropy).unwrap_or_default();
|
||||
let stack_bytes = serde_json::to_vec(stack).unwrap_or_default();
|
||||
|
||||
let entropy_hash = blake3::hash(entropy_json.as_bytes());
|
||||
let stack_hash = blake3::hash(stack_json.as_bytes());
|
||||
let entropy_hash = blake3::hash(&entropy_bytes);
|
||||
let stack_hash = blake3::hash(&stack_bytes);
|
||||
|
||||
let mut h = Hasher::new();
|
||||
// Mix the salt into the hash state
|
||||
h.update(salt);
|
||||
h.update(prev_hash);
|
||||
h.update(×tamp.to_le_bytes());
|
||||
@@ -34,7 +52,12 @@ pub fn next_chain_hash(
|
||||
h.finalize().as_bytes().to_vec()
|
||||
}
|
||||
|
||||
/// Hash of all stack items for VM HASH opcode
|
||||
/// Computes a 32-bit FNV-like Blake3 hash of all stack elements.
|
||||
///
|
||||
/// This is used by the VM `HASH` opcode to fold the current stack state into a single value.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `stack` - The list of u32 stack elements to hash.
|
||||
pub fn hash_stack(stack: &[u32]) -> u32 {
|
||||
let data: Vec<u8> = stack.iter().flat_map(|x| x.to_le_bytes()).collect();
|
||||
let hash = blake3::hash(&data);
|
||||
|
||||
@@ -1,73 +1,118 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Request payload sent by the client to initialize a new attestation session.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct InitRequest {
|
||||
/// Hex-encoded 32-byte Ed25519 public verifying key generated by the client.
|
||||
pub public_key: String,
|
||||
}
|
||||
|
||||
/// Response payload returned by the server upon successful session initialization.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct InitResponse {
|
||||
/// The unique hex-encoded session identifier.
|
||||
pub session_id: String,
|
||||
/// The initial server-issued salt to be mixed in the first heartbeat's hash.
|
||||
pub salt: String,
|
||||
/// Base64-encoded initial VM program for client stack execution.
|
||||
pub opcodes_b64: String,
|
||||
/// The computed initial hash of the attestation chain.
|
||||
pub initial_hash: String,
|
||||
/// Timestamp in milliseconds indicating when the session expires.
|
||||
pub expires_at: u64,
|
||||
/// Minimum time in milliseconds allowed between subsequent heartbeats.
|
||||
pub heartbeat_min_interval_ms: u64,
|
||||
/// Maximum time in milliseconds allowed between subsequent heartbeats.
|
||||
pub heartbeat_max_interval_ms: u64,
|
||||
/// Size of the synthetic gene byte buffer.
|
||||
pub gene_size: u32,
|
||||
/// The current mutation step index (starts at 1).
|
||||
pub mutation_step: u64,
|
||||
/// Base64-encoded initial gene mutation program.
|
||||
pub mutation_order_b64: String,
|
||||
/// The number of mutation rounds configured on the server.
|
||||
pub mutation_rounds: u8,
|
||||
}
|
||||
|
||||
/// Heartbeat request payload submitted periodically by the client to prove session continuity.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct HeartbeatRequest {
|
||||
/// The session identifier.
|
||||
pub session_id: String,
|
||||
/// The expected hash from the previous heartbeat/initialization step.
|
||||
pub prev_hash: String,
|
||||
/// The client's current system timestamp in milliseconds.
|
||||
pub timestamp: u64,
|
||||
/// The collected client entropy data (such as mouse events).
|
||||
pub entropy_data: EntropyData,
|
||||
/// The final execution state of the client's VM stack program.
|
||||
pub stack_state: StackState,
|
||||
/// The client's browser hardware and layout fingerprint.
|
||||
pub fingerprint: Fingerprint,
|
||||
/// The mutation step index corresponding to the pending mutation.
|
||||
pub mutation_step: u64,
|
||||
/// Hex-encoded commitment of the mutated gene state.
|
||||
pub gene_commitment: String,
|
||||
/// Ed25519 signature of the canonical JSON-serialized payload.
|
||||
pub signature: String,
|
||||
}
|
||||
|
||||
/// Response payload returned by the server for heartbeat submissions.
|
||||
///
|
||||
/// In case of silent rejection, all fields except `status` are omitted.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct HeartbeatResponse {
|
||||
/// Attestation status, typically "ok" even on silent failures.
|
||||
pub status: String,
|
||||
/// The next server-issued salt for hash chain progression.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub next_salt: Option<String>,
|
||||
/// The next expected mutation step index.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub next_mutation_step: Option<u64>,
|
||||
/// Base64-encoded next mutation program for client gene progression.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub next_mutation_order_b64: Option<String>,
|
||||
}
|
||||
|
||||
/// Client browser fingerprint metadata used for basic sanity checks.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Fingerprint {
|
||||
/// Aspect ratio of the client screen.
|
||||
#[serde(rename = "aspectRatio")]
|
||||
pub aspect_ratio: String,
|
||||
/// Device pixel ratio of the screen.
|
||||
#[serde(rename = "devicePixelRatio")]
|
||||
pub device_pixel_ratio: String,
|
||||
/// Number of logical processor cores available.
|
||||
#[serde(rename = "hardwareConcurrency")]
|
||||
pub hardware_concurrency: u32,
|
||||
}
|
||||
|
||||
/// Wrapper for browser-side entropy collection.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct EntropyData {
|
||||
/// A chronological list of mouse movement events.
|
||||
pub events: Vec<MouseEvent>,
|
||||
}
|
||||
|
||||
/// Information about a single mouse movement interaction.
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
pub struct MouseEvent {
|
||||
/// Absolute horizontal coordinate of the cursor.
|
||||
pub x: f64,
|
||||
/// Absolute vertical coordinate of the cursor.
|
||||
pub y: f64,
|
||||
/// Relative timestamp in milliseconds of the event occurrence.
|
||||
#[serde(rename = "t")]
|
||||
pub timestamp_ms: f64,
|
||||
}
|
||||
|
||||
/// The state of the VM stack machine after executing a program.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct StackState {
|
||||
/// The elements remaining on the stack.
|
||||
pub stack: Vec<u32>,
|
||||
/// The final instruction pointer location at program completion or termination.
|
||||
pub ip: u16,
|
||||
}
|
||||
@@ -25,6 +25,9 @@ pub fn execute(program: &[u8]) -> StackState {
|
||||
program[ip + 3],
|
||||
]);
|
||||
ip += 4;
|
||||
if stack.len() >= crate::constants::MAX_STACK_DEPTH {
|
||||
break;
|
||||
}
|
||||
stack.push(val);
|
||||
}
|
||||
0x01..=0x07 => {
|
||||
@@ -43,6 +46,9 @@ pub fn execute(program: &[u8]) -> StackState {
|
||||
0x07 => a.rotate_left(b % 32),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
if stack.len() >= crate::constants::MAX_STACK_DEPTH {
|
||||
break;
|
||||
}
|
||||
stack.push(r);
|
||||
}
|
||||
0x08 => {
|
||||
@@ -50,11 +56,17 @@ pub fn execute(program: &[u8]) -> StackState {
|
||||
break;
|
||||
}
|
||||
let a = stack.pop().unwrap();
|
||||
if stack.len() >= crate::constants::MAX_STACK_DEPTH {
|
||||
break;
|
||||
}
|
||||
stack.push(!a);
|
||||
}
|
||||
0x09 => {
|
||||
let r = crate::hashing::hash_stack(&stack);
|
||||
stack.clear();
|
||||
if stack.len() >= crate::constants::MAX_STACK_DEPTH {
|
||||
break;
|
||||
}
|
||||
stack.push(r);
|
||||
}
|
||||
_ => break,
|
||||
|
||||
@@ -88,10 +88,18 @@ impl From<GeneError> for MutationError {
|
||||
}
|
||||
}
|
||||
|
||||
/// Encodes a `MutationOrder` into standard Base64 representation of its bytecode.
|
||||
pub fn encode_order_b64(order: &MutationOrder) -> String {
|
||||
base64::Engine::encode(&base64::engine::general_purpose::STANDARD, &order.program)
|
||||
}
|
||||
|
||||
/// Decodes a `MutationOrder` from its Base64 representation.
|
||||
///
|
||||
/// Validates that the decoded program size does not exceed the allowed maximum budget size.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `step` - The step index associated with this mutation order.
|
||||
/// * `b64` - The Base64 string containing the raw bytecode.
|
||||
pub fn decode_order_b64(step: u64, b64: &str) -> Result<MutationOrder, MutationError> {
|
||||
let program = base64::Engine::decode(&base64::engine::general_purpose::STANDARD, b64)
|
||||
.map_err(MutationError::Base64)?;
|
||||
@@ -101,11 +109,27 @@ pub fn decode_order_b64(step: u64, b64: &str) -> Result<MutationOrder, MutationE
|
||||
Ok(MutationOrder { step, program })
|
||||
}
|
||||
|
||||
/// Generates a randomized `MutationOrder` program for a given step and gene size.
|
||||
///
|
||||
/// Uses thread-local random number generator.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `step` - The step index.
|
||||
/// * `gene_size` - The length of the gene byte buffer.
|
||||
pub fn generate_order(step: u64, gene_size: usize) -> MutationOrder {
|
||||
let mut rng = rand::thread_rng();
|
||||
generate_order_with_rng(&mut rng, step, gene_size)
|
||||
}
|
||||
|
||||
/// Generates a randomized `MutationOrder` program using a specific custom RNG.
|
||||
///
|
||||
/// Builds a program containing between 20 and 36 mutation instructions (e.g. loads, point changes,
|
||||
/// insertions, deletions, env modifications) and ensures a minimum number of finalize hash steps are included.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `rng` - The random number generator.
|
||||
/// * `step` - The step index.
|
||||
/// * `gene_size` - The length of the gene byte buffer.
|
||||
pub fn generate_order_with_rng<R: Rng + ?Sized>(
|
||||
rng: &mut R,
|
||||
step: u64,
|
||||
@@ -213,10 +237,12 @@ pub fn generate_order_with_rng<R: Rng + ?Sized>(
|
||||
MutationOrder { step, program }
|
||||
}
|
||||
|
||||
/// Clones the `GeneState` and executes the mutation program for `DEFAULT_MUTATION_ROUNDS`.
|
||||
pub fn apply_program_clone(state: &GeneState, program: &[u8]) -> Result<GeneState, MutationError> {
|
||||
apply_program_clone_with_rounds(state, program, DEFAULT_MUTATION_ROUNDS)
|
||||
}
|
||||
|
||||
/// Clones the `GeneState` and executes the mutation program for a specific number of rounds.
|
||||
pub fn apply_program_clone_with_rounds(
|
||||
state: &GeneState,
|
||||
program: &[u8],
|
||||
@@ -227,6 +253,7 @@ pub fn apply_program_clone_with_rounds(
|
||||
Ok(next)
|
||||
}
|
||||
|
||||
/// Executes the mutation program on the mutable `GeneState` reference for a specific number of rounds.
|
||||
pub fn apply_program_with_rounds(
|
||||
state: &mut GeneState,
|
||||
program: &[u8],
|
||||
@@ -236,11 +263,21 @@ pub fn apply_program_with_rounds(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Executes the mutation program on the mutable `GeneState` reference for `DEFAULT_MUTATION_ROUNDS`.
|
||||
pub fn apply_program(state: &mut GeneState, program: &[u8]) -> Result<(), MutationError> {
|
||||
let _ = execute_program_with_rounds(state, program, DEFAULT_MUTATION_ROUNDS)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Executes the mutation program on the mutable `GeneState` reference for multiple rounds.
|
||||
///
|
||||
/// Implements a soft instruction cost-budget cap check to prevent hostile/inefficient
|
||||
/// programs from lagging the host server thread or client runtime.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `state` - The mutable gene state buffer.
|
||||
/// * `program` - The raw bytecode sequence.
|
||||
/// * `rounds` - The requested number of execution rounds.
|
||||
pub fn execute_program_with_rounds(
|
||||
state: &mut GeneState,
|
||||
program: &[u8],
|
||||
@@ -317,6 +354,13 @@ fn estimate_program_cost(program: &[u8]) -> usize {
|
||||
cost.max(1)
|
||||
}
|
||||
|
||||
/// Executes the VM mutation program on the mutable `GeneState` reference.
|
||||
///
|
||||
/// This interprets VM mutation opcodes to modify the gene byte array and environment records.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `state` - The mutable gene state to mutate.
|
||||
/// * `program` - The raw instruction bytecode slice.
|
||||
pub fn execute_program(
|
||||
state: &mut GeneState,
|
||||
program: &[u8],
|
||||
@@ -835,4 +879,25 @@ mod tests {
|
||||
"mutation execution too slow: {elapsed:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vm_instruction_budget_soft_cap() {
|
||||
let state = new_state(8).unwrap();
|
||||
// Construct a program with 130 OP_FINALIZE_GENE_HASH instructions.
|
||||
// HASH has HASH_OPCODE_INSTRUCTION_COST = 16.
|
||||
// Total cost will be 130 * 16 = 2080, which exceeds MAX_MUTATION_INSTRUCTION_BUDGET (2048).
|
||||
let program = vec![OP_FINALIZE_GENE_HASH; 130];
|
||||
let cost = estimate_program_cost(&program);
|
||||
assert!(cost >= 2080);
|
||||
|
||||
// Assert that the max allowed rounds is calculated as 1 since cost > budget.
|
||||
let expected_rounds = std::cmp::max(1, MAX_MUTATION_INSTRUCTION_BUDGET / cost);
|
||||
assert_eq!(expected_rounds, 1);
|
||||
|
||||
// Execute the program with a requested 10 rounds.
|
||||
// The runtime should execute it successfully without panic, while applying the round limitation.
|
||||
let mut test_state = state.clone();
|
||||
let trace = execute_program_with_rounds(&mut test_state, &program, 10).unwrap();
|
||||
assert_eq!(trace.final_ip, program.len());
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "chronoseal-wasm"
|
||||
version = "0.6.0"
|
||||
version = "1.0.1"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
|
||||
@@ -51,8 +51,14 @@ pub fn compute_next_hash(
|
||||
) -> String {
|
||||
let prev = hex::decode(prev_hash_hex).unwrap_or_default();
|
||||
let salt = hex::decode(salt_hex).unwrap_or_default();
|
||||
let entropy = serde_json::from_str::<shared::protocol::EntropyData>(entropy_data_json).unwrap();
|
||||
let stack = serde_json::from_str::<shared::protocol::StackState>(stack_state_json).unwrap();
|
||||
let entropy = match serde_json::from_str::<shared::protocol::EntropyData>(entropy_data_json) {
|
||||
Ok(v) => v,
|
||||
Err(_) => return String::new(),
|
||||
};
|
||||
let stack = match serde_json::from_str::<shared::protocol::StackState>(stack_state_json) {
|
||||
Ok(v) => v,
|
||||
Err(_) => return String::new(),
|
||||
};
|
||||
let new = shared::hashing::next_chain_hash(&prev, timestamp, &entropy, &stack, &salt);
|
||||
hex::encode(new)
|
||||
}
|
||||
@@ -4,70 +4,19 @@ use wasm_bindgen::prelude::*;
|
||||
#[wasm_bindgen]
|
||||
pub fn run_program(program_b64: &str) -> JsValue {
|
||||
use base64::Engine;
|
||||
let bytes = base64::engine::general_purpose::STANDARD
|
||||
.decode(program_b64)
|
||||
.unwrap();
|
||||
let bytes = match base64::engine::general_purpose::STANDARD.decode(program_b64) {
|
||||
Ok(v) => v,
|
||||
Err(_) => return JsValue::NULL,
|
||||
};
|
||||
let state = execute(&bytes);
|
||||
serde_wasm_bindgen::to_value(&state).unwrap()
|
||||
match serde_wasm_bindgen::to_value(&state) {
|
||||
Ok(v) => v,
|
||||
Err(_) => JsValue::NULL,
|
||||
}
|
||||
}
|
||||
|
||||
fn execute(program: &[u8]) -> StackState {
|
||||
let mut stack: Vec<u32> = Vec::new();
|
||||
let mut ip: usize = 0;
|
||||
while ip < program.len() {
|
||||
let op = program[ip];
|
||||
ip += 1;
|
||||
match op {
|
||||
0x00 => {
|
||||
if ip + 4 > program.len() {
|
||||
break;
|
||||
}
|
||||
let val = u32::from_le_bytes([
|
||||
program[ip],
|
||||
program[ip + 1],
|
||||
program[ip + 2],
|
||||
program[ip + 3],
|
||||
]);
|
||||
ip += 4;
|
||||
stack.push(val);
|
||||
}
|
||||
0x01..=0x07 => {
|
||||
if stack.len() < 2 {
|
||||
break;
|
||||
}
|
||||
let b = stack.pop().unwrap();
|
||||
let a = stack.pop().unwrap();
|
||||
let r = match op {
|
||||
0x01 => a.wrapping_add(b),
|
||||
0x02 => a.wrapping_sub(b),
|
||||
0x03 => a.wrapping_mul(b),
|
||||
0x04 => a ^ b,
|
||||
0x05 => a & b,
|
||||
0x06 => a | b,
|
||||
0x07 => a.rotate_left(b % 32),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
stack.push(r);
|
||||
}
|
||||
0x08 => {
|
||||
if stack.is_empty() {
|
||||
break;
|
||||
}
|
||||
let a = stack.pop().unwrap();
|
||||
stack.push(!a);
|
||||
}
|
||||
0x09 => {
|
||||
let r = shared::hashing::hash_stack(&stack);
|
||||
stack.clear();
|
||||
stack.push(r);
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
StackState {
|
||||
stack,
|
||||
ip: ip as u16,
|
||||
}
|
||||
shared::vm::execute(program)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -0,0 +1,403 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
||||
<title>API Reference | ChronoSeal Documentation</title>
|
||||
<meta name="description" content="ChronoSeal HTTP API Reference, documenting endpoints, JSON request-response shapes, and WASM exports.">
|
||||
<link rel="stylesheet" href="css/chronoseal.css">
|
||||
<link rel="stylesheet" href="css/docs.css">
|
||||
<link rel="stylesheet" href="css/print.css" media="print">
|
||||
<link rel="manifest" href="site.webmanifest">
|
||||
<link rel="icon" href="assets/logo.svg" type="image/svg+xml">
|
||||
|
||||
<!-- Font Awesome for Icons -->
|
||||
<link rel="preconnect" href="https://cdnjs.cloudflare.com" crossorigin />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
|
||||
integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw=="
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Floating Background Spheres -->
|
||||
<div class="bg-animation">
|
||||
<div class="gradient-sphere sphere-1"></div>
|
||||
<div class="gradient-sphere sphere-2"></div>
|
||||
<div class="gradient-sphere sphere-3"></div>
|
||||
</div>
|
||||
|
||||
<!-- Header / Navbar -->
|
||||
<nav class="navbar" id="navbar">
|
||||
<div class="nav-container">
|
||||
<a href="index.html" class="logo">
|
||||
<img class="logobar" src="assets/logo.svg" alt="ChronoSeal Logo">
|
||||
<span>ChronoSeal</span>
|
||||
</a>
|
||||
<div class="nav-links" id="navLinks">
|
||||
<a href="philosophy.html">Philosophy</a>
|
||||
<a href="architecture.html">Architecture</a>
|
||||
<a href="protocol.html">Protocol</a>
|
||||
<a href="comparison.html">Comparison</a>
|
||||
<div class="nav-dropdown">
|
||||
<a href="#" class="nav-link">More <small>▼</small></a>
|
||||
<div class="nav-dropdown-menu">
|
||||
<a href="api.html" class="active">API Reference</a>
|
||||
<a href="deployment.html">Deployment Guide</a>
|
||||
<a href="operations.html">Operations Guide</a>
|
||||
<a href="testing.html">Testing Strategy</a>
|
||||
<a href="performance.html">Performance Tuning</a>
|
||||
<a href="threat-model.html">Threat Model</a>
|
||||
<a href="security.html">Security Policy</a>
|
||||
<a href="privacy.html">Privacy Policy</a>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-ghost btn-sm btn-icon search-trigger" aria-label="Search" onclick="ChronoSearch.open()">
|
||||
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" width="16" height="16"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
|
||||
</button>
|
||||
<a href="https://github.com/thakares/chronoseal-rs" class="github-btn" target="_blank" rel="noopener">
|
||||
<i class="fab fa-github"></i> GitHub
|
||||
</a>
|
||||
</div>
|
||||
<button class="nav-toggle" id="menuBtn" aria-label="Toggle Navigation">
|
||||
<i class="fas fa-bars"></i>
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Doc Page Shell -->
|
||||
<div class="doc-page">
|
||||
<div class="doc-layout">
|
||||
|
||||
<!-- Sidebar -->
|
||||
<aside class="doc-sidebar">
|
||||
<button class="doc-sidebar-toggle">
|
||||
<i class="fas fa-bars"></i> Sidebar Menu
|
||||
</button>
|
||||
<div class="doc-nav-group">
|
||||
<div class="doc-nav-label">Core Concepts</div>
|
||||
<a href="philosophy.html" class="doc-nav-item">Design Philosophy</a>
|
||||
<a href="architecture.html" class="doc-nav-item">Architecture Overview</a>
|
||||
<a href="comparison.html" class="doc-nav-item">ChronoSeal vs Others</a>
|
||||
</div>
|
||||
<div class="doc-nav-group">
|
||||
<div class="doc-nav-label">Protocol & API</div>
|
||||
<a href="protocol.html" class="doc-nav-item">Protocol Specification</a>
|
||||
<a href="api.html" class="doc-nav-item active">API Reference</a>
|
||||
</div>
|
||||
<div class="doc-nav-group">
|
||||
<div class="doc-nav-label">Operations & Security</div>
|
||||
<a href="threat-model.html" class="doc-nav-item">Threat Model</a>
|
||||
<a href="security.html" class="doc-nav-item">Security Policy</a>
|
||||
<a href="privacy.html" class="doc-nav-item">Privacy Policy</a>
|
||||
<a href="performance.html" class="doc-nav-item">Performance Tuning</a>
|
||||
</div>
|
||||
<div class="doc-nav-group">
|
||||
<div class="doc-nav-label">Lifecycle & Dev</div>
|
||||
<a href="deployment.html" class="doc-nav-item">Deployment Guide</a>
|
||||
<a href="operations.html" class="doc-nav-item">Operations Handbook</a>
|
||||
<a href="testing.html" class="doc-nav-item">Testing Strategy</a>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content Area -->
|
||||
<main class="doc-main">
|
||||
<nav class="doc-breadcrumb" aria-label="Breadcrumb">
|
||||
<a href="index.html">Home</a>
|
||||
<span class="sep">/</span>
|
||||
<a href="#">Protocol & API</a>
|
||||
<span class="sep">/</span>
|
||||
<span>API Reference</span>
|
||||
</nav>
|
||||
|
||||
<article class="doc-content">
|
||||
<h1>ChronoSeal API Reference</h1>
|
||||
<p class="doc-subtitle">ChronoSeal exposes a lightweight HTTP API for session handshakes, heartbeat attestation verification, metrics, and statistics.</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Endpoint Summary</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Method</th>
|
||||
<th>Path</th>
|
||||
<th>Core Purpose</th>
|
||||
<th>Content Type</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>POST</code></td>
|
||||
<td><code>/init</code></td>
|
||||
<td>Create a new attestation session</td>
|
||||
<td><code>application/json</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>POST</code></td>
|
||||
<td><code>/hb</code></td>
|
||||
<td>Submit and verify a signed heartbeat</td>
|
||||
<td><code>application/json</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>GET</code></td>
|
||||
<td><code>/health</code></td>
|
||||
<td>Check daemon operational health</td>
|
||||
<td><code>application/json</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>GET</code></td>
|
||||
<td><code>/stats</code></td>
|
||||
<td>Get runtime database statistics</td>
|
||||
<td><code>application/json</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>GET</code></td>
|
||||
<td><code>/metrics</code></td>
|
||||
<td>Prometheus-compatible scraping metrics</td>
|
||||
<td><code>text/plain</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>GET</code></td>
|
||||
<td><code>/</code></td>
|
||||
<td>Serve static integration files</td>
|
||||
<td>HTML / JS / WASM</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2>Data Encoding Formats</h2>
|
||||
<ul>
|
||||
<li><strong>Keys & Signatures:</strong> Ed25519 public keys represent 64 hex characters (32 bytes). Signatures represent 128 hex characters (64 bytes).</li>
|
||||
<li><strong>Hashes:</strong> Blake3 digests represent 64 hex characters (32 bytes).</li>
|
||||
<li><strong>Salts:</strong> Random seeds represented as 32 hex characters (16 bytes).</li>
|
||||
<li><strong>Timestamps:</strong> Integer epoch milliseconds.</li>
|
||||
<li><strong>Programs:</strong> Base64-encoded strings (representing VM opcodes or mutation steps).</li>
|
||||
</ul>
|
||||
|
||||
<h2><code>POST /init</code></h2>
|
||||
<p>Registers the browser public key and initiates the session tracker.</p>
|
||||
|
||||
<h3>Request Payload</h3>
|
||||
<div class="code-block">
|
||||
<div class="code-header">
|
||||
<span class="code-lang">JSON Request</span>
|
||||
<button class="code-copy-btn">Copy</button>
|
||||
</div>
|
||||
<pre><code>{
|
||||
"public_key": "24a1b0cd982fecba45...64 hex chars"
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Successful Response (200 OK)</h3>
|
||||
<div class="code-block">
|
||||
<div class="code-header">
|
||||
<span class="code-lang">JSON Response</span>
|
||||
<button class="code-copy-btn">Copy</button>
|
||||
</div>
|
||||
<pre><code>{
|
||||
"session_id": "8902abc345def678...64 hex chars",
|
||||
"salt": "f51278ba...32 hex chars",
|
||||
"opcodes_b64": "AQAFAwEG...",
|
||||
"initial_hash": "23ab89c0...64 hex chars",
|
||||
"expires_at": 1782390482000,
|
||||
"heartbeat_min_interval_ms": 12000,
|
||||
"heartbeat_max_interval_ms": 25000,
|
||||
"gene_size": 512,
|
||||
"mutation_step": 1,
|
||||
"mutation_order_b64": "YWJjZGVm..."
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<h2><code>POST /hb</code></h2>
|
||||
<p>Verifies client compliance for the current step and rolls over session parameters.</p>
|
||||
|
||||
<h3>Request Payload</h3>
|
||||
<div class="code-block">
|
||||
<div class="code-header">
|
||||
<span class="code-lang">JSON Request</span>
|
||||
<button class="code-copy-btn">Copy</button>
|
||||
</div>
|
||||
<pre><code>{
|
||||
"session_id": "8902abc345def678...64 hex chars",
|
||||
"prev_hash": "23ab89c0...64 hex chars",
|
||||
"timestamp": 1782390494000,
|
||||
"entropy_data": {
|
||||
"events": [
|
||||
{ "x": 124.5, "y": 308.2, "t": 120.4 }
|
||||
]
|
||||
},
|
||||
"stack_state": {
|
||||
"stack": [108429, 3902],
|
||||
"ip": 12
|
||||
},
|
||||
"fingerprint": {
|
||||
"aspectRatio": "1.7777777778",
|
||||
"devicePixelRatio": "2",
|
||||
"hardwareConcurrency": 8
|
||||
},
|
||||
"mutation_step": 1,
|
||||
"gene_commitment": "56ab12cd...64 hex chars",
|
||||
"signature": "ab0921cd56ef...128 hex chars"
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Accepted Response</h3>
|
||||
<div class="code-block">
|
||||
<div class="code-header">
|
||||
<span class="code-lang">JSON Response</span>
|
||||
<button class="code-copy-btn">Copy</button>
|
||||
</div>
|
||||
<pre><code>{
|
||||
"status": "ok",
|
||||
"next_salt": "78abef90...32 hex chars",
|
||||
"next_mutation_step": 2,
|
||||
"next_mutation_order_b64": "cGFzc3dvcmQ..."
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Rejected Response (Silent Rejection)</h3>
|
||||
<div class="code-block">
|
||||
<div class="code-header">
|
||||
<span class="code-lang">JSON Response</span>
|
||||
<button class="code-copy-btn">Copy</button>
|
||||
</div>
|
||||
<pre><code>{
|
||||
"status": "ok"
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="doc-alert doc-alert--warn">
|
||||
<div class="doc-alert-icon">⚠️</div>
|
||||
<div class="doc-alert-body">
|
||||
<strong>Important:</strong> Heartbeat rejections return <code>200 OK</code> with <code>status: ok</code> but OMIT next-state fields. Clients must check for the presence of <code>next_salt</code> before advancing local states.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Canonical Signing Payload</h2>
|
||||
<p>The Ed25519 signature covers a canonical JSON string. The server orders the keys alphabetically using camelCase notation. Ensure serialization matches this format precisely:</p>
|
||||
<div class="code-block">
|
||||
<div class="code-header">
|
||||
<span class="code-lang">JSON Payload</span>
|
||||
<button class="code-copy-btn">Copy</button>
|
||||
</div>
|
||||
<pre><code>{
|
||||
"entropyData": { "events": [{ "t": 120.4, "x": 124.5, "y": 308.2 }] },
|
||||
"fingerprint": { "aspectRatio": "1.7777777778", "devicePixelRatio": "2", "hardwareConcurrency": 8 },
|
||||
"geneCommitment": "56ab12cd...",
|
||||
"mutationStep": 1,
|
||||
"prevHash": "23ab89c0...",
|
||||
"sessionId": "8902abc3...",
|
||||
"stackState": { "ip": 12, "stack": [108429, 3902] },
|
||||
"timestamp": 1782390494000
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<h2>Operational Probes</h2>
|
||||
|
||||
<h3><code>GET /health</code></h3>
|
||||
<div class="code-block">
|
||||
<pre><code>{
|
||||
"status": "healthy"
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3><code>GET /stats</code></h3>
|
||||
<div class="code-block">
|
||||
<pre><code>{
|
||||
"sessions": 412,
|
||||
"expired_sessions": 3,
|
||||
"max_chain_length": 84
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3><code>GET /metrics</code></h3>
|
||||
<div class="code-block">
|
||||
<pre><code># HELP chronoseal_sessions Active ChronoSeal sessions
|
||||
# TYPE chronoseal_sessions gauge
|
||||
chronoseal_sessions 412
|
||||
# HELP chronoseal_expired_sessions Expired sessions not yet removed
|
||||
# TYPE chronoseal_expired_sessions gauge
|
||||
chronoseal_expired_sessions 3
|
||||
# HELP chronoseal_max_chain_length Maximum heartbeat chain length
|
||||
# TYPE chronoseal_max_chain_length gauge
|
||||
chronoseal_max_chain_length 84</code></pre>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<!-- Pager -->
|
||||
<nav class="doc-pager" aria-label="Doc navigation Pager">
|
||||
<a href="protocol.html" class="doc-pager-link">
|
||||
<div class="doc-pager-label">Previous</div>
|
||||
<div class="doc-pager-title">Protocol Specification</div>
|
||||
</a>
|
||||
<a href="threat-model.html" class="doc-pager-link doc-pager-link--next">
|
||||
<div class="doc-pager-label">Next</div>
|
||||
<div class="doc-pager-title">Threat Model</div>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<div class="doc-meta">
|
||||
Last Updated: June 2026 (v1.0.2)
|
||||
</div>
|
||||
</main>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="footer">
|
||||
<div class="footer-content">
|
||||
<div class="footer-section">
|
||||
<h4>
|
||||
<img class="logobar" src="assets/logo.svg" alt="ChronoSeal Logo" style="width: 24px; height: 24px; vertical-align: middle;"> ChronoSeal
|
||||
</h4>
|
||||
<p>Next-generation browser attestation daemon built for the modern web.</p>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h4>Resources</h4>
|
||||
<a href="philosophy.html">Design Philosophy</a>
|
||||
<a href="architecture.html">Architecture</a>
|
||||
<a href="protocol.html">Protocol</a>
|
||||
<a href="api.html">API Reference</a>
|
||||
<a href="comparison.html">Comparison</a>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h4>Community</h4>
|
||||
<a href="https://github.com/thakares/chronoseal-rs" target="_blank" rel="noopener"><i class="fab fa-github"></i> GitHub</a>
|
||||
<a href="deployment.html"><i class="fas fa-rocket"></i> Deployment</a>
|
||||
<a href="testing.html"><i class="fas fa-vial"></i> Testing</a>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h4>Legal & Security</h4>
|
||||
<a href="security.html">Security Policy</a>
|
||||
<a href="privacy.html">Privacy Policy</a>
|
||||
<a href="performance.html">Performance Tuning</a>
|
||||
<a href="operations.html">Operations Guide</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<p>© 2026 ChronoSeal Project. Built with 🦀 Rust and ❤️ for open source.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Search Overlay -->
|
||||
<div class="search-overlay">
|
||||
<div class="search-box">
|
||||
<div class="search-input-wrap">
|
||||
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
|
||||
<input type="text" class="search-input" placeholder="Search documentation... (Esc to close)">
|
||||
<span class="search-kbd">/</span>
|
||||
</div>
|
||||
<div class="search-results"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Back to top -->
|
||||
<a href="#" class="back-to-top" aria-label="Back to top">▲</a>
|
||||
|
||||
<!-- JS Scripts -->
|
||||
<script src="js/search.js"></script>
|
||||
<script src="js/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,300 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
||||
<title>Architecture Overview | ChronoSeal Documentation</title>
|
||||
<meta name="description" content="ChronoSeal system architecture, state models, components, validation pipelines, and trust boundaries.">
|
||||
<link rel="stylesheet" href="css/chronoseal.css">
|
||||
<link rel="stylesheet" href="css/docs.css">
|
||||
<link rel="stylesheet" href="css/print.css" media="print">
|
||||
<link rel="manifest" href="site.webmanifest">
|
||||
<link rel="icon" href="assets/logo.svg" type="image/svg+xml">
|
||||
|
||||
<!-- Font Awesome for Icons -->
|
||||
<link rel="preconnect" href="https://cdnjs.cloudflare.com" crossorigin />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
|
||||
integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw=="
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Floating Background Spheres -->
|
||||
<div class="bg-animation">
|
||||
<div class="gradient-sphere sphere-1"></div>
|
||||
<div class="gradient-sphere sphere-2"></div>
|
||||
<div class="gradient-sphere sphere-3"></div>
|
||||
</div>
|
||||
|
||||
<!-- Header / Navbar -->
|
||||
<nav class="navbar" id="navbar">
|
||||
<div class="nav-container">
|
||||
<a href="index.html" class="logo">
|
||||
<img class="logobar" src="assets/logo.svg" alt="ChronoSeal Logo">
|
||||
<span>ChronoSeal</span>
|
||||
</a>
|
||||
<div class="nav-links" id="navLinks">
|
||||
<a href="philosophy.html">Philosophy</a>
|
||||
<a href="architecture.html" class="active">Architecture</a>
|
||||
<a href="protocol.html">Protocol</a>
|
||||
<a href="comparison.html">Comparison</a>
|
||||
<div class="nav-dropdown">
|
||||
<a href="#" class="nav-link">More <small>▼</small></a>
|
||||
<div class="nav-dropdown-menu">
|
||||
<a href="api.html">API Reference</a>
|
||||
<a href="deployment.html">Deployment Guide</a>
|
||||
<a href="operations.html">Operations Guide</a>
|
||||
<a href="testing.html">Testing Strategy</a>
|
||||
<a href="performance.html">Performance Tuning</a>
|
||||
<a href="threat-model.html">Threat Model</a>
|
||||
<a href="security.html">Security Policy</a>
|
||||
<a href="privacy.html">Privacy Policy</a>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-ghost btn-sm btn-icon search-trigger" aria-label="Search" onclick="ChronoSearch.open()">
|
||||
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" width="16" height="16"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
|
||||
</button>
|
||||
<a href="https://github.com/thakares/chronoseal-rs" class="github-btn" target="_blank" rel="noopener">
|
||||
<i class="fab fa-github"></i> GitHub
|
||||
</a>
|
||||
</div>
|
||||
<button class="nav-toggle" id="menuBtn" aria-label="Toggle Navigation">
|
||||
<i class="fas fa-bars"></i>
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Doc Page Shell -->
|
||||
<div class="doc-page">
|
||||
<div class="doc-layout">
|
||||
|
||||
<!-- Sidebar -->
|
||||
<aside class="doc-sidebar">
|
||||
<button class="doc-sidebar-toggle">
|
||||
<i class="fas fa-bars"></i> Sidebar Menu
|
||||
</button>
|
||||
<div class="doc-nav-group">
|
||||
<div class="doc-nav-label">Core Concepts</div>
|
||||
<a href="philosophy.html" class="doc-nav-item">Design Philosophy</a>
|
||||
<a href="architecture.html" class="doc-nav-item active">Architecture Overview</a>
|
||||
<a href="comparison.html" class="doc-nav-item">ChronoSeal vs Others</a>
|
||||
</div>
|
||||
<div class="doc-nav-group">
|
||||
<div class="doc-nav-label">Protocol & API</div>
|
||||
<a href="protocol.html" class="doc-nav-item">Protocol Specification</a>
|
||||
<a href="api.html" class="doc-nav-item">API Reference</a>
|
||||
</div>
|
||||
<div class="doc-nav-group">
|
||||
<div class="doc-nav-label">Operations & Security</div>
|
||||
<a href="threat-model.html" class="doc-nav-item">Threat Model</a>
|
||||
<a href="security.html" class="doc-nav-item">Security Policy</a>
|
||||
<a href="privacy.html" class="doc-nav-item">Privacy Policy</a>
|
||||
<a href="performance.html" class="doc-nav-item">Performance Tuning</a>
|
||||
</div>
|
||||
<div class="doc-nav-group">
|
||||
<div class="doc-nav-label">Lifecycle & Dev</div>
|
||||
<a href="deployment.html" class="doc-nav-item">Deployment Guide</a>
|
||||
<a href="operations.html" class="doc-nav-item">Operations Handbook</a>
|
||||
<a href="testing.html" class="doc-nav-item">Testing Strategy</a>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content Area -->
|
||||
<main class="doc-main">
|
||||
<nav class="doc-breadcrumb" aria-label="Breadcrumb">
|
||||
<a href="index.html">Home</a>
|
||||
<span class="sep">/</span>
|
||||
<a href="#">Core Concepts</a>
|
||||
<span class="sep">/</span>
|
||||
<span>Architecture Overview</span>
|
||||
</nav>
|
||||
|
||||
<article class="doc-content">
|
||||
<h1>ChronoSeal Architecture</h1>
|
||||
<p class="doc-subtitle">ChronoSeal is a Unix-native browser attestation daemon. It validates session continuity by combining cryptographic signatures, hash-chain progression, deterministic VM execution, and a shared Synthetic Gene Mutation Engine.</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>System Diagram</h2>
|
||||
<p>The following diagram shows the relationship between browser clients, the daemon process, and the shared protocol library:</p>
|
||||
|
||||
<!-- Inline Architecture Diagram Container -->
|
||||
<div id="diagram-architecture" class="arch-diagram" style="margin-top:24px"></div>
|
||||
|
||||
<h2>Workspace Components</h2>
|
||||
<p>The codebase is structured as a Rust workspace containing three runtime crates and static frontend assets:</p>
|
||||
|
||||
<h3>1. <code>shared/</code> Crate</h3>
|
||||
<p>The <strong>determinism boundary</strong> of ChronoSeal. Any execution logic that must agree precisely between the browser WASM runtime and server verification belongs here. Its responsibilities include:</p>
|
||||
<ul>
|
||||
<li>Wire protocol struct formats for request and response models.</li>
|
||||
<li>Blake3 hash-chain progression functions.</li>
|
||||
<li>Synthetic gene model definitions and commitments.</li>
|
||||
<li>Mutation program bytecode generator, interpreter, and execution tracer.</li>
|
||||
</ul>
|
||||
|
||||
<h3>2. <code>server/</code> Crate</h3>
|
||||
<p>Compiles into the <code>chronoseal</code> daemon binary. It manages the server runtime, HTTP API routes, database backends, and verification logic. Key responsibilities:</p>
|
||||
<ul>
|
||||
<li>CLI command parsing and flag defaults.</li>
|
||||
<li>Axum-based web router and health endpoints.</li>
|
||||
<li>Verification pipeline (signature verification, timestamp drift checks, rate limit validations).</li>
|
||||
<li>Pluggable storage adapters (SQLite memory/disk, Valkey).</li>
|
||||
<li>Background cleanup loop for session purges.</li>
|
||||
</ul>
|
||||
|
||||
<h3>3. <code>wasm/</code> Crate</h3>
|
||||
<p>Compiles into the browser WebAssembly package (using <code>wasm-pack</code>) used by the frontend. Its key functions include:</p>
|
||||
<ul>
|
||||
<li>Secure client-side Ed25519 keypair generation and verification.</li>
|
||||
<li>Message signing for canonical heartbeat payloads.</li>
|
||||
<li>Client-side VM program execution and stack history logging.</li>
|
||||
<li>Previewing, committing, and discarding synthetic gene mutations.</li>
|
||||
</ul>
|
||||
|
||||
<h3>4. <code>frontend/</code> Directory</h3>
|
||||
<p>Contains static JavaScript (<code>heartbeat.js</code>, <code>app.js</code>) and assets served to browsers to orchestrate background attestation calls without blocking UI rendering.</p>
|
||||
|
||||
<h2>Session State Model</h2>
|
||||
<p>The daemon stores a single <code>SessionRecord</code> in the active database per session, structured as follows:</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Core Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>session_id</code></td>
|
||||
<td>Random 32-byte session lookup key.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>public_key</code></td>
|
||||
<td>Registered Ed25519 public key. Used to verify all heartbeats.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>salt</code></td>
|
||||
<td>Current server salt required to verify the next heartbeat.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>last_hash</code></td>
|
||||
<td>Current accepted Blake3 hash head. Prevents out-of-order repeats.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>gene</code></td>
|
||||
<td>Committed synthetic gene byte buffer.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>pending_mutation</code></td>
|
||||
<td>The mutation program compiled by the server for the next heartbeat step.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>pending_mutation_step</code></td>
|
||||
<td>Mismatches between this and request steps cause silent rejection.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="doc-alert doc-alert--warn">
|
||||
<div class="doc-alert-icon">⚠️</div>
|
||||
<div class="doc-alert-body">
|
||||
<strong>State Invariant:</strong> The server-side session state advances ONLY after a heartbeat passes all pipeline validations. Failed heartbeats never alter the stored salt, hash, or gene parameters, preventing desynchronization exploits.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Verification Pipeline</h2>
|
||||
<p>Heartbeat verification follows a strict chronological order. If any check fails, execution immediately halts, returning the silent rejection response. The pipeline is:</p>
|
||||
<ol>
|
||||
<li>Load the session record using the submitted <code>session_id</code>.</li>
|
||||
<li>Assert that the session exists and has not expired (<code>now < expires_at</code>).</li>
|
||||
<li>Verify the Ed25519 signature against the reconstructed canonical JSON payload.</li>
|
||||
<li>Assert the request's <code>prev_hash</code> matches the server-stored <code>last_hash</code>.</li>
|
||||
<li>Compare request step with <code>pending_mutation_step</code>.</li>
|
||||
<li>Apply the stored <code>pending_mutation</code> to a cloned gene buffer.</li>
|
||||
<li>Assert the computed gene commitment matches the request's <code>gene_commitment</code>.</li>
|
||||
<li>Assert that the timestamp drift matches liveness bounds (within 30 seconds).</li>
|
||||
<li>Assert that mouse activity entropy is present and speed falls within thresholds.</li>
|
||||
<li>Assert screen aspect ratio, device pixel ratio, and concurrency parameters match bounds.</li>
|
||||
<li>Advance the session's hash head, rotate the salt, compile the next mutation program, and persist.</li>
|
||||
</ol>
|
||||
</article>
|
||||
|
||||
<!-- Pager -->
|
||||
<nav class="doc-pager" aria-label="Doc navigation Pager">
|
||||
<a href="comparison.html" class="doc-pager-link">
|
||||
<div class="doc-pager-label">Previous</div>
|
||||
<div class="doc-pager-title">ChronoSeal vs Others</div>
|
||||
</a>
|
||||
<a href="protocol.html" class="doc-pager-link doc-pager-link--next">
|
||||
<div class="doc-pager-label">Next</div>
|
||||
<div class="doc-pager-title">Protocol Specification</div>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<div class="doc-meta">
|
||||
Last Updated: June 2026 (v1.0.2)
|
||||
</div>
|
||||
</main>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="footer">
|
||||
<div class="footer-content">
|
||||
<div class="footer-section">
|
||||
<h4>
|
||||
<img class="logobar" src="assets/logo.svg" alt="ChronoSeal Logo" style="width: 24px; height: 24px; vertical-align: middle;"> ChronoSeal
|
||||
</h4>
|
||||
<p>Next-generation browser attestation daemon built for the modern web.</p>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h4>Resources</h4>
|
||||
<a href="philosophy.html">Design Philosophy</a>
|
||||
<a href="architecture.html">Architecture</a>
|
||||
<a href="protocol.html">Protocol</a>
|
||||
<a href="api.html">API Reference</a>
|
||||
<a href="comparison.html">Comparison</a>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h4>Community</h4>
|
||||
<a href="https://github.com/thakares/chronoseal-rs" target="_blank" rel="noopener"><i class="fab fa-github"></i> GitHub</a>
|
||||
<a href="deployment.html"><i class="fas fa-rocket"></i> Deployment</a>
|
||||
<a href="testing.html"><i class="fas fa-vial"></i> Testing</a>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h4>Legal & Security</h4>
|
||||
<a href="security.html">Security Policy</a>
|
||||
<a href="privacy.html">Privacy Policy</a>
|
||||
<a href="performance.html">Performance Tuning</a>
|
||||
<a href="operations.html">Operations Guide</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<p>© 2026 ChronoSeal Project. Built with 🦀 Rust and ❤️ for open source.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Search Overlay -->
|
||||
<div class="search-overlay">
|
||||
<div class="search-box">
|
||||
<div class="search-input-wrap">
|
||||
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
|
||||
<input type="text" class="search-input" placeholder="Search documentation... (Esc to close)">
|
||||
<span class="search-kbd">/</span>
|
||||
</div>
|
||||
<div class="search-results"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Back to top -->
|
||||
<a href="#" class="back-to-top" aria-label="Back to top">▲</a>
|
||||
|
||||
<!-- JS Scripts -->
|
||||
<script src="js/search.js"></script>
|
||||
<script src="js/app.js"></script>
|
||||
<script src="js/diagrams.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 168 KiB |
@@ -0,0 +1,127 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.1"
|
||||
id="Layer_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="296.99997mm"
|
||||
viewBox="0 0 1122.5196 793.7008"
|
||||
enable-background="new 0 0 1254 1254"
|
||||
xml:space="preserve"
|
||||
height="210mm"
|
||||
sodipodi:docname="chronoseal.svg"
|
||||
inkscape:export-filename="logo1.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96"
|
||||
inkscape:version="1.4.4 (dcaf3e7d9e, 2026-05-05)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:zoom="1.1654266"
|
||||
inkscape:cx="561.16791"
|
||||
inkscape:cy="396.85039"
|
||||
inkscape:window-width="2048"
|
||||
inkscape:window-height="1205"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="Layer_1" /><defs
|
||||
id="defs44" />
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<path
|
||||
fill="none"
|
||||
opacity="0"
|
||||
stroke="none"
|
||||
d="m 791.89377,632.01366 c 1.04068,-1.03091 1.90903,-2.38956 3.14896,-3.04166 11.48425,-6.03955 22.98721,-12.04745 34.57742,-17.88034 8.52002,-4.28775 14.37558,-10.49713 14.23218,-20.46771 -0.14127,-9.82202 -6.25309,-15.51495 -14.66742,-19.5827 -61.76651,-29.85993 -123.47361,-59.84277 -185.21183,-89.76129 -10.48242,-5.07981 -20.81335,-10.5555 -31.58477,-14.93764 -19.71186,-8.01936 -39.87799,-8.40881 -59.18305,0.84148 -71.77527,34.39209 -143.33667,69.23047 -215.00567,103.84463 -8.40815,4.06091 -14.32166,9.84101 -14.39978,19.74008 -0.079,10.00498 5.52282,16.22797 14.08978,20.54743 12.3277,6.21567 24.51239,12.71484 37.34228,19.41369 7.51429,3.93381 14.42273,7.5896 21.37854,11.15274 31.68775,16.23208 63.40558,32.40548 95.07782,48.66778 14.943,7.6726 29.74964,15.6123 44.73053,23.2091 8.44464,4.2823 16.79547,9.1534 25.75525,11.9324 18.81989,5.8372 37.10712,3.9335 54.83185,-5.5578 25.51599,-13.6634 51.41541,-26.6141 77.2135,-39.7465 30.82071,-15.6894 61.7009,-31.26196 92.58359,-46.82907 1.56006,-0.78641 3.38776,-1.0419 5.09082,-1.54462 z"
|
||||
id="path2" /><path
|
||||
fill="#4b4d51"
|
||||
opacity="1"
|
||||
stroke="none"
|
||||
d="m 791.89376,550.60089 c 1.04068,-1.03091 1.90903,-2.38956 3.14896,-3.04166 11.48425,-6.03955 22.98721,-12.04745 34.57742,-17.88034 8.52002,-4.28775 14.37558,-10.49713 14.23218,-20.46771 -0.14127,-9.82202 -6.25309,-15.51495 -14.66742,-19.5827 -61.76651,-29.85993 -123.47361,-59.84277 -185.21183,-89.76129 -10.48242,-5.07981 -20.81335,-10.5555 -31.58477,-14.93764 -19.71186,-8.01936 -39.87799,-8.40881 -59.18305,0.84148 -71.77522,34.39209 -143.33662,69.23047 -215.00562,103.84463 -8.4082,4.06091 -14.3217,9.84101 -14.3998,19.74008 -0.079,10.00498 5.5228,16.22797 14.0898,20.54743 12.3277,6.21567 24.5123,12.71484 37.3422,19.41369 7.5143,3.93381 14.4228,7.5896 21.3786,11.15274 31.6877,16.23212 63.4056,32.40552 95.0778,48.66782 14.943,7.67261 29.7496,15.61227 44.73052,23.20911 8.44464,4.28232 16.79547,9.15341 25.75525,11.93237 18.81989,5.83722 37.10712,3.93353 54.83185,-5.55777 25.51599,-13.66336 51.41541,-26.61407 77.2135,-39.74655 30.82071,-15.68933 61.7009,-31.26196 92.58359,-46.82907 1.56006,-0.78641 3.38776,-1.0419 5.09082,-1.54462 z"
|
||||
id="path45"
|
||||
style="fill:#e1e1e4;fill-opacity:1" /><path
|
||||
fill="#4b4d51"
|
||||
opacity="1"
|
||||
stroke="none"
|
||||
d="m 791.89377,469.18814 c 1.04068,-1.03091 1.90903,-2.38956 3.14896,-3.04166 11.48425,-6.03955 22.98721,-12.04745 34.57742,-17.88034 8.52002,-4.28775 14.37558,-10.49713 14.23218,-20.46771 -0.14127,-9.82202 -6.25309,-15.51495 -14.66742,-19.5827 C 767.4184,378.3558 705.7113,348.37296 643.97308,318.45444 c -10.48242,-5.07981 -20.81335,-10.5555 -31.58477,-14.93764 -19.71186,-8.01936 -39.87799,-8.40881 -59.18305,0.84148 -71.77527,34.39209 -143.33667,69.23047 -215.00567,103.84463 -8.40815,4.06091 -14.32166,9.84101 -14.39978,19.74008 -0.079,10.00498 5.52282,16.22797 14.08978,20.54743 12.3277,6.21567 24.51239,12.71484 37.34228,19.41369 7.51429,3.93381 14.42273,7.5896 21.37854,11.15274 31.68775,16.23212 63.40558,32.40552 95.07782,48.66782 14.943,7.67261 29.74964,15.61227 44.73053,23.20911 8.44464,4.28232 16.79547,9.15341 25.75525,11.93237 18.81989,5.83722 37.10712,3.93353 54.83185,-5.55777 25.51599,-13.66336 51.41541,-26.61407 77.2135,-39.74655 30.82071,-15.68933 61.7009,-31.26196 92.58359,-46.82907 1.56006,-0.78641 3.38776,-1.0419 5.09082,-1.54462 z"
|
||||
id="path46"
|
||||
style="fill:#b0b2b8;fill-opacity:1" /><path
|
||||
fill="#4b4d51"
|
||||
opacity="1"
|
||||
stroke="none"
|
||||
d="m 791.89377,387.77539 c 1.04068,-1.03091 1.90903,-2.38956 3.14896,-3.04166 11.48425,-6.03955 22.98721,-12.04745 34.57742,-17.88034 8.52002,-4.28775 14.37558,-10.49713 14.23218,-20.46771 -0.14127,-9.82202 -6.25309,-15.51495 -14.66742,-19.5827 -61.76651,-29.85993 -123.47361,-59.84277 -185.21183,-89.76129 -10.48242,-5.07981 -20.81335,-10.5555 -31.58477,-14.93764 -19.71186,-8.01936 -39.87799,-8.40881 -59.18305,0.84148 -71.77527,34.39209 -143.33667,69.23047 -215.00567,103.84463 -8.40815,4.06091 -14.32166,9.84101 -14.39978,19.74008 -0.079,10.00498 5.52282,16.22797 14.08978,20.54743 12.3277,6.21567 24.51239,12.71484 37.34228,19.41369 7.51429,3.93381 14.42273,7.5896 21.37854,11.15274 31.68775,16.23212 63.40558,32.40552 95.07782,48.66782 14.943,7.67261 29.74964,15.61227 44.73053,23.20911 8.44464,4.28232 16.79547,9.15341 25.75525,11.93237 18.81989,5.83722 37.10712,3.93353 54.83185,-5.55777 25.51599,-13.66336 51.41541,-26.61407 77.2135,-39.74655 30.82071,-15.68933 61.7009,-31.26196 92.58359,-46.82907 1.56006,-0.78641 3.38776,-1.0419 5.09082,-1.54462 z"
|
||||
id="path47"
|
||||
style="fill:#7b7e85;fill-opacity:1" /><text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:160px;font-family:'URW Gothic';-inkscape-font-specification:'URW Gothic, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;writing-mode:lr-tb;direction:ltr;fill:#e1e1e4;fill-opacity:1;stroke-width:39.1"
|
||||
x="154.54701"
|
||||
y="550.13623"
|
||||
id="text47"><tspan
|
||||
id="tspan47"
|
||||
x="154.54701"
|
||||
y="550.13623" /><tspan
|
||||
id="tspan48"
|
||||
x="154.54701"
|
||||
y="750.13623"
|
||||
style="font-style:normal;font-variant:normal;font-weight:600;font-stretch:normal;font-size:160px;font-family:'URW Gothic';-inkscape-font-specification:'URW Gothic, Semi-Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:600;font-stretch:normal;font-size:160px;font-family:'URW Gothic';-inkscape-font-specification:'URW Gothic, Semi-Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1f2024;fill-opacity:1"
|
||||
id="tspan49">chrono</tspan>seal</tspan></text><path
|
||||
fill="#4b4d51"
|
||||
opacity="1"
|
||||
stroke="none"
|
||||
d="m 791.89376,306.36264 c 1.04068,-1.03091 1.90903,-2.38956 3.14896,-3.04166 11.48425,-6.03955 22.98721,-12.04745 34.57742,-17.88034 8.52002,-4.28775 14.37558,-10.49713 14.23218,-20.46771 -0.14127,-9.82202 -6.25309,-15.51495 -14.66742,-19.5827 -61.76651,-29.85993 -123.47361,-59.84277 -185.21183,-89.76129 -10.48242,-5.07981 -20.81335,-10.5555 -31.58477,-14.93764 -19.71186,-8.01936 -39.87799,-8.40881 -59.18305,0.84148 -71.77522,34.39209 -143.33662,69.23047 -215.00562,103.84463 -8.4082,4.06091 -14.3217,9.84101 -14.3998,19.74008 -0.079,10.00498 5.5228,16.22797 14.0898,20.54743 12.3277,6.21567 24.5123,12.71484 37.3422,19.41369 7.5143,3.93381 14.4228,7.5896 21.3786,11.15274 31.6877,16.23212 63.4056,32.40552 95.0778,48.66782 14.943,7.67261 29.7496,15.61227 44.73052,23.20911 8.44464,4.28232 16.79547,9.15341 25.75525,11.93237 18.81989,5.83722 37.10712,3.93353 54.83185,-5.55777 25.51599,-13.66336 51.41541,-26.61407 77.2135,-39.74655 30.82071,-15.68933 61.7009,-31.26196 92.58359,-46.82907 1.56006,-0.78641 3.38776,-1.0419 5.09082,-1.54462 z"
|
||||
id="path1"
|
||||
style="fill:#4a4d53;fill-opacity:1" /></svg>
|
||||
|
After Width: | Height: | Size: 8.1 KiB |
|
After Width: | Height: | Size: 192 KiB |
|
After Width: | Height: | Size: 168 KiB |
@@ -0,0 +1,127 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.1"
|
||||
id="Layer_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="296.99997mm"
|
||||
viewBox="0 0 1122.5196 793.7008"
|
||||
enable-background="new 0 0 1254 1254"
|
||||
xml:space="preserve"
|
||||
height="210mm"
|
||||
sodipodi:docname="chronoseal.svg"
|
||||
inkscape:export-filename="logo1.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96"
|
||||
inkscape:version="1.4.4 (dcaf3e7d9e, 2026-05-05)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:zoom="1.1654266"
|
||||
inkscape:cx="561.16791"
|
||||
inkscape:cy="396.85039"
|
||||
inkscape:window-width="2048"
|
||||
inkscape:window-height="1205"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="Layer_1" /><defs
|
||||
id="defs44" />
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<path
|
||||
fill="none"
|
||||
opacity="0"
|
||||
stroke="none"
|
||||
d="m 791.89377,632.01366 c 1.04068,-1.03091 1.90903,-2.38956 3.14896,-3.04166 11.48425,-6.03955 22.98721,-12.04745 34.57742,-17.88034 8.52002,-4.28775 14.37558,-10.49713 14.23218,-20.46771 -0.14127,-9.82202 -6.25309,-15.51495 -14.66742,-19.5827 -61.76651,-29.85993 -123.47361,-59.84277 -185.21183,-89.76129 -10.48242,-5.07981 -20.81335,-10.5555 -31.58477,-14.93764 -19.71186,-8.01936 -39.87799,-8.40881 -59.18305,0.84148 -71.77527,34.39209 -143.33667,69.23047 -215.00567,103.84463 -8.40815,4.06091 -14.32166,9.84101 -14.39978,19.74008 -0.079,10.00498 5.52282,16.22797 14.08978,20.54743 12.3277,6.21567 24.51239,12.71484 37.34228,19.41369 7.51429,3.93381 14.42273,7.5896 21.37854,11.15274 31.68775,16.23208 63.40558,32.40548 95.07782,48.66778 14.943,7.6726 29.74964,15.6123 44.73053,23.2091 8.44464,4.2823 16.79547,9.1534 25.75525,11.9324 18.81989,5.8372 37.10712,3.9335 54.83185,-5.5578 25.51599,-13.6634 51.41541,-26.6141 77.2135,-39.7465 30.82071,-15.6894 61.7009,-31.26196 92.58359,-46.82907 1.56006,-0.78641 3.38776,-1.0419 5.09082,-1.54462 z"
|
||||
id="path2" /><path
|
||||
fill="#4b4d51"
|
||||
opacity="1"
|
||||
stroke="none"
|
||||
d="m 791.89376,550.60089 c 1.04068,-1.03091 1.90903,-2.38956 3.14896,-3.04166 11.48425,-6.03955 22.98721,-12.04745 34.57742,-17.88034 8.52002,-4.28775 14.37558,-10.49713 14.23218,-20.46771 -0.14127,-9.82202 -6.25309,-15.51495 -14.66742,-19.5827 -61.76651,-29.85993 -123.47361,-59.84277 -185.21183,-89.76129 -10.48242,-5.07981 -20.81335,-10.5555 -31.58477,-14.93764 -19.71186,-8.01936 -39.87799,-8.40881 -59.18305,0.84148 -71.77522,34.39209 -143.33662,69.23047 -215.00562,103.84463 -8.4082,4.06091 -14.3217,9.84101 -14.3998,19.74008 -0.079,10.00498 5.5228,16.22797 14.0898,20.54743 12.3277,6.21567 24.5123,12.71484 37.3422,19.41369 7.5143,3.93381 14.4228,7.5896 21.3786,11.15274 31.6877,16.23212 63.4056,32.40552 95.0778,48.66782 14.943,7.67261 29.7496,15.61227 44.73052,23.20911 8.44464,4.28232 16.79547,9.15341 25.75525,11.93237 18.81989,5.83722 37.10712,3.93353 54.83185,-5.55777 25.51599,-13.66336 51.41541,-26.61407 77.2135,-39.74655 30.82071,-15.68933 61.7009,-31.26196 92.58359,-46.82907 1.56006,-0.78641 3.38776,-1.0419 5.09082,-1.54462 z"
|
||||
id="path45"
|
||||
style="fill:#e1e1e4;fill-opacity:1" /><path
|
||||
fill="#4b4d51"
|
||||
opacity="1"
|
||||
stroke="none"
|
||||
d="m 791.89377,469.18814 c 1.04068,-1.03091 1.90903,-2.38956 3.14896,-3.04166 11.48425,-6.03955 22.98721,-12.04745 34.57742,-17.88034 8.52002,-4.28775 14.37558,-10.49713 14.23218,-20.46771 -0.14127,-9.82202 -6.25309,-15.51495 -14.66742,-19.5827 C 767.4184,378.3558 705.7113,348.37296 643.97308,318.45444 c -10.48242,-5.07981 -20.81335,-10.5555 -31.58477,-14.93764 -19.71186,-8.01936 -39.87799,-8.40881 -59.18305,0.84148 -71.77527,34.39209 -143.33667,69.23047 -215.00567,103.84463 -8.40815,4.06091 -14.32166,9.84101 -14.39978,19.74008 -0.079,10.00498 5.52282,16.22797 14.08978,20.54743 12.3277,6.21567 24.51239,12.71484 37.34228,19.41369 7.51429,3.93381 14.42273,7.5896 21.37854,11.15274 31.68775,16.23212 63.40558,32.40552 95.07782,48.66782 14.943,7.67261 29.74964,15.61227 44.73053,23.20911 8.44464,4.28232 16.79547,9.15341 25.75525,11.93237 18.81989,5.83722 37.10712,3.93353 54.83185,-5.55777 25.51599,-13.66336 51.41541,-26.61407 77.2135,-39.74655 30.82071,-15.68933 61.7009,-31.26196 92.58359,-46.82907 1.56006,-0.78641 3.38776,-1.0419 5.09082,-1.54462 z"
|
||||
id="path46"
|
||||
style="fill:#b0b2b8;fill-opacity:1" /><path
|
||||
fill="#4b4d51"
|
||||
opacity="1"
|
||||
stroke="none"
|
||||
d="m 791.89377,387.77539 c 1.04068,-1.03091 1.90903,-2.38956 3.14896,-3.04166 11.48425,-6.03955 22.98721,-12.04745 34.57742,-17.88034 8.52002,-4.28775 14.37558,-10.49713 14.23218,-20.46771 -0.14127,-9.82202 -6.25309,-15.51495 -14.66742,-19.5827 -61.76651,-29.85993 -123.47361,-59.84277 -185.21183,-89.76129 -10.48242,-5.07981 -20.81335,-10.5555 -31.58477,-14.93764 -19.71186,-8.01936 -39.87799,-8.40881 -59.18305,0.84148 -71.77527,34.39209 -143.33667,69.23047 -215.00567,103.84463 -8.40815,4.06091 -14.32166,9.84101 -14.39978,19.74008 -0.079,10.00498 5.52282,16.22797 14.08978,20.54743 12.3277,6.21567 24.51239,12.71484 37.34228,19.41369 7.51429,3.93381 14.42273,7.5896 21.37854,11.15274 31.68775,16.23212 63.40558,32.40552 95.07782,48.66782 14.943,7.67261 29.74964,15.61227 44.73053,23.20911 8.44464,4.28232 16.79547,9.15341 25.75525,11.93237 18.81989,5.83722 37.10712,3.93353 54.83185,-5.55777 25.51599,-13.66336 51.41541,-26.61407 77.2135,-39.74655 30.82071,-15.68933 61.7009,-31.26196 92.58359,-46.82907 1.56006,-0.78641 3.38776,-1.0419 5.09082,-1.54462 z"
|
||||
id="path47"
|
||||
style="fill:#7b7e85;fill-opacity:1" /><text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:160px;font-family:'URW Gothic';-inkscape-font-specification:'URW Gothic, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;writing-mode:lr-tb;direction:ltr;fill:#e1e1e4;fill-opacity:1;stroke-width:39.1"
|
||||
x="154.54701"
|
||||
y="550.13623"
|
||||
id="text47"><tspan
|
||||
id="tspan47"
|
||||
x="154.54701"
|
||||
y="550.13623" /><tspan
|
||||
id="tspan48"
|
||||
x="154.54701"
|
||||
y="750.13623"
|
||||
style="font-style:normal;font-variant:normal;font-weight:600;font-stretch:normal;font-size:160px;font-family:'URW Gothic';-inkscape-font-specification:'URW Gothic, Semi-Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:600;font-stretch:normal;font-size:160px;font-family:'URW Gothic';-inkscape-font-specification:'URW Gothic, Semi-Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1f2024;fill-opacity:1"
|
||||
id="tspan49">chrono</tspan>seal</tspan></text><path
|
||||
fill="#4b4d51"
|
||||
opacity="1"
|
||||
stroke="none"
|
||||
d="m 791.89376,306.36264 c 1.04068,-1.03091 1.90903,-2.38956 3.14896,-3.04166 11.48425,-6.03955 22.98721,-12.04745 34.57742,-17.88034 8.52002,-4.28775 14.37558,-10.49713 14.23218,-20.46771 -0.14127,-9.82202 -6.25309,-15.51495 -14.66742,-19.5827 -61.76651,-29.85993 -123.47361,-59.84277 -185.21183,-89.76129 -10.48242,-5.07981 -20.81335,-10.5555 -31.58477,-14.93764 -19.71186,-8.01936 -39.87799,-8.40881 -59.18305,0.84148 -71.77522,34.39209 -143.33662,69.23047 -215.00562,103.84463 -8.4082,4.06091 -14.3217,9.84101 -14.3998,19.74008 -0.079,10.00498 5.5228,16.22797 14.0898,20.54743 12.3277,6.21567 24.5123,12.71484 37.3422,19.41369 7.5143,3.93381 14.4228,7.5896 21.3786,11.15274 31.6877,16.23212 63.4056,32.40552 95.0778,48.66782 14.943,7.67261 29.7496,15.61227 44.73052,23.20911 8.44464,4.28232 16.79547,9.15341 25.75525,11.93237 18.81989,5.83722 37.10712,3.93353 54.83185,-5.55777 25.51599,-13.66336 51.41541,-26.61407 77.2135,-39.74655 30.82071,-15.68933 61.7009,-31.26196 92.58359,-46.82907 1.56006,-0.78641 3.38776,-1.0419 5.09082,-1.54462 z"
|
||||
id="path1"
|
||||
style="fill:#4a4d53;fill-opacity:1" /></svg>
|
||||
|
After Width: | Height: | Size: 8.1 KiB |
@@ -0,0 +1,356 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
||||
<title>ChronoSeal vs Commercial Anti-Bot Systems | ChronoSeal Documentation</title>
|
||||
<meta name="description" content="Compare ChronoSeal honestly with commercial anti-bot Edge/SaaS platforms like Cloudflare, Akamai, PerimeterX, and reCAPTCHA.">
|
||||
<link rel="stylesheet" href="css/chronoseal.css">
|
||||
<link rel="stylesheet" href="css/docs.css">
|
||||
<link rel="stylesheet" href="css/print.css" media="print">
|
||||
<link rel="manifest" href="site.webmanifest">
|
||||
<link rel="icon" href="assets/logo.svg" type="image/svg+xml">
|
||||
|
||||
<!-- Font Awesome for Icons -->
|
||||
<link rel="preconnect" href="https://cdnjs.cloudflare.com" crossorigin />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
|
||||
integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw=="
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Floating Background Spheres -->
|
||||
<div class="bg-animation">
|
||||
<div class="gradient-sphere sphere-1"></div>
|
||||
<div class="gradient-sphere sphere-2"></div>
|
||||
<div class="gradient-sphere sphere-3"></div>
|
||||
</div>
|
||||
|
||||
<!-- Header / Navbar -->
|
||||
<nav class="navbar" id="navbar">
|
||||
<div class="nav-container">
|
||||
<a href="index.html" class="logo">
|
||||
<img class="logobar" src="assets/logo.svg" alt="ChronoSeal Logo">
|
||||
<span>ChronoSeal</span>
|
||||
</a>
|
||||
<div class="nav-links" id="navLinks">
|
||||
<a href="philosophy.html">Philosophy</a>
|
||||
<a href="architecture.html">Architecture</a>
|
||||
<a href="protocol.html">Protocol</a>
|
||||
<a href="comparison.html" class="active">Comparison</a>
|
||||
<div class="nav-dropdown">
|
||||
<a href="#" class="nav-link">More <small>▼</small></a>
|
||||
<div class="nav-dropdown-menu">
|
||||
<a href="api.html">API Reference</a>
|
||||
<a href="deployment.html">Deployment Guide</a>
|
||||
<a href="operations.html">Operations Guide</a>
|
||||
<a href="testing.html">Testing Strategy</a>
|
||||
<a href="performance.html">Performance Tuning</a>
|
||||
<a href="threat-model.html">Threat Model</a>
|
||||
<a href="security.html">Security Policy</a>
|
||||
<a href="privacy.html">Privacy Policy</a>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-ghost btn-sm btn-icon search-trigger" aria-label="Search" onclick="ChronoSearch.open()">
|
||||
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" width="16" height="16"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
|
||||
</button>
|
||||
<a href="https://github.com/thakares/chronoseal-rs" class="github-btn" target="_blank" rel="noopener">
|
||||
<i class="fab fa-github"></i> GitHub
|
||||
</a>
|
||||
</div>
|
||||
<button class="nav-toggle" id="menuBtn" aria-label="Toggle Navigation">
|
||||
<i class="fas fa-bars"></i>
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Doc Page Shell -->
|
||||
<div class="doc-page">
|
||||
<div class="doc-layout">
|
||||
|
||||
<!-- Sidebar -->
|
||||
<aside class="doc-sidebar">
|
||||
<button class="doc-sidebar-toggle">
|
||||
<i class="fas fa-bars"></i> Sidebar Menu
|
||||
</button>
|
||||
<div class="doc-nav-group">
|
||||
<div class="doc-nav-label">Core Concepts</div>
|
||||
<a href="philosophy.html" class="doc-nav-item">Design Philosophy</a>
|
||||
<a href="architecture.html" class="doc-nav-item">Architecture Overview</a>
|
||||
<a href="comparison.html" class="doc-nav-item active">ChronoSeal vs Others</a>
|
||||
</div>
|
||||
<div class="doc-nav-group">
|
||||
<div class="doc-nav-label">Protocol & API</div>
|
||||
<a href="protocol.html" class="doc-nav-item">Protocol Specification</a>
|
||||
<a href="api.html" class="doc-nav-item">API Reference</a>
|
||||
</div>
|
||||
<div class="doc-nav-group">
|
||||
<div class="doc-nav-label">Operations & Security</div>
|
||||
<a href="threat-model.html" class="doc-nav-item">Threat Model</a>
|
||||
<a href="security.html" class="doc-nav-item">Security Policy</a>
|
||||
<a href="privacy.html" class="doc-nav-item">Privacy Policy</a>
|
||||
<a href="performance.html" class="doc-nav-item">Performance Tuning</a>
|
||||
</div>
|
||||
<div class="doc-nav-group">
|
||||
<div class="doc-nav-label">Lifecycle & Dev</div>
|
||||
<a href="deployment.html" class="doc-nav-item">Deployment Guide</a>
|
||||
<a href="operations.html" class="doc-nav-item">Operations Handbook</a>
|
||||
<a href="testing.html" class="doc-nav-item">Testing Strategy</a>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content Area -->
|
||||
<main class="doc-main">
|
||||
<nav class="doc-breadcrumb" aria-label="Breadcrumb">
|
||||
<a href="index.html">Home</a>
|
||||
<span class="sep">/</span>
|
||||
<a href="#">Core Concepts</a>
|
||||
<span class="sep">/</span>
|
||||
<span>ChronoSeal vs Others</span>
|
||||
</nav>
|
||||
|
||||
<article class="doc-content">
|
||||
<h1>ChronoSeal vs Popular Anti-Bot Systems</h1>
|
||||
<p class="doc-subtitle">ChronoSeal is a self-hosted, cryptographic attestation daemon. This document compares it honestly with leading commercial solutions.</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Quick Comparison Matrix</h2>
|
||||
|
||||
<div class="comparison-table-wrap" style="margin-bottom: 2rem;">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Solution</th>
|
||||
<th>Type</th>
|
||||
<th>Core Method</th>
|
||||
<th>Privacy</th>
|
||||
<th>Self-Hosted</th>
|
||||
<th>Strength</th>
|
||||
<th>Behavioral</th>
|
||||
<th>Cost</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>ChronoSeal</strong></td>
|
||||
<td>Self-hosted Daemon</td>
|
||||
<td>Ed25519 + Gene Mutation</td>
|
||||
<td><span class="check"><i class="fas fa-check-circle"></i> Excellent</span></td>
|
||||
<td>Yes</td>
|
||||
<td>Very High</td>
|
||||
<td>Light + Tunable</td>
|
||||
<td><span class="check"><i class="fas fa-check-circle"></i> Free</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Cloudflare Bot Mgmt</td>
|
||||
<td>Cloud Edge</td>
|
||||
<td>Challenges + Fingerprint</td>
|
||||
<td>Medium</td>
|
||||
<td>No</td>
|
||||
<td>Medium</td>
|
||||
<td>Strong</td>
|
||||
<td>Freemium</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Akamai Bot Manager</td>
|
||||
<td>Enterprise Edge</td>
|
||||
<td>Fingerprinting + Heuristics</td>
|
||||
<td><span class="times"><i class="fas fa-times-circle"></i> Low</span></td>
|
||||
<td>Hybrid</td>
|
||||
<td>Medium</td>
|
||||
<td>Very Strong</td>
|
||||
<td>Very High</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HUMAN (PerimeterX)</td>
|
||||
<td>Cloud SaaS</td>
|
||||
<td>Behavioral Biometrics + ML</td>
|
||||
<td><span class="times"><i class="fas fa-times-circle"></i> Low</span></td>
|
||||
<td>No</td>
|
||||
<td>Medium</td>
|
||||
<td>Very Strong</td>
|
||||
<td>Enterprise</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>DataDome</td>
|
||||
<td>Cloud SaaS</td>
|
||||
<td>Real-time ML scoring</td>
|
||||
<td>Medium</td>
|
||||
<td>No</td>
|
||||
<td>Medium</td>
|
||||
<td>Strong</td>
|
||||
<td>Enterprise</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>reCAPTCHA v3</td>
|
||||
<td>Google Service</td>
|
||||
<td>Invisible risk challenges</td>
|
||||
<td><span class="times"><i class="fas fa-times-circle"></i> Poor</span></td>
|
||||
<td>No</td>
|
||||
<td>Low</td>
|
||||
<td>Medium</td>
|
||||
<td>Free → Paid</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Kasada</td>
|
||||
<td>Cloud SaaS</td>
|
||||
<td>Proof-of-Work + Obfuscation</td>
|
||||
<td>Medium</td>
|
||||
<td>No</td>
|
||||
<td>High</td>
|
||||
<td>Strong</td>
|
||||
<td>Enterprise</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2>Detailed Analysis</h2>
|
||||
|
||||
<h3>1. ChronoSeal (v1.0.2)</h3>
|
||||
<p><strong>Strengths:</strong></p>
|
||||
<ul>
|
||||
<li>Strongest cryptographic foundation (Ed25519 signatures + Blake3 hash chain + Synthetic Gene Mutation Engine).</li>
|
||||
<li>Fully deterministic server ↔ WASM execution agreement.</li>
|
||||
<li>Completely invisible to users with silent rejection mechanics.</li>
|
||||
<li>Excellent privacy posture — no third-party tracking, profiling, or persistent databases.</li>
|
||||
<li>Tunable mutation complexity parameters (<code>gene_size</code> and <code>mutation_rounds</code>).</li>
|
||||
<li>100% control, auditability, and local operations.</li>
|
||||
</ul>
|
||||
<p><strong>Weaknesses:</strong></p>
|
||||
<ul>
|
||||
<li>Requires self-hosting, configuration management, and server capacity.</li>
|
||||
<li>No global threat intelligence network or shared IP reputation lists.</li>
|
||||
</ul>
|
||||
|
||||
<h3>2. Cloudflare Bot Management</h3>
|
||||
<p><strong>Strengths:</strong></p>
|
||||
<ul>
|
||||
<li>Extremely easy to deploy for domains already routed through Cloudflare.</li>
|
||||
<li>Excellent scale and global threat reputation database.</li>
|
||||
</ul>
|
||||
<p><strong>Weaknesses vs ChronoSeal:</strong></p>
|
||||
<ul>
|
||||
<li>Relies heavily on browser fingerprint heuristics and invasive JS challenges.</li>
|
||||
<li>Sends visitor metadata to Cloudflare (privacy impact).</li>
|
||||
<li>Vendor lock-in and zero visibility into decision algorithms.</li>
|
||||
</ul>
|
||||
|
||||
<h3>3. Enterprise Solutions (Akamai, HUMAN, DataDome, Kasada)</h3>
|
||||
<p><strong>Strengths:</strong></p>
|
||||
<ul>
|
||||
<li>Sophisticated machine learning modeling of biometrics and timing.</li>
|
||||
<li>Professional support, operational SLAs, and security response teams.</li>
|
||||
</ul>
|
||||
<p><strong>Weaknesses vs ChronoSeal:</strong></p>
|
||||
<ul>
|
||||
<li>Extremely expensive enterprise licensing models.</li>
|
||||
<li>Black-box systems with limited logging and transparency.</li>
|
||||
<li>Heavy user data collection, causing privacy and compliance overhead.</li>
|
||||
</ul>
|
||||
|
||||
<h3>4. reCAPTCHA v3</h3>
|
||||
<p><strong>Strengths:</strong></p>
|
||||
<ul>
|
||||
<li>Free tier with wide community adoption.</li>
|
||||
<li>Quick to integrate in basic web forms.</li>
|
||||
</ul>
|
||||
<p><strong>Weaknesses vs ChronoSeal:</strong></p>
|
||||
<ul>
|
||||
<li>Heavy Google user tracking cookies and profiling.</li>
|
||||
<li>Fails to block modern, stateful automated browsers and headless runs.</li>
|
||||
<li>High rate of bypasses by standard captcha-solving farms.</li>
|
||||
</ul>
|
||||
|
||||
<h2>When to Choose ChronoSeal</h2>
|
||||
<p>Choose <strong>ChronoSeal</strong> if you want:</p>
|
||||
<ul>
|
||||
<li>Maximum visitor privacy.</li>
|
||||
<li>Strong, deterministic cryptographic guarantees.</li>
|
||||
<li>Complete control over your server infrastructure and logs.</li>
|
||||
<li>Configurable and tunable verification strength.</li>
|
||||
<li>Zero dependency on third-party SaaS vendors.</li>
|
||||
</ul>
|
||||
|
||||
<h2>Technical Differentiation</h2>
|
||||
<p>ChronoSeal's primary advantage is the <strong>Synthetic Gene Mutation Engine</strong>. Instead of just checking static fingerprint values or browser headers, the server issues dynamic mutation programs that both the server and client WASM execute in sync. This establishes a second stateful channel that is extremely difficult for automation clients to spoof at scale without implementing the complete state model.</p>
|
||||
</article>
|
||||
|
||||
<!-- Pager -->
|
||||
<nav class="doc-pager" aria-label="Doc navigation Pager">
|
||||
<a href="security.html" class="doc-pager-link">
|
||||
<div class="doc-pager-label">Previous</div>
|
||||
<div class="doc-pager-title">Security Policy</div>
|
||||
</a>
|
||||
<a href="architecture.html" class="doc-pager-link doc-pager-link--next">
|
||||
<div class="doc-pager-label">Next</div>
|
||||
<div class="doc-pager-title">Architecture Overview</div>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<div class="doc-meta">
|
||||
Last Updated: June 2026 (v1.0.2)
|
||||
</div>
|
||||
</main>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="footer">
|
||||
<div class="footer-content">
|
||||
<div class="footer-section">
|
||||
<h4>
|
||||
<img class="logobar" src="assets/logo.svg" alt="ChronoSeal Logo" style="width: 24px; height: 24px; vertical-align: middle;"> ChronoSeal
|
||||
</h4>
|
||||
<p>Next-generation browser attestation daemon built for the modern web.</p>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h4>Resources</h4>
|
||||
<a href="philosophy.html">Design Philosophy</a>
|
||||
<a href="architecture.html">Architecture</a>
|
||||
<a href="protocol.html">Protocol</a>
|
||||
<a href="api.html">API Reference</a>
|
||||
<a href="comparison.html">Comparison</a>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h4>Community</h4>
|
||||
<a href="https://github.com/thakares/chronoseal-rs" target="_blank" rel="noopener"><i class="fab fa-github"></i> GitHub</a>
|
||||
<a href="deployment.html"><i class="fas fa-rocket"></i> Deployment</a>
|
||||
<a href="testing.html"><i class="fas fa-vial"></i> Testing</a>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h4>Legal & Security</h4>
|
||||
<a href="security.html">Security Policy</a>
|
||||
<a href="privacy.html">Privacy Policy</a>
|
||||
<a href="performance.html">Performance Tuning</a>
|
||||
<a href="operations.html">Operations Guide</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<p>© 2026 ChronoSeal Project. Built with 🦀 Rust and ❤️ for open source.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Search Overlay -->
|
||||
<div class="search-overlay">
|
||||
<div class="search-box">
|
||||
<div class="search-input-wrap">
|
||||
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
|
||||
<input type="text" class="search-input" placeholder="Search documentation... (Esc to close)">
|
||||
<span class="search-kbd">/</span>
|
||||
</div>
|
||||
<div class="search-results"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Back to top -->
|
||||
<a href="#" class="back-to-top" aria-label="Back to top">▲</a>
|
||||
|
||||
<!-- JS Scripts -->
|
||||
<script src="js/search.js"></script>
|
||||
<script src="js/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,984 @@
|
||||
/* ============================================================
|
||||
ChronoSeal Design System — chronoseal.css
|
||||
Vibrant, premium, glassmorphism theme matching the target site
|
||||
============================================================ */
|
||||
|
||||
/* ---- Custom Properties ---- */
|
||||
:root {
|
||||
--bg-primary: #0a0e27;
|
||||
--bg-secondary: #11162e;
|
||||
--bg-card: rgba(18, 24, 48, 0.7);
|
||||
--bg-card-solid: #121830;
|
||||
--border: rgba(56, 78, 135, 0.3);
|
||||
--border-glow: rgba(0, 255, 255, 0.2);
|
||||
--text-primary: #ffffff;
|
||||
--text-secondary: #a0a8c3;
|
||||
--text-muted: #5a6490;
|
||||
|
||||
--accent-cyan: #00e5ff;
|
||||
--accent-purple: #b84eff;
|
||||
--accent-green: #00ff88;
|
||||
--accent-red: #ff4757;
|
||||
|
||||
--gradient-1: linear-gradient(135deg, #00e5ff 0%, #b84eff 100%);
|
||||
--gradient-2: linear-gradient(135deg, #00ff88 0%, #00e5ff 100%);
|
||||
--shadow-glow: 0 0 30px rgba(0, 229, 255, 0.1);
|
||||
|
||||
--font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
--font-mono: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
|
||||
|
||||
--max-width: 1400px;
|
||||
--header-h: 75px;
|
||||
--r-sm: 8px;
|
||||
--r-md: 12px;
|
||||
--r-lg: 20px;
|
||||
--ease: cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
--tr: .3s ease;
|
||||
}
|
||||
|
||||
/* ---- Reset ---- */
|
||||
*, *::before, *::after {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-sans);
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
line-height: 1.6;
|
||||
overflow-x: hidden;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--text-secondary);
|
||||
text-decoration: none;
|
||||
transition: all var(--tr);
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
img, svg {
|
||||
max-width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
button {
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
border: none;
|
||||
background: none;
|
||||
}
|
||||
|
||||
code, pre {
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
/* Scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--border);
|
||||
border-radius: 4px;
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--accent-cyan);
|
||||
}
|
||||
|
||||
/* ---- Layout ---- */
|
||||
.container, .main-content {
|
||||
width: 100%;
|
||||
max-width: var(--max-width);
|
||||
margin: 0 auto;
|
||||
padding: 0 2rem;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
padding-top: var(--header-h);
|
||||
}
|
||||
|
||||
.section {
|
||||
padding: 6rem 0;
|
||||
scroll-margin-top: 100px;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.arch-diagram {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 2rem auto;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.arch-diagram svg {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.section--alt {
|
||||
background: var(--bg-secondary);
|
||||
border-top: 1px solid var(--border);
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
BACKGROUND ANIMATION
|
||||
============================================================ */
|
||||
.bg-animation {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: -1;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.gradient-sphere {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
filter: blur(80px);
|
||||
opacity: 0.4;
|
||||
animation: float 20s infinite ease-in-out;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.sphere-1 { width: 600px; height: 600px; background: var(--accent-cyan); top: -200px; right: -200px; animation-delay: 0s; }
|
||||
.sphere-2 { width: 500px; height: 500px; background: var(--accent-purple); bottom: -150px; left: -150px; animation-delay: -5s; }
|
||||
.sphere-3 { width: 400px; height: 400px; background: var(--accent-green); top: 40%; left: 30%; animation-delay: -10s; opacity: 0.2; }
|
||||
|
||||
@keyframes float {
|
||||
0%, 100% { transform: translate(0, 0) scale(1); }
|
||||
33% { transform: translate(30px, -30px) scale(1.05); }
|
||||
66% { transform: translate(-20px, 20px) scale(0.95); }
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
NAVBAR / HEADER
|
||||
============================================================ */
|
||||
.navbar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1000;
|
||||
background: rgba(10, 14, 39, 0.85);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border-bottom: 1px solid transparent;
|
||||
padding: 1.25rem 2rem;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.navbar.scrolled {
|
||||
padding: 0.75rem 2rem;
|
||||
background: rgba(10, 14, 39, 0.98);
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.nav-container {
|
||||
max-width: var(--max-width);
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 800;
|
||||
background: var(--gradient-1);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.logobar {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.logo span {
|
||||
color: transparent;
|
||||
background: var(--gradient-1);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
display: flex;
|
||||
gap: 2.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.nav-links a {
|
||||
color: var(--text-secondary);
|
||||
text-decoration: none;
|
||||
transition: color 0.3s ease;
|
||||
font-weight: 500;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.nav-links a:hover, .nav-links a.active {
|
||||
color: var(--text-primary);
|
||||
text-shadow: 0 0 10px rgba(0, 229, 255, 0.4);
|
||||
}
|
||||
|
||||
.github-btn {
|
||||
background: var(--bg-card-solid);
|
||||
padding: 0.5rem 1.25rem;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.github-btn:hover {
|
||||
border-color: var(--accent-cyan);
|
||||
box-shadow: 0 0 15px rgba(0, 229, 255, 0.2);
|
||||
}
|
||||
|
||||
.nav-toggle {
|
||||
display: none;
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-primary);
|
||||
font-size: 1.5rem;
|
||||
cursor: pointer;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
/* Nav Dropdown */
|
||||
.nav-dropdown {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.nav-dropdown-menu {
|
||||
position: absolute;
|
||||
top: calc(100% + 15px);
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(8px);
|
||||
min-width: 220px;
|
||||
background: var(--bg-card-solid);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--r-md);
|
||||
padding: 6px;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity var(--tr), transform var(--tr);
|
||||
box-shadow: 0 16px 48px rgba(0,0,0,.4);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.nav-dropdown:hover .nav-dropdown-menu {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
transform: translateX(-50%) translateY(0);
|
||||
}
|
||||
|
||||
.nav-dropdown-menu a {
|
||||
display: block;
|
||||
padding: 8px 14px;
|
||||
font-size: .88rem;
|
||||
color: var(--text-secondary);
|
||||
border-radius: var(--r-sm);
|
||||
transition: all var(--tr);
|
||||
}
|
||||
|
||||
.nav-dropdown-menu a:hover {
|
||||
color: var(--text-primary);
|
||||
background: rgba(0, 229, 255, 0.05);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
HERO SECTION
|
||||
============================================================ */
|
||||
.hero {
|
||||
text-align: center;
|
||||
padding: 6rem 0;
|
||||
}
|
||||
|
||||
.hero-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 1.25rem;
|
||||
background: rgba(0, 229, 255, 0.05);
|
||||
border: 1px solid var(--border-glow);
|
||||
border-radius: 50px;
|
||||
font-size: 0.875rem;
|
||||
color: var(--accent-cyan);
|
||||
margin-bottom: 2rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
font-size: clamp(3rem, 5vw, 4.5rem);
|
||||
font-weight: 800;
|
||||
margin-bottom: 1.5rem;
|
||||
background: var(--gradient-1);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.hero-subtitle {
|
||||
font-size: 1.25rem;
|
||||
color: var(--text-secondary);
|
||||
max-width: 700px;
|
||||
margin: 0 auto 2.5rem;
|
||||
}
|
||||
|
||||
.hero-buttons {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
BUTTONS
|
||||
============================================================ */
|
||||
.btn {
|
||||
padding: 0.875rem 2rem;
|
||||
border-radius: 12px;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
transition: all var(--tr);
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--gradient-1);
|
||||
color: var(--bg-primary);
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 30px rgba(0, 229, 255, 0.4);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid var(--border);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
border-color: var(--accent-cyan);
|
||||
background: rgba(0, 229, 255, 0.05);
|
||||
}
|
||||
|
||||
.btn-ghost {
|
||||
color: var(--text-secondary);
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
.btn-ghost:hover {
|
||||
color: var(--text-primary);
|
||||
border-color: var(--border);
|
||||
background: rgba(255,255,255,0.05);
|
||||
}
|
||||
.btn-sm {
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
STATS ROW
|
||||
============================================================ */
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 2rem;
|
||||
text-align: center;
|
||||
margin-bottom: 6rem;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: var(--bg-card);
|
||||
border-radius: 20px;
|
||||
padding: 2.5rem 2rem;
|
||||
border: 1px solid var(--border);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 3.5rem;
|
||||
font-weight: 800;
|
||||
background: var(--gradient-1);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
color: var(--text-secondary);
|
||||
margin-top: 1rem;
|
||||
font-weight: 500;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
SECTION HEADER
|
||||
============================================================ */
|
||||
.section-header {
|
||||
text-align: center;
|
||||
margin-bottom: 4rem;
|
||||
}
|
||||
|
||||
.section-label {
|
||||
display: inline-block;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
color: var(--accent-cyan);
|
||||
margin-bottom: 1rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.section-header h2 {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 800;
|
||||
margin-bottom: 1rem;
|
||||
background: var(--gradient-2);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.section-header p {
|
||||
color: var(--text-secondary);
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
FEATURES GRID / CARDS
|
||||
============================================================ */
|
||||
.cards-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: var(--bg-card);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 20px;
|
||||
padding: 2.5rem;
|
||||
transition: all 0.4s var(--ease);
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-10px);
|
||||
border-color: var(--accent-cyan);
|
||||
box-shadow: var(--shadow-glow);
|
||||
}
|
||||
|
||||
.card-icon {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
background: rgba(0, 229, 255, 0.1);
|
||||
border-radius: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 1.5rem;
|
||||
border: 1px solid rgba(0, 229, 255, 0.2);
|
||||
}
|
||||
|
||||
.card-icon i {
|
||||
font-size: 1.75rem;
|
||||
color: var(--accent-cyan);
|
||||
}
|
||||
|
||||
.card h3 {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.card p {
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 1.5rem;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.card-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.tag {
|
||||
padding: 0.35rem 0.85rem;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 20px;
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-primary);
|
||||
font-weight: 500;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
STEPS / HOW IT WORKS
|
||||
============================================================ */
|
||||
.steps {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.step {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--r-md);
|
||||
padding: 2rem;
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
transition: all var(--tr);
|
||||
}
|
||||
|
||||
.step:hover {
|
||||
border-color: var(--accent-cyan);
|
||||
box-shadow: var(--shadow-glow);
|
||||
}
|
||||
|
||||
.step-number {
|
||||
flex-shrink: 0;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 50%;
|
||||
background: var(--gradient-1);
|
||||
color: var(--bg-primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.step-content h3 {
|
||||
font-size: 1.25rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.step-content p {
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.65;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
COMPARISON TABLE
|
||||
============================================================ */
|
||||
.comparison-table-wrap {
|
||||
overflow-x: auto;
|
||||
background: var(--bg-card);
|
||||
border-radius: 20px;
|
||||
border: 1px solid var(--border);
|
||||
padding: 1rem;
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.comparison-table-wrap table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
min-width: 700px;
|
||||
}
|
||||
|
||||
.comparison-table-wrap th, .comparison-table-wrap td {
|
||||
padding: 1.25rem 1rem;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.comparison-table-wrap th {
|
||||
color: var(--text-primary);
|
||||
font-weight: 600;
|
||||
background: rgba(0, 229, 255, 0.05);
|
||||
}
|
||||
|
||||
.comparison-table-wrap tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.check {
|
||||
color: var(--accent-green);
|
||||
font-weight: 600;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
td.check {
|
||||
display: table-cell;
|
||||
}
|
||||
|
||||
td.check i {
|
||||
margin-right: 0.4rem;
|
||||
}
|
||||
|
||||
.times {
|
||||
color: var(--accent-red);
|
||||
opacity: 0.7;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
td.times {
|
||||
display: table-cell;
|
||||
}
|
||||
|
||||
td.times i {
|
||||
margin-right: 0.4rem;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
CODE BLOCKS
|
||||
============================================================ */
|
||||
.code-block {
|
||||
background: #080b1a;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
overflow-x: auto;
|
||||
font-size: 0.9rem;
|
||||
margin: 1.5rem 0;
|
||||
box-shadow: inset 0 0 10px rgba(0,0,0,0.5);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.code-block-center {
|
||||
max-width: 700px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.code-block pre {
|
||||
color: #a0a8c3;
|
||||
line-height: 1.5;
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
.code-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1rem;
|
||||
border-bottom: 1px solid rgba(255,255,255,0.05);
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.code-lang {
|
||||
font-size: 0.75rem;
|
||||
color: var(--accent-cyan);
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.code-copy-btn {
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-secondary);
|
||||
border: 1px solid var(--border);
|
||||
padding: 3px 8px;
|
||||
border-radius: 4px;
|
||||
background: rgba(255,255,255,0.03);
|
||||
transition: all var(--tr);
|
||||
}
|
||||
.code-copy-btn:hover {
|
||||
color: var(--text-primary);
|
||||
border-color: var(--accent-cyan);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
FOOTER
|
||||
============================================================ */
|
||||
.footer {
|
||||
background: var(--bg-secondary);
|
||||
border-top: 1px solid var(--border);
|
||||
padding: 5rem 2rem 2rem;
|
||||
margin-top: 6rem;
|
||||
}
|
||||
|
||||
.footer-content {
|
||||
max-width: var(--max-width);
|
||||
margin: 0 auto;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 4rem;
|
||||
}
|
||||
|
||||
.footer-section h4 {
|
||||
margin-bottom: 1.5rem;
|
||||
color: var(--text-primary);
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.footer-section p {
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.footer-section a {
|
||||
display: inline-block;
|
||||
color: var(--text-secondary);
|
||||
text-decoration: none;
|
||||
margin-bottom: 0.75rem;
|
||||
transition: all var(--tr);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.footer-section a:hover {
|
||||
color: var(--accent-cyan);
|
||||
transform: translateX(5px);
|
||||
}
|
||||
|
||||
.footer-bottom {
|
||||
text-align: center;
|
||||
padding-top: 3rem;
|
||||
margin-top: 3rem;
|
||||
border-top: 1px solid var(--border);
|
||||
color: var(--text-muted);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
SEARCH OVERLAY
|
||||
============================================================ */
|
||||
.search-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 2000;
|
||||
background: rgba(10, 14, 39, 0.85);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
padding-top: 15vh;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity var(--tr);
|
||||
}
|
||||
|
||||
.search-overlay.open {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
width: 100%;
|
||||
max-width: 580px;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--r-lg);
|
||||
overflow: hidden;
|
||||
transform: translateY(16px);
|
||||
transition: transform var(--tr);
|
||||
box-shadow: 0 24px 64px rgba(0,0,0,.5);
|
||||
}
|
||||
|
||||
.search-overlay.open .search-box {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.search-input-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16px 20px;
|
||||
gap: 12px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.search-input-wrap svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
color: var(--text-muted);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
background: none;
|
||||
border: none;
|
||||
outline: none;
|
||||
font-size: 1rem;
|
||||
color: var(--text-primary);
|
||||
font-family: var(--font-sans);
|
||||
}
|
||||
|
||||
.search-input::placeholder {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.search-kbd {
|
||||
font-size: .7rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-muted);
|
||||
padding: 2px 8px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
background: var(--bg-card-solid);
|
||||
}
|
||||
|
||||
.search-results {
|
||||
max-height: 360px;
|
||||
overflow-y: auto;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.search-result {
|
||||
display: block;
|
||||
padding: 10px 16px;
|
||||
border-radius: var(--r-sm);
|
||||
transition: background var(--tr);
|
||||
}
|
||||
|
||||
.search-result:hover, .search-result.selected {
|
||||
background: rgba(0, 229, 255, 0.05);
|
||||
}
|
||||
|
||||
.search-result-title {
|
||||
font-size: .9rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.search-result-desc {
|
||||
font-size: .8rem;
|
||||
color: var(--text-secondary);
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.search-empty {
|
||||
padding: 24px;
|
||||
text-align: center;
|
||||
color: var(--text-secondary);
|
||||
font-size: .9rem;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
BACK TO TOP
|
||||
============================================================ */
|
||||
.back-to-top {
|
||||
position: fixed;
|
||||
bottom: 24px;
|
||||
right: 24px;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--bg-card-solid);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 50%;
|
||||
color: var(--text-secondary);
|
||||
font-size: 1.1rem;
|
||||
z-index: 100;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: all var(--tr);
|
||||
}
|
||||
|
||||
.back-to-top.visible {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.back-to-top:hover {
|
||||
background: rgba(0, 229, 255, 0.05);
|
||||
color: var(--accent-cyan);
|
||||
border-color: var(--accent-cyan);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--shadow-glow);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
ANIMATION INTERSECT
|
||||
============================================================ */
|
||||
.animate {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
transition: opacity 0.6s ease-out, transform 0.6s ease-out;
|
||||
}
|
||||
.animate.in-view {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
RESPONSIVE
|
||||
============================================================ */
|
||||
@media (max-width: 900px) {
|
||||
.nav-links {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: rgba(10, 14, 39, 0.98);
|
||||
backdrop-filter: blur(15px);
|
||||
flex-direction: column;
|
||||
padding: 2rem 1rem;
|
||||
border-bottom: 1px solid var(--border);
|
||||
gap: 1.5rem;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transform: translateY(-10px);
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
box-shadow: 0 20px 40px rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
.nav-links.active {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.nav-toggle {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,440 @@
|
||||
/* ============================================================
|
||||
Documentation Pages — docs.css
|
||||
Layout, sidebar, content rendering for documentation pages
|
||||
============================================================ */
|
||||
|
||||
/* ---- Doc page shell ---- */
|
||||
.doc-page {
|
||||
padding-top: var(--header-h);
|
||||
}
|
||||
|
||||
.doc-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 280px 1fr;
|
||||
gap: 0;
|
||||
min-height: calc(100vh - var(--header-h));
|
||||
max-width: var(--max-width);
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* ---- Sidebar ---- */
|
||||
.doc-sidebar {
|
||||
position: sticky;
|
||||
top: var(--header-h);
|
||||
height: calc(100vh - var(--header-h));
|
||||
overflow-y: auto;
|
||||
padding: 2.5rem 1.5rem;
|
||||
background: rgba(17, 22, 46, 0.5);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
border-right: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.doc-sidebar-toggle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.doc-nav-group {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.doc-nav-label {
|
||||
font-size: .75rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .08em;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 0.75rem;
|
||||
padding-left: 0.75rem;
|
||||
}
|
||||
|
||||
.doc-nav-item {
|
||||
display: block;
|
||||
padding: 8px 14px;
|
||||
font-size: .9rem;
|
||||
color: var(--text-secondary);
|
||||
border-radius: var(--r-sm);
|
||||
transition: all var(--tr);
|
||||
border-left: 2px solid transparent;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.doc-nav-item:hover {
|
||||
color: var(--text-primary);
|
||||
background: rgba(0, 229, 255, 0.05);
|
||||
}
|
||||
|
||||
.doc-nav-item.active {
|
||||
color: var(--accent-cyan);
|
||||
background: rgba(0, 229, 255, 0.05);
|
||||
border-left-color: var(--accent-cyan);
|
||||
text-shadow: 0 0 10px rgba(0, 229, 255, 0.3);
|
||||
}
|
||||
|
||||
/* ---- Content area ---- */
|
||||
.doc-main {
|
||||
padding: 3rem 4rem 6rem;
|
||||
max-width: 960px;
|
||||
}
|
||||
|
||||
/* ---- Breadcrumb ---- */
|
||||
.doc-breadcrumb {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: .8rem;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.doc-breadcrumb a {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.doc-breadcrumb a:hover {
|
||||
color: var(--accent-cyan);
|
||||
}
|
||||
|
||||
.doc-breadcrumb .sep {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* ---- Markdown content rendering ---- */
|
||||
.doc-content h1 {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: -.03em;
|
||||
margin-bottom: 0.5rem;
|
||||
line-height: 1.15;
|
||||
background: var(--gradient-1);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.doc-content .doc-subtitle {
|
||||
font-size: 1.15rem;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 2rem;
|
||||
line-height: 1.65;
|
||||
}
|
||||
|
||||
.doc-content h2 {
|
||||
font-size: 1.75rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: -.02em;
|
||||
margin-top: 3.5rem;
|
||||
margin-bottom: 1.25rem;
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: 1px solid var(--border);
|
||||
scroll-margin-top: calc(var(--header-h) + 24px);
|
||||
background: var(--gradient-2);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.doc-content h3 {
|
||||
font-size: 1.35rem;
|
||||
font-weight: 700;
|
||||
margin-top: 2.5rem;
|
||||
margin-bottom: 1rem;
|
||||
scroll-margin-top: calc(var(--header-h) + 24px);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.doc-content h4 {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 700;
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 0.75rem;
|
||||
color: var(--text-secondary);
|
||||
scroll-margin-top: calc(var(--header-h) + 24px);
|
||||
}
|
||||
|
||||
.doc-content p {
|
||||
margin-bottom: 1.25rem;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.doc-content ul, .doc-content ol {
|
||||
margin-bottom: 1.25rem;
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
|
||||
.doc-content ul {
|
||||
list-style: disc;
|
||||
}
|
||||
|
||||
.doc-content ol {
|
||||
list-style: decimal;
|
||||
}
|
||||
|
||||
.doc-content li {
|
||||
margin-bottom: 0.5rem;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.doc-content li strong {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.doc-content code {
|
||||
padding: 2px 6px;
|
||||
font-size: .88em;
|
||||
background: rgba(0, 229, 255, 0.05);
|
||||
border: 1px solid rgba(0, 229, 255, 0.15);
|
||||
border-radius: 4px;
|
||||
color: var(--accent-cyan);
|
||||
}
|
||||
|
||||
.doc-content pre {
|
||||
background: #080b1a;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
margin: 1.5rem 0 2rem;
|
||||
overflow-x: auto;
|
||||
font-size: .88rem;
|
||||
line-height: 1.6;
|
||||
box-shadow: inset 0 0 10px rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
.doc-content pre code {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
color: #a0a8c3;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
.doc-content strong {
|
||||
color: var(--text-primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.doc-content em {
|
||||
color: var(--text-secondary);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.doc-content a {
|
||||
color: var(--accent-cyan);
|
||||
border-bottom: 1px solid transparent;
|
||||
transition: all var(--tr);
|
||||
}
|
||||
|
||||
.doc-content a:hover {
|
||||
color: var(--text-primary);
|
||||
border-bottom-color: var(--accent-cyan);
|
||||
}
|
||||
|
||||
.doc-content blockquote {
|
||||
margin: 1.5rem 0;
|
||||
padding: 1.25rem 1.5rem;
|
||||
border-left: 4px solid var(--accent-purple);
|
||||
background: var(--bg-card);
|
||||
border-radius: 0 var(--r-md) var(--r-md) 0;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.doc-content blockquote p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.doc-content table {
|
||||
width: 100%;
|
||||
margin: 1.5rem 0 2rem;
|
||||
border-collapse: collapse;
|
||||
font-size: .88rem;
|
||||
}
|
||||
|
||||
.doc-content table th {
|
||||
padding: 12px 16px;
|
||||
text-align: left;
|
||||
font-weight: 700;
|
||||
font-size: .78rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .06em;
|
||||
color: var(--text-primary);
|
||||
background: rgba(0, 229, 255, 0.05);
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.doc-content table td {
|
||||
padding: 12px 16px;
|
||||
border: 1px solid var(--border);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.doc-content table tr:hover td {
|
||||
background: rgba(255,255,255,0.02);
|
||||
}
|
||||
|
||||
.doc-content hr {
|
||||
border: none;
|
||||
height: 1px;
|
||||
background: var(--border);
|
||||
margin: 3rem 0;
|
||||
}
|
||||
|
||||
/* ---- Alert boxes ---- */
|
||||
.doc-alert {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
padding: 1rem 1.25rem;
|
||||
margin: 1.5rem 0 2rem;
|
||||
border-radius: var(--r-md);
|
||||
border: 1px solid;
|
||||
}
|
||||
|
||||
.doc-alert-icon {
|
||||
flex-shrink: 0;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.doc-alert-body {
|
||||
font-size: .9rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.doc-alert-body p {
|
||||
margin-bottom: 4px;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.doc-alert-body p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.doc-alert--note {
|
||||
background: rgba(0, 229, 255, 0.05);
|
||||
border-color: rgba(0, 229, 255, 0.2);
|
||||
color: var(--accent-cyan);
|
||||
}
|
||||
|
||||
.doc-alert--tip {
|
||||
background: rgba(0, 255, 136, 0.05);
|
||||
border-color: rgba(0, 255, 136, 0.2);
|
||||
color: var(--accent-green);
|
||||
}
|
||||
|
||||
.doc-alert--warn {
|
||||
background: rgba(251, 191, 36, 0.05);
|
||||
border-color: rgba(251, 191, 36, 0.2);
|
||||
color: #fbbf24;
|
||||
}
|
||||
|
||||
.doc-alert--danger {
|
||||
background: rgba(255, 71, 87, 0.05);
|
||||
border-color: rgba(255, 71, 87, 0.2);
|
||||
color: var(--accent-red);
|
||||
}
|
||||
|
||||
/* ---- Prev / Next navigation ---- */
|
||||
.doc-pager {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 1.5rem;
|
||||
margin-top: 4rem;
|
||||
padding-top: 2rem;
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.doc-pager-link {
|
||||
padding: 1.5rem;
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--r-md);
|
||||
transition: all var(--tr);
|
||||
}
|
||||
|
||||
.doc-pager-link:hover {
|
||||
border-color: var(--accent-cyan);
|
||||
background: rgba(0, 229, 255, 0.03);
|
||||
box-shadow: var(--shadow-glow);
|
||||
}
|
||||
|
||||
.doc-pager-label {
|
||||
font-size: .75rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .08em;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.doc-pager-title {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.doc-pager-link--next {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* ---- Last updated ---- */
|
||||
.doc-meta {
|
||||
font-size: .8rem;
|
||||
color: var(--text-muted);
|
||||
margin-top: 2.5rem;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
RESPONSIVE
|
||||
============================================================ */
|
||||
@media(max-width:900px) {
|
||||
.doc-layout {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.doc-sidebar {
|
||||
position: fixed;
|
||||
top: var(--header-h);
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 280px;
|
||||
z-index: 998;
|
||||
transform: translateX(-100%);
|
||||
transition: transform var(--tr);
|
||||
border-right: 1px solid var(--border);
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.doc-sidebar.open {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.doc-sidebar-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 16px;
|
||||
font-size: .85rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-secondary);
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--r-sm);
|
||||
margin-bottom: 16px;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.doc-sidebar-toggle:hover {
|
||||
color: var(--text-primary);
|
||||
border-color: var(--accent-cyan);
|
||||
}
|
||||
|
||||
.doc-main {
|
||||
padding: 2rem 1.5rem 4rem;
|
||||
}
|
||||
|
||||
.doc-pager {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/* ============================================================
|
||||
Print Stylesheet — print.css
|
||||
Clean print layout for documentation pages
|
||||
============================================================ */
|
||||
|
||||
@media print {
|
||||
*{color:#111!important;background:white!important;box-shadow:none!important;text-shadow:none!important}
|
||||
|
||||
body{font-size:11pt;line-height:1.6}
|
||||
|
||||
.site-header,.site-footer,.nav-toggle,.back-to-top,
|
||||
.search-overlay,.doc-sidebar,.doc-toc,.doc-pager,
|
||||
.doc-sidebar-toggle,.doc-breadcrumb,
|
||||
.hero-bg,.hero-grid,.hero-badge,.hero-actions,
|
||||
.hero-terminal,.cta-section,
|
||||
.btn,.nav-cta{display:none!important}
|
||||
|
||||
.doc-layout,.doc-layout--toc{display:block!important}
|
||||
.doc-main{padding:0!important;max-width:100%!important}
|
||||
.doc-page{padding-top:0!important}
|
||||
|
||||
a{text-decoration:underline}
|
||||
a[href^="http"]::after{content:" (" attr(href) ")";font-size:9pt;color:#666}
|
||||
a[href^="#"]::after{content:""}
|
||||
|
||||
pre,code{font-size:9pt;border:1px solid #ddd;padding:8px;page-break-inside:avoid}
|
||||
table{border-collapse:collapse;width:100%}
|
||||
th,td{border:1px solid #ccc;padding:6px 10px;font-size:9pt}
|
||||
th{background:#eee!important;font-weight:700}
|
||||
|
||||
h1{font-size:20pt;border-bottom:2px solid #111;padding-bottom:6pt;margin-bottom:12pt}
|
||||
h2{font-size:16pt;border-bottom:1px solid #999;padding-bottom:4pt;margin-top:24pt;page-break-after:avoid}
|
||||
h3{font-size:13pt;margin-top:18pt;page-break-after:avoid}
|
||||
|
||||
img{max-width:100%!important}
|
||||
.section{padding:24pt 0!important}
|
||||
|
||||
@page{margin:1.5cm 2cm}
|
||||
}
|
||||
@@ -0,0 +1,334 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
||||
<title>Deployment Guide | ChronoSeal Documentation</title>
|
||||
<meta name="description" content="ChronoSeal Deployment Guide, detailing compiler requirements, native installation script, environment variables, Nginx configurations, and Docker integration.">
|
||||
<link rel="stylesheet" href="css/chronoseal.css">
|
||||
<link rel="stylesheet" href="css/docs.css">
|
||||
<link rel="stylesheet" href="css/print.css" media="print">
|
||||
<link rel="manifest" href="site.webmanifest">
|
||||
<link rel="icon" href="assets/logo.svg" type="image/svg+xml">
|
||||
|
||||
<!-- Font Awesome for Icons -->
|
||||
<link rel="preconnect" href="https://cdnjs.cloudflare.com" crossorigin />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
|
||||
integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw=="
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Floating Background Spheres -->
|
||||
<div class="bg-animation">
|
||||
<div class="gradient-sphere sphere-1"></div>
|
||||
<div class="gradient-sphere sphere-2"></div>
|
||||
<div class="gradient-sphere sphere-3"></div>
|
||||
</div>
|
||||
|
||||
<!-- Header / Navbar -->
|
||||
<nav class="navbar" id="navbar">
|
||||
<div class="nav-container">
|
||||
<a href="index.html" class="logo">
|
||||
<img class="logobar" src="assets/logo.svg" alt="ChronoSeal Logo">
|
||||
<span>ChronoSeal</span>
|
||||
</a>
|
||||
<div class="nav-links" id="navLinks">
|
||||
<a href="philosophy.html">Philosophy</a>
|
||||
<a href="architecture.html">Architecture</a>
|
||||
<a href="protocol.html">Protocol</a>
|
||||
<a href="comparison.html">Comparison</a>
|
||||
<div class="nav-dropdown">
|
||||
<a href="#" class="nav-link">More <small>▼</small></a>
|
||||
<div class="nav-dropdown-menu">
|
||||
<a href="api.html">API Reference</a>
|
||||
<a href="deployment.html" class="active">Deployment Guide</a>
|
||||
<a href="operations.html">Operations Guide</a>
|
||||
<a href="testing.html">Testing Strategy</a>
|
||||
<a href="performance.html">Performance Tuning</a>
|
||||
<a href="threat-model.html">Threat Model</a>
|
||||
<a href="security.html">Security Policy</a>
|
||||
<a href="privacy.html">Privacy Policy</a>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-ghost btn-sm btn-icon search-trigger" aria-label="Search" onclick="ChronoSearch.open()">
|
||||
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" width="16" height="16"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
|
||||
</button>
|
||||
<a href="https://github.com/thakares/chronoseal-rs" class="github-btn" target="_blank" rel="noopener">
|
||||
<i class="fab fa-github"></i> GitHub
|
||||
</a>
|
||||
</div>
|
||||
<button class="nav-toggle" id="menuBtn" aria-label="Toggle Navigation">
|
||||
<i class="fas fa-bars"></i>
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Doc Page Shell -->
|
||||
<div class="doc-page">
|
||||
<div class="doc-layout">
|
||||
|
||||
<!-- Sidebar -->
|
||||
<aside class="doc-sidebar">
|
||||
<button class="doc-sidebar-toggle">
|
||||
<i class="fas fa-bars"></i> Sidebar Menu
|
||||
</button>
|
||||
<div class="doc-nav-group">
|
||||
<div class="doc-nav-label">Core Concepts</div>
|
||||
<a href="philosophy.html" class="doc-nav-item">Design Philosophy</a>
|
||||
<a href="architecture.html" class="doc-nav-item">Architecture Overview</a>
|
||||
<a href="comparison.html" class="doc-nav-item">ChronoSeal vs Others</a>
|
||||
</div>
|
||||
<div class="doc-nav-group">
|
||||
<div class="doc-nav-label">Protocol & API</div>
|
||||
<a href="protocol.html" class="doc-nav-item">Protocol Specification</a>
|
||||
<a href="api.html" class="doc-nav-item">API Reference</a>
|
||||
</div>
|
||||
<div class="doc-nav-group">
|
||||
<div class="doc-nav-label">Operations & Security</div>
|
||||
<a href="threat-model.html" class="doc-nav-item">Threat Model</a>
|
||||
<a href="security.html" class="doc-nav-item">Security Policy</a>
|
||||
<a href="privacy.html" class="doc-nav-item">Privacy Policy</a>
|
||||
<a href="performance.html" class="doc-nav-item">Performance Tuning</a>
|
||||
</div>
|
||||
<div class="doc-nav-group">
|
||||
<div class="doc-nav-label">Lifecycle & Dev</div>
|
||||
<a href="deployment.html" class="doc-nav-item active">Deployment Guide</a>
|
||||
<a href="operations.html" class="doc-nav-item">Operations Handbook</a>
|
||||
<a href="testing.html" class="doc-nav-item">Testing Strategy</a>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content Area -->
|
||||
<main class="doc-main">
|
||||
<nav class="doc-breadcrumb" aria-label="Breadcrumb">
|
||||
<a href="index.html">Home</a>
|
||||
<span class="sep">/</span>
|
||||
<a href="#">Lifecycle & Dev</a>
|
||||
<span class="sep">/</span>
|
||||
<span>Deployment Guide</span>
|
||||
</nav>
|
||||
|
||||
<article class="doc-content">
|
||||
<h1>ChronoSeal Deployment Guide</h1>
|
||||
<p class="doc-subtitle">ChronoSeal runs as a native Unix daemon behind TLS, serving WebAssembly assets. This guide covers building, configuring, and service installation options.</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>System Requirements</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Requirement</th>
|
||||
<th>Min Version</th>
|
||||
<th>Core Purpose</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Rust (cargo)</td>
|
||||
<td>1.87 stable</td>
|
||||
<td>Compile server binary and shared libraries</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>wasm-pack</td>
|
||||
<td>0.13</td>
|
||||
<td>Generate the browser WebAssembly module package</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>wasm32 target</td>
|
||||
<td>stable</td>
|
||||
<td>Required target for cargo wasm32 compilation</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>systemd</td>
|
||||
<td>248+</td>
|
||||
<td>Service management and sandbox isolation</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Docker</td>
|
||||
<td>24.x</td>
|
||||
<td>Containerization support</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2>Building from Source</h2>
|
||||
<p>Install the Rust WASM build targets and tools:</p>
|
||||
<div class="code-block">
|
||||
<div class="code-header">
|
||||
<span class="code-lang">Shell</span>
|
||||
<button class="code-copy-btn">Copy</button>
|
||||
</div>
|
||||
<pre><code>rustup target add wasm32-unknown-unknown
|
||||
cargo install wasm-pack</code></pre>
|
||||
</div>
|
||||
|
||||
<p>Build the browser WASM package and compile the server daemon:</p>
|
||||
<div class="code-block">
|
||||
<div class="code-header">
|
||||
<span class="code-lang">Shell</span>
|
||||
<button class="code-copy-btn">Copy</button>
|
||||
</div>
|
||||
<pre><code># Build WASM package and copy to frontend assets
|
||||
wasm-pack build wasm --target web --release
|
||||
rm -rf frontend/pkg
|
||||
mv wasm/pkg frontend/pkg
|
||||
|
||||
# Build release server daemon
|
||||
cargo build -p chronoseal-server --bin chronoseal --release</code></pre>
|
||||
</div>
|
||||
<p>The compiled binary is written to <code>target/release/chronoseal</code>.</p>
|
||||
|
||||
<h2>Native Service Installation</h2>
|
||||
<p>The easiest way to deploy ChronoSeal on Linux is using our installer script. This creates a dedicated system user, configures directory paths, copies assets, and sets up systemd sandboxing:</p>
|
||||
<div class="code-block">
|
||||
<div class="code-header">
|
||||
<span class="code-lang">Shell</span>
|
||||
<button class="code-copy-btn">Copy</button>
|
||||
</div>
|
||||
<pre><code>sudo bash scripts/install.sh</code></pre>
|
||||
</div>
|
||||
<p>Verify installation operational status:</p>
|
||||
<div class="code-block">
|
||||
<div class="code-header">
|
||||
<span class="code-lang">Shell</span>
|
||||
<button class="code-copy-btn">Copy</button>
|
||||
</div>
|
||||
<pre><code>sudo systemctl status chronoseal
|
||||
chronoseal health
|
||||
sudo journalctl -u chronoseal -f</code></pre>
|
||||
</div>
|
||||
|
||||
<h2>Configuration Precedence</h2>
|
||||
<p>ChronoSeal resolves configuration variables in this order:
|
||||
<code>CLI parameters</code> > <code>CHRONOSEAL_* Environment Variables</code> > <code>TOML file</code> > <code>Defaults</code>.
|
||||
</p>
|
||||
<p>The TOML configuration is searched at:
|
||||
<code>$CHRONOSEAL_CONFIG</code>, <code>/etc/chronoseal/config.toml</code>, <code>~/.config/chronoseal/config.toml</code>.
|
||||
</p>
|
||||
|
||||
<h3>Common Environment overrides</h3>
|
||||
<ul>
|
||||
<li><code>CHRONOSEAL_BIND</code>: Binding address (e.g. <code>127.0.0.1:3000</code>).</li>
|
||||
<li><code>CHRONOSEAL_DB_TYPE</code>: <code>sqlite-in-memory</code>, <code>sqlite-in-disk</code>, or <code>valkey</code>.</li>
|
||||
<li><code>CHRONOSEAL_VALKEY_ADDR</code>: Address of Valkey server (defaults to <code>127.0.0.1:6666</code>).</li>
|
||||
<li><code>CHRONOSEAL_FRONTEND_DIR</code>: Directory containing frontend static assets.</li>
|
||||
</ul>
|
||||
|
||||
<h2>Reverse Proxy Configuration (Nginx)</h2>
|
||||
<p>Ensure ChronoSeal runs behind TLS in production. Secure proxy traffic to the local daemon port:</p>
|
||||
<div class="code-block">
|
||||
<div class="code-header">
|
||||
<span class="code-lang">Nginx</span>
|
||||
<button class="code-copy-btn">Copy</button>
|
||||
</div>
|
||||
<pre><code>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;
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<h2>Docker Compose Deployment</h2>
|
||||
<p>Build and run the container service (configured for non-root execution by default):</p>
|
||||
<div class="code-block">
|
||||
<div class="code-header">
|
||||
<span class="code-lang">Shell</span>
|
||||
<button class="code-copy-btn">Copy</button>
|
||||
</div>
|
||||
<pre><code>bash scripts/build.sh
|
||||
docker compose up -d --build</code></pre>
|
||||
</div>
|
||||
|
||||
<h2>Production Checklist</h2>
|
||||
<ul>
|
||||
<li>Ensure WASM modules in <code>frontend/pkg</code> are rebuilt with the <code>--release</code> flag.</li>
|
||||
<li>Serve all ChronoSeal endpoints exclusively over HTTPS.</li>
|
||||
<li>Restict server bind address to localhost (<code>127.0.0.1</code>) behind a reverse proxy.</li>
|
||||
<li>Run the daemon service under a dedicated unprivileged user account.</li>
|
||||
<li>Disable debug logging (avoid <code>CHRONOSEAL_LOG=debug</code>) in production to protect credentials.</li>
|
||||
<li>Configure Valkey or persistent SQLite for production session storage.</li>
|
||||
</ul>
|
||||
</article>
|
||||
|
||||
<!-- Pager -->
|
||||
<nav class="doc-pager" aria-label="Doc navigation Pager">
|
||||
<a href="operations.html" class="doc-pager-link">
|
||||
<div class="doc-pager-label">Previous</div>
|
||||
<div class="doc-pager-title">Operations Handbook</div>
|
||||
</a>
|
||||
<a href="testing.html" class="doc-pager-link doc-pager-link--next">
|
||||
<div class="doc-pager-label">Next</div>
|
||||
<div class="doc-pager-title">Testing Strategy</div>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<div class="doc-meta">
|
||||
Last Updated: June 2026 (v1.0.2)
|
||||
</div>
|
||||
</main>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="footer">
|
||||
<div class="footer-content">
|
||||
<div class="footer-section">
|
||||
<h4>
|
||||
<img class="logobar" src="assets/logo.svg" alt="ChronoSeal Logo" style="width: 24px; height: 24px; vertical-align: middle;"> ChronoSeal
|
||||
</h4>
|
||||
<p>Next-generation browser attestation daemon built for the modern web.</p>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h4>Resources</h4>
|
||||
<a href="philosophy.html">Design Philosophy</a>
|
||||
<a href="architecture.html">Architecture</a>
|
||||
<a href="protocol.html">Protocol</a>
|
||||
<a href="api.html">API Reference</a>
|
||||
<a href="comparison.html">Comparison</a>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h4>Community</h4>
|
||||
<a href="https://github.com/thakares/chronoseal-rs" target="_blank" rel="noopener"><i class="fab fa-github"></i> GitHub</a>
|
||||
<a href="deployment.html"><i class="fas fa-rocket"></i> Deployment</a>
|
||||
<a href="testing.html"><i class="fas fa-vial"></i> Testing</a>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h4>Legal & Security</h4>
|
||||
<a href="security.html">Security Policy</a>
|
||||
<a href="privacy.html">Privacy Policy</a>
|
||||
<a href="performance.html">Performance Tuning</a>
|
||||
<a href="operations.html">Operations Guide</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<p>© 2026 ChronoSeal Project. Built with 🦀 Rust and ❤️ for open source.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Search Overlay -->
|
||||
<div class="search-overlay">
|
||||
<div class="search-box">
|
||||
<div class="search-input-wrap">
|
||||
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
|
||||
<input type="text" class="search-input" placeholder="Search documentation... (Esc to close)">
|
||||
<span class="search-kbd">/</span>
|
||||
</div>
|
||||
<div class="search-results"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Back to top -->
|
||||
<a href="#" class="back-to-top" aria-label="Back to top">▲</a>
|
||||
|
||||
<!-- JS Scripts -->
|
||||
<script src="js/search.js"></script>
|
||||
<script src="js/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 192 KiB |
@@ -0,0 +1,43 @@
|
||||
name: chronoseal-www
|
||||
services:
|
||||
chronoseal:
|
||||
cpu_shares: 90
|
||||
command: []
|
||||
container_name: chronoseal-www
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: "33491517440"
|
||||
hostname: chronoseal-www
|
||||
image: nginx:alpine
|
||||
labels:
|
||||
icon: https://github.com/thakares/chronoseal-rs/raw/main/logo/chronoseal.svg
|
||||
networks:
|
||||
default: null
|
||||
ports:
|
||||
- mode: ingress
|
||||
target: 80
|
||||
published: "8383"
|
||||
protocol: tcp
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- type: bind
|
||||
source: /DATA/AppData/chronoseal-www
|
||||
target: /usr/share/nginx/html
|
||||
read_only: true
|
||||
bind:
|
||||
create_host_path: true
|
||||
networks:
|
||||
default:
|
||||
name: chronoseal-www_default
|
||||
x-casaos:
|
||||
author: self
|
||||
category: self
|
||||
hostname: ""
|
||||
icon: https://github.com/thakares/chronoseal-rs/raw/main/logo/chronoseal.svg
|
||||
index: /
|
||||
is_uncontrolled: false
|
||||
port_map: "8383"
|
||||
scheme: http
|
||||
title:
|
||||
custom: chronoseal-www
|
||||
@@ -0,0 +1,127 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.1"
|
||||
id="Layer_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="296.99997mm"
|
||||
viewBox="0 0 1122.5196 793.7008"
|
||||
enable-background="new 0 0 1254 1254"
|
||||
xml:space="preserve"
|
||||
height="210mm"
|
||||
sodipodi:docname="chronoseal.svg"
|
||||
inkscape:export-filename="logo1.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96"
|
||||
inkscape:version="1.4.4 (dcaf3e7d9e, 2026-05-05)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:zoom="1.1654266"
|
||||
inkscape:cx="561.16791"
|
||||
inkscape:cy="396.85039"
|
||||
inkscape:window-width="2048"
|
||||
inkscape:window-height="1205"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="Layer_1" /><defs
|
||||
id="defs44" />
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<path
|
||||
fill="none"
|
||||
opacity="0"
|
||||
stroke="none"
|
||||
d="m 791.89377,632.01366 c 1.04068,-1.03091 1.90903,-2.38956 3.14896,-3.04166 11.48425,-6.03955 22.98721,-12.04745 34.57742,-17.88034 8.52002,-4.28775 14.37558,-10.49713 14.23218,-20.46771 -0.14127,-9.82202 -6.25309,-15.51495 -14.66742,-19.5827 -61.76651,-29.85993 -123.47361,-59.84277 -185.21183,-89.76129 -10.48242,-5.07981 -20.81335,-10.5555 -31.58477,-14.93764 -19.71186,-8.01936 -39.87799,-8.40881 -59.18305,0.84148 -71.77527,34.39209 -143.33667,69.23047 -215.00567,103.84463 -8.40815,4.06091 -14.32166,9.84101 -14.39978,19.74008 -0.079,10.00498 5.52282,16.22797 14.08978,20.54743 12.3277,6.21567 24.51239,12.71484 37.34228,19.41369 7.51429,3.93381 14.42273,7.5896 21.37854,11.15274 31.68775,16.23208 63.40558,32.40548 95.07782,48.66778 14.943,7.6726 29.74964,15.6123 44.73053,23.2091 8.44464,4.2823 16.79547,9.1534 25.75525,11.9324 18.81989,5.8372 37.10712,3.9335 54.83185,-5.5578 25.51599,-13.6634 51.41541,-26.6141 77.2135,-39.7465 30.82071,-15.6894 61.7009,-31.26196 92.58359,-46.82907 1.56006,-0.78641 3.38776,-1.0419 5.09082,-1.54462 z"
|
||||
id="path2" /><path
|
||||
fill="#4b4d51"
|
||||
opacity="1"
|
||||
stroke="none"
|
||||
d="m 791.89376,550.60089 c 1.04068,-1.03091 1.90903,-2.38956 3.14896,-3.04166 11.48425,-6.03955 22.98721,-12.04745 34.57742,-17.88034 8.52002,-4.28775 14.37558,-10.49713 14.23218,-20.46771 -0.14127,-9.82202 -6.25309,-15.51495 -14.66742,-19.5827 -61.76651,-29.85993 -123.47361,-59.84277 -185.21183,-89.76129 -10.48242,-5.07981 -20.81335,-10.5555 -31.58477,-14.93764 -19.71186,-8.01936 -39.87799,-8.40881 -59.18305,0.84148 -71.77522,34.39209 -143.33662,69.23047 -215.00562,103.84463 -8.4082,4.06091 -14.3217,9.84101 -14.3998,19.74008 -0.079,10.00498 5.5228,16.22797 14.0898,20.54743 12.3277,6.21567 24.5123,12.71484 37.3422,19.41369 7.5143,3.93381 14.4228,7.5896 21.3786,11.15274 31.6877,16.23212 63.4056,32.40552 95.0778,48.66782 14.943,7.67261 29.7496,15.61227 44.73052,23.20911 8.44464,4.28232 16.79547,9.15341 25.75525,11.93237 18.81989,5.83722 37.10712,3.93353 54.83185,-5.55777 25.51599,-13.66336 51.41541,-26.61407 77.2135,-39.74655 30.82071,-15.68933 61.7009,-31.26196 92.58359,-46.82907 1.56006,-0.78641 3.38776,-1.0419 5.09082,-1.54462 z"
|
||||
id="path45"
|
||||
style="fill:#e1e1e4;fill-opacity:1" /><path
|
||||
fill="#4b4d51"
|
||||
opacity="1"
|
||||
stroke="none"
|
||||
d="m 791.89377,469.18814 c 1.04068,-1.03091 1.90903,-2.38956 3.14896,-3.04166 11.48425,-6.03955 22.98721,-12.04745 34.57742,-17.88034 8.52002,-4.28775 14.37558,-10.49713 14.23218,-20.46771 -0.14127,-9.82202 -6.25309,-15.51495 -14.66742,-19.5827 C 767.4184,378.3558 705.7113,348.37296 643.97308,318.45444 c -10.48242,-5.07981 -20.81335,-10.5555 -31.58477,-14.93764 -19.71186,-8.01936 -39.87799,-8.40881 -59.18305,0.84148 -71.77527,34.39209 -143.33667,69.23047 -215.00567,103.84463 -8.40815,4.06091 -14.32166,9.84101 -14.39978,19.74008 -0.079,10.00498 5.52282,16.22797 14.08978,20.54743 12.3277,6.21567 24.51239,12.71484 37.34228,19.41369 7.51429,3.93381 14.42273,7.5896 21.37854,11.15274 31.68775,16.23212 63.40558,32.40552 95.07782,48.66782 14.943,7.67261 29.74964,15.61227 44.73053,23.20911 8.44464,4.28232 16.79547,9.15341 25.75525,11.93237 18.81989,5.83722 37.10712,3.93353 54.83185,-5.55777 25.51599,-13.66336 51.41541,-26.61407 77.2135,-39.74655 30.82071,-15.68933 61.7009,-31.26196 92.58359,-46.82907 1.56006,-0.78641 3.38776,-1.0419 5.09082,-1.54462 z"
|
||||
id="path46"
|
||||
style="fill:#b0b2b8;fill-opacity:1" /><path
|
||||
fill="#4b4d51"
|
||||
opacity="1"
|
||||
stroke="none"
|
||||
d="m 791.89377,387.77539 c 1.04068,-1.03091 1.90903,-2.38956 3.14896,-3.04166 11.48425,-6.03955 22.98721,-12.04745 34.57742,-17.88034 8.52002,-4.28775 14.37558,-10.49713 14.23218,-20.46771 -0.14127,-9.82202 -6.25309,-15.51495 -14.66742,-19.5827 -61.76651,-29.85993 -123.47361,-59.84277 -185.21183,-89.76129 -10.48242,-5.07981 -20.81335,-10.5555 -31.58477,-14.93764 -19.71186,-8.01936 -39.87799,-8.40881 -59.18305,0.84148 -71.77527,34.39209 -143.33667,69.23047 -215.00567,103.84463 -8.40815,4.06091 -14.32166,9.84101 -14.39978,19.74008 -0.079,10.00498 5.52282,16.22797 14.08978,20.54743 12.3277,6.21567 24.51239,12.71484 37.34228,19.41369 7.51429,3.93381 14.42273,7.5896 21.37854,11.15274 31.68775,16.23212 63.40558,32.40552 95.07782,48.66782 14.943,7.67261 29.74964,15.61227 44.73053,23.20911 8.44464,4.28232 16.79547,9.15341 25.75525,11.93237 18.81989,5.83722 37.10712,3.93353 54.83185,-5.55777 25.51599,-13.66336 51.41541,-26.61407 77.2135,-39.74655 30.82071,-15.68933 61.7009,-31.26196 92.58359,-46.82907 1.56006,-0.78641 3.38776,-1.0419 5.09082,-1.54462 z"
|
||||
id="path47"
|
||||
style="fill:#7b7e85;fill-opacity:1" /><text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:160px;font-family:'URW Gothic';-inkscape-font-specification:'URW Gothic, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;writing-mode:lr-tb;direction:ltr;fill:#e1e1e4;fill-opacity:1;stroke-width:39.1"
|
||||
x="154.54701"
|
||||
y="550.13623"
|
||||
id="text47"><tspan
|
||||
id="tspan47"
|
||||
x="154.54701"
|
||||
y="550.13623" /><tspan
|
||||
id="tspan48"
|
||||
x="154.54701"
|
||||
y="750.13623"
|
||||
style="font-style:normal;font-variant:normal;font-weight:600;font-stretch:normal;font-size:160px;font-family:'URW Gothic';-inkscape-font-specification:'URW Gothic, Semi-Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:600;font-stretch:normal;font-size:160px;font-family:'URW Gothic';-inkscape-font-specification:'URW Gothic, Semi-Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1f2024;fill-opacity:1"
|
||||
id="tspan49">chrono</tspan>seal</tspan></text><path
|
||||
fill="#4b4d51"
|
||||
opacity="1"
|
||||
stroke="none"
|
||||
d="m 791.89376,306.36264 c 1.04068,-1.03091 1.90903,-2.38956 3.14896,-3.04166 11.48425,-6.03955 22.98721,-12.04745 34.57742,-17.88034 8.52002,-4.28775 14.37558,-10.49713 14.23218,-20.46771 -0.14127,-9.82202 -6.25309,-15.51495 -14.66742,-19.5827 -61.76651,-29.85993 -123.47361,-59.84277 -185.21183,-89.76129 -10.48242,-5.07981 -20.81335,-10.5555 -31.58477,-14.93764 -19.71186,-8.01936 -39.87799,-8.40881 -59.18305,0.84148 -71.77522,34.39209 -143.33662,69.23047 -215.00562,103.84463 -8.4082,4.06091 -14.3217,9.84101 -14.3998,19.74008 -0.079,10.00498 5.5228,16.22797 14.0898,20.54743 12.3277,6.21567 24.5123,12.71484 37.3422,19.41369 7.5143,3.93381 14.4228,7.5896 21.3786,11.15274 31.6877,16.23212 63.4056,32.40552 95.0778,48.66782 14.943,7.67261 29.7496,15.61227 44.73052,23.20911 8.44464,4.28232 16.79547,9.15341 25.75525,11.93237 18.81989,5.83722 37.10712,3.93353 54.83185,-5.55777 25.51599,-13.66336 51.41541,-26.61407 77.2135,-39.74655 30.82071,-15.68933 61.7009,-31.26196 92.58359,-46.82907 1.56006,-0.78641 3.38776,-1.0419 5.09082,-1.54462 z"
|
||||
id="path1"
|
||||
style="fill:#4a4d53;fill-opacity:1" /></svg>
|
||||
|
After Width: | Height: | Size: 8.1 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 594 B |
|
After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" id="Layer_1" x="0px" y="0px" width="296.99997mm" viewBox="0 0 1122.5196 793.7008" enable-background="new 0 0 1254 1254" xml:space="preserve" height="210mm" sodipodi:docname="logo1.svg" inkscape:export-filename="logo1.png" inkscape:export-xdpi="96" inkscape:export-ydpi="96" inkscape:version="1.4.4 (dcaf3e7d9e, 2026-05-05)" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg"><metadata><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:dc="http://purl.org/dc/elements/1.1/"><rdf:Description><dc:creator>RealFaviconGenerator</dc:creator><dc:source>https://realfavicongenerator.net</dc:source></rdf:Description></rdf:RDF></metadata><sodipodi:namedview id="namedview1" pagecolor="#ffffff" bordercolor="#000000" borderopacity="0.25" inkscape:showpageshadow="2" inkscape:pageopacity="0.0" inkscape:pagecheckerboard="0" inkscape:deskcolor="#d1d1d1" inkscape:document-units="mm" inkscape:zoom="1.1654266" inkscape:cx="561.16791" inkscape:cy="396.85039" inkscape:window-width="2048" inkscape:window-height="1205" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1" inkscape:current-layer="Layer_1"></sodipodi:namedview><defs id="defs44"></defs><path fill="none" opacity="1" stroke="none" d="m 791.89377,632.01366 c 1.04068,-1.03091 1.90903,-2.38956 3.14896,-3.04166 11.48425,-6.03955 22.98721,-12.04745 34.57742,-17.88034 8.52002,-4.28775 14.37558,-10.49713 14.23218,-20.46771 -0.14127,-9.82202 -6.25309,-15.51495 -14.66742,-19.5827 -61.76651,-29.85993 -123.47361,-59.84277 -185.21183,-89.76129 -10.48242,-5.07981 -20.81335,-10.5555 -31.58477,-14.93764 -19.71186,-8.01936 -39.87799,-8.40881 -59.18305,0.84148 -71.77527,34.39209 -143.33667,69.23047 -215.00567,103.84463 -8.40815,4.06091 -14.32166,9.84101 -14.39978,19.74008 -0.079,10.00498 5.52282,16.22797 14.08978,20.54743 12.3277,6.21567 24.51239,12.71484 37.34228,19.41369 7.51429,3.93381 14.42273,7.5896 21.37854,11.15274 31.68775,16.23208 63.40558,32.40548 95.07782,48.66778 14.943,7.6726 29.74964,15.6123 44.73053,23.2091 8.44464,4.2823 16.79547,9.1534 25.75525,11.9324 18.81989,5.8372 37.10712,3.9335 54.83185,-5.5578 25.51599,-13.6634 51.41541,-26.6141 77.2135,-39.7465 30.82071,-15.6894 61.7009,-31.26196 92.58359,-46.82907 1.56006,-0.78641 3.38776,-1.0419 5.09082,-1.54462 z" id="path2"></path><path fill="#4b4d51" opacity="1" stroke="none" d="m 791.89376,550.60089 c 1.04068,-1.03091 1.90903,-2.38956 3.14896,-3.04166 11.48425,-6.03955 22.98721,-12.04745 34.57742,-17.88034 8.52002,-4.28775 14.37558,-10.49713 14.23218,-20.46771 -0.14127,-9.82202 -6.25309,-15.51495 -14.66742,-19.5827 -61.76651,-29.85993 -123.47361,-59.84277 -185.21183,-89.76129 -10.48242,-5.07981 -20.81335,-10.5555 -31.58477,-14.93764 -19.71186,-8.01936 -39.87799,-8.40881 -59.18305,0.84148 -71.77522,34.39209 -143.33662,69.23047 -215.00562,103.84463 -8.4082,4.06091 -14.3217,9.84101 -14.3998,19.74008 -0.079,10.00498 5.5228,16.22797 14.0898,20.54743 12.3277,6.21567 24.5123,12.71484 37.3422,19.41369 7.5143,3.93381 14.4228,7.5896 21.3786,11.15274 31.6877,16.23212 63.4056,32.40552 95.0778,48.66782 14.943,7.67261 29.7496,15.61227 44.73052,23.20911 8.44464,4.28232 16.79547,9.15341 25.75525,11.93237 18.81989,5.83722 37.10712,3.93353 54.83185,-5.55777 25.51599,-13.66336 51.41541,-26.61407 77.2135,-39.74655 30.82071,-15.68933 61.7009,-31.26196 92.58359,-46.82907 1.56006,-0.78641 3.38776,-1.0419 5.09082,-1.54462 z" id="path45" style="fill:#e1e1e4;fill-opacity:1"></path><path fill="#4b4d51" opacity="1" stroke="none" d="m 791.89377,469.18814 c 1.04068,-1.03091 1.90903,-2.38956 3.14896,-3.04166 11.48425,-6.03955 22.98721,-12.04745 34.57742,-17.88034 8.52002,-4.28775 14.37558,-10.49713 14.23218,-20.46771 -0.14127,-9.82202 -6.25309,-15.51495 -14.66742,-19.5827 C 767.4184,378.3558 705.7113,348.37296 643.97308,318.45444 c -10.48242,-5.07981 -20.81335,-10.5555 -31.58477,-14.93764 -19.71186,-8.01936 -39.87799,-8.40881 -59.18305,0.84148 -71.77527,34.39209 -143.33667,69.23047 -215.00567,103.84463 -8.40815,4.06091 -14.32166,9.84101 -14.39978,19.74008 -0.079,10.00498 5.52282,16.22797 14.08978,20.54743 12.3277,6.21567 24.51239,12.71484 37.34228,19.41369 7.51429,3.93381 14.42273,7.5896 21.37854,11.15274 31.68775,16.23212 63.40558,32.40552 95.07782,48.66782 14.943,7.67261 29.74964,15.61227 44.73053,23.20911 8.44464,4.28232 16.79547,9.15341 25.75525,11.93237 18.81989,5.83722 37.10712,3.93353 54.83185,-5.55777 25.51599,-13.66336 51.41541,-26.61407 77.2135,-39.74655 30.82071,-15.68933 61.7009,-31.26196 92.58359,-46.82907 1.56006,-0.78641 3.38776,-1.0419 5.09082,-1.54462 z" id="path46" style="fill:#b0b2b8;fill-opacity:1"></path><path fill="#4b4d51" opacity="1" stroke="none" d="m 791.89377,387.77539 c 1.04068,-1.03091 1.90903,-2.38956 3.14896,-3.04166 11.48425,-6.03955 22.98721,-12.04745 34.57742,-17.88034 8.52002,-4.28775 14.37558,-10.49713 14.23218,-20Line truncated
|
||||
|
After Width: | Height: | Size: 8.1 KiB |
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "MyWebSite",
|
||||
"short_name": "MySite",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/web-app-manifest-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "/web-app-manifest-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
}
|
||||
],
|
||||
"theme_color": "#ffffff",
|
||||
"background_color": "#ffffff",
|
||||
"display": "standalone"
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 594 B |
|
After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" id="Layer_1" x="0px" y="0px" width="296.99997mm" viewBox="0 0 1122.5196 793.7008" enable-background="new 0 0 1254 1254" xml:space="preserve" height="210mm" sodipodi:docname="logo1.svg" inkscape:export-filename="logo1.png" inkscape:export-xdpi="96" inkscape:export-ydpi="96" inkscape:version="1.4.4 (dcaf3e7d9e, 2026-05-05)" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg"><metadata><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:dc="http://purl.org/dc/elements/1.1/"><rdf:Description><dc:creator>RealFaviconGenerator</dc:creator><dc:source>https://realfavicongenerator.net</dc:source></rdf:Description></rdf:RDF></metadata><sodipodi:namedview id="namedview1" pagecolor="#ffffff" bordercolor="#000000" borderopacity="0.25" inkscape:showpageshadow="2" inkscape:pageopacity="0.0" inkscape:pagecheckerboard="0" inkscape:deskcolor="#d1d1d1" inkscape:document-units="mm" inkscape:zoom="1.1654266" inkscape:cx="561.16791" inkscape:cy="396.85039" inkscape:window-width="2048" inkscape:window-height="1205" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1" inkscape:current-layer="Layer_1"></sodipodi:namedview><defs id="defs44"></defs><path fill="none" opacity="1" stroke="none" d="m 791.89377,632.01366 c 1.04068,-1.03091 1.90903,-2.38956 3.14896,-3.04166 11.48425,-6.03955 22.98721,-12.04745 34.57742,-17.88034 8.52002,-4.28775 14.37558,-10.49713 14.23218,-20.46771 -0.14127,-9.82202 -6.25309,-15.51495 -14.66742,-19.5827 -61.76651,-29.85993 -123.47361,-59.84277 -185.21183,-89.76129 -10.48242,-5.07981 -20.81335,-10.5555 -31.58477,-14.93764 -19.71186,-8.01936 -39.87799,-8.40881 -59.18305,0.84148 -71.77527,34.39209 -143.33667,69.23047 -215.00567,103.84463 -8.40815,4.06091 -14.32166,9.84101 -14.39978,19.74008 -0.079,10.00498 5.52282,16.22797 14.08978,20.54743 12.3277,6.21567 24.51239,12.71484 37.34228,19.41369 7.51429,3.93381 14.42273,7.5896 21.37854,11.15274 31.68775,16.23208 63.40558,32.40548 95.07782,48.66778 14.943,7.6726 29.74964,15.6123 44.73053,23.2091 8.44464,4.2823 16.79547,9.1534 25.75525,11.9324 18.81989,5.8372 37.10712,3.9335 54.83185,-5.5578 25.51599,-13.6634 51.41541,-26.6141 77.2135,-39.7465 30.82071,-15.6894 61.7009,-31.26196 92.58359,-46.82907 1.56006,-0.78641 3.38776,-1.0419 5.09082,-1.54462 z" id="path2"></path><path fill="#4b4d51" opacity="1" stroke="none" d="m 791.89376,550.60089 c 1.04068,-1.03091 1.90903,-2.38956 3.14896,-3.04166 11.48425,-6.03955 22.98721,-12.04745 34.57742,-17.88034 8.52002,-4.28775 14.37558,-10.49713 14.23218,-20.46771 -0.14127,-9.82202 -6.25309,-15.51495 -14.66742,-19.5827 -61.76651,-29.85993 -123.47361,-59.84277 -185.21183,-89.76129 -10.48242,-5.07981 -20.81335,-10.5555 -31.58477,-14.93764 -19.71186,-8.01936 -39.87799,-8.40881 -59.18305,0.84148 -71.77522,34.39209 -143.33662,69.23047 -215.00562,103.84463 -8.4082,4.06091 -14.3217,9.84101 -14.3998,19.74008 -0.079,10.00498 5.5228,16.22797 14.0898,20.54743 12.3277,6.21567 24.5123,12.71484 37.3422,19.41369 7.5143,3.93381 14.4228,7.5896 21.3786,11.15274 31.6877,16.23212 63.4056,32.40552 95.0778,48.66782 14.943,7.67261 29.7496,15.61227 44.73052,23.20911 8.44464,4.28232 16.79547,9.15341 25.75525,11.93237 18.81989,5.83722 37.10712,3.93353 54.83185,-5.55777 25.51599,-13.66336 51.41541,-26.61407 77.2135,-39.74655 30.82071,-15.68933 61.7009,-31.26196 92.58359,-46.82907 1.56006,-0.78641 3.38776,-1.0419 5.09082,-1.54462 z" id="path45" style="fill:#e1e1e4;fill-opacity:1"></path><path fill="#4b4d51" opacity="1" stroke="none" d="m 791.89377,469.18814 c 1.04068,-1.03091 1.90903,-2.38956 3.14896,-3.04166 11.48425,-6.03955 22.98721,-12.04745 34.57742,-17.88034 8.52002,-4.28775 14.37558,-10.49713 14.23218,-20.46771 -0.14127,-9.82202 -6.25309,-15.51495 -14.66742,-19.5827 C 767.4184,378.3558 705.7113,348.37296 643.97308,318.45444 c -10.48242,-5.07981 -20.81335,-10.5555 -31.58477,-14.93764 -19.71186,-8.01936 -39.87799,-8.40881 -59.18305,0.84148 -71.77527,34.39209 -143.33667,69.23047 -215.00567,103.84463 -8.40815,4.06091 -14.32166,9.84101 -14.39978,19.74008 -0.079,10.00498 5.52282,16.22797 14.08978,20.54743 12.3277,6.21567 24.51239,12.71484 37.34228,19.41369 7.51429,3.93381 14.42273,7.5896 21.37854,11.15274 31.68775,16.23212 63.40558,32.40552 95.07782,48.66782 14.943,7.67261 29.74964,15.61227 44.73053,23.20911 8.44464,4.28232 16.79547,9.15341 25.75525,11.93237 18.81989,5.83722 37.10712,3.93353 54.83185,-5.55777 25.51599,-13.66336 51.41541,-26.61407 77.2135,-39.74655 30.82071,-15.68933 61.7009,-31.26196 92.58359,-46.82907 1.56006,-0.78641 3.38776,-1.0419 5.09082,-1.54462 z" id="path46" style="fill:#b0b2b8;fill-opacity:1"></path><path fill="#4b4d51" opacity="1" stroke="none" d="m 791.89377,387.77539 c 1.04068,-1.03091 1.90903,-2.38956 3.14896,-3.04166 11.48425,-6.03955 22.98721,-12.04745 34.57742,-17.88034 8.52002,-4.28775 14.37558,-10.49713 14.23218,-20Line truncated
|
||||
|
After Width: | Height: | Size: 8.1 KiB |
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "MyWebSite",
|
||||
"short_name": "MySite",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/web-app-manifest-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "/web-app-manifest-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
}
|
||||
],
|
||||
"theme_color": "#ffffff",
|
||||
"background_color": "#ffffff",
|
||||
"display": "standalone"
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
@@ -0,0 +1,424 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
||||
<title>ChronoSeal | Browser Attestation Daemon</title>
|
||||
<meta name="description" content="ChronoSeal is a Unix-native browser attestation daemon combining cryptographic signatures, deterministic state machines, and a synthetic gene mutation engine for session integrity.">
|
||||
<link rel="stylesheet" href="css/chronoseal.css">
|
||||
<link rel="stylesheet" href="css/print.css" media="print">
|
||||
<link rel="manifest" href="site.webmanifest">
|
||||
<link rel="icon" href="assets/logo.svg" type="image/svg+xml">
|
||||
|
||||
<!-- Font Awesome for Icons -->
|
||||
<link rel="preconnect" href="https://cdnjs.cloudflare.com" crossorigin />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
|
||||
integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw=="
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Floating Background Spheres -->
|
||||
<div class="bg-animation">
|
||||
<div class="gradient-sphere sphere-1"></div>
|
||||
<div class="gradient-sphere sphere-2"></div>
|
||||
<div class="gradient-sphere sphere-3"></div>
|
||||
</div>
|
||||
|
||||
<!-- Header / Navbar -->
|
||||
<nav class="navbar" id="navbar">
|
||||
<div class="nav-container">
|
||||
<a href="index.html" class="logo">
|
||||
<img class="logobar" src="assets/logo.svg" alt="ChronoSeal Logo">
|
||||
<span>ChronoSeal</span>
|
||||
</a>
|
||||
<div class="nav-links" id="navLinks">
|
||||
<a href="philosophy.html">Philosophy</a>
|
||||
<a href="architecture.html">Architecture</a>
|
||||
<a href="protocol.html">Protocol</a>
|
||||
<a href="comparison.html">Comparison</a>
|
||||
<div class="nav-dropdown">
|
||||
<a href="#" class="nav-link">More <small>▼</small></a>
|
||||
<div class="nav-dropdown-menu">
|
||||
<a href="api.html">API Reference</a>
|
||||
<a href="deployment.html">Deployment Guide</a>
|
||||
<a href="operations.html">Operations Guide</a>
|
||||
<a href="testing.html">Testing Strategy</a>
|
||||
<a href="performance.html">Performance Tuning</a>
|
||||
<a href="threat-model.html">Threat Model</a>
|
||||
<a href="security.html">Security Policy</a>
|
||||
<a href="privacy.html">Privacy Policy</a>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-ghost btn-sm btn-icon search-trigger" aria-label="Search" onclick="ChronoSearch.open()">
|
||||
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" width="16" height="16"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
|
||||
</button>
|
||||
<a href="https://github.com/thakares/chronoseal-rs" class="github-btn" target="_blank" rel="noopener">
|
||||
<i class="fab fa-github"></i> GitHub
|
||||
</a>
|
||||
</div>
|
||||
<button class="nav-toggle" id="menuBtn" aria-label="Toggle Navigation">
|
||||
<i class="fas fa-bars"></i>
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="main-content">
|
||||
|
||||
<!-- Hero Section -->
|
||||
<section id="home" class="hero animate">
|
||||
<div class="hero-badge">
|
||||
<i class="fas fa-code-branch"></i> v1.0.2 · Production Hardened
|
||||
</div>
|
||||
<h1>Browser Attestation<br>Reimagined</h1>
|
||||
<p class="hero-subtitle">
|
||||
ChronoSeal is a Unix-native browser attestation daemon combining cryptographic signatures,
|
||||
deterministic state machines, and a synthetic gene mutation engine for unparalleled session integrity.
|
||||
</p>
|
||||
<div class="hero-buttons">
|
||||
<a href="deployment.html" class="btn btn-primary">
|
||||
<i class="fas fa-terminal"></i> Get Started
|
||||
</a>
|
||||
<a href="https://github.com/thakares/chronoseal-rs" class="btn btn-secondary" target="_blank" rel="noopener">
|
||||
<i class="fab fa-github"></i> View on GitHub
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Stats Grid -->
|
||||
<div class="stats-grid animate">
|
||||
<div class="stat-card">
|
||||
<div class="stat-number">100</div>
|
||||
<div class="stat-label">Tests Passing</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-number">3</div>
|
||||
<div class="stat-label">Storage Backends</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-number">10</div>
|
||||
<div class="stat-label">VM Opcodes</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-number">30s</div>
|
||||
<div class="stat-label">Max Drift</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Core Features Section -->
|
||||
<section id="features" class="section animate">
|
||||
<div class="section-header">
|
||||
<h2>Core Features</h2>
|
||||
<p>Everything you need for robust, enterprise-grade browser attestation</p>
|
||||
</div>
|
||||
<div class="cards-grid">
|
||||
<div class="card">
|
||||
<div class="card-icon"><i class="fas fa-dna"></i></div>
|
||||
<h3>Synthetic Gene Engine</h3>
|
||||
<p>Deterministic mutation sequence that both server and browser WASM must execute in sync—creating an unprecedented second state channel.</p>
|
||||
<div class="card-tags">
|
||||
<span class="tag">Mutation Rounds</span>
|
||||
<span class="tag">Gene Size 512-4096</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-icon"><i class="fas fa-link"></i></div>
|
||||
<h3>Cryptographic Chain</h3>
|
||||
<p>Ed25519 signatures and a Blake3 hash chain progression with rotating salts ensures replay resistance and continuity verification.</p>
|
||||
<div class="card-tags">
|
||||
<span class="tag">Ed25519</span>
|
||||
<span class="tag">Blake3</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-icon"><i class="fas fa-microchip"></i></div>
|
||||
<h3>Deterministic VM</h3>
|
||||
<p>A lightweight 10-opcode virtual machine executing server-issued programs with exact stack state verification between client and server.</p>
|
||||
<div class="card-tags">
|
||||
<span class="tag">Stack Verification</span>
|
||||
<span class="tag">Custom Instruction Set</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-icon"><i class="fas fa-user-secret"></i></div>
|
||||
<h3>Silent Rejection</h3>
|
||||
<p>Failed heartbeats return identical HTTP 200 responses—providing zero oracle feedback and making automated probing impossible.</p>
|
||||
<div class="card-tags">
|
||||
<span class="tag">No Oracle</span>
|
||||
<span class="tag">Security First</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-icon"><i class="fas fa-database"></i></div>
|
||||
<h3>Pluggable Storage</h3>
|
||||
<p>Use SQLite in-memory for testing, SQLite disk for standalone, or Valkey/Redis for massive distributed cluster deployments.</p>
|
||||
<div class="card-tags">
|
||||
<span class="tag">SQLite</span>
|
||||
<span class="tag">Valkey/Redis</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-icon"><i class="fas fa-server"></i></div>
|
||||
<h3>Production Ready</h3>
|
||||
<p>Built-in Prometheus metrics, liveness/readiness probes, structured logging, systemd integration, and graceful shutdown.</p>
|
||||
<div class="card-tags">
|
||||
<span class="tag">Prometheus</span>
|
||||
<span class="tag">Observability</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Architecture Section -->
|
||||
<section id="architecture-sec" class="section animate">
|
||||
<div class="section-header">
|
||||
<h2>System Architecture</h2>
|
||||
<p>A look inside how ChronoSeal protects your sessions in real-time</p>
|
||||
</div>
|
||||
<div class="architecture-container">
|
||||
<!-- Inline diagram container dynamically drawn by diagrams.js -->
|
||||
<div id="diagram-architecture" class="arch-diagram"></div>
|
||||
</div>
|
||||
<div class="text-center" style="margin-top:24px">
|
||||
<a href="architecture.html" class="btn btn-secondary btn-sm">Read Architecture Docs</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Protocol Section -->
|
||||
<section id="protocol-sec" class="section animate">
|
||||
<div class="section-header">
|
||||
<h2>Protocol Flow</h2>
|
||||
<p>Secure handshake and continuous background verification</p>
|
||||
</div>
|
||||
<div class="code-block code-block-center">
|
||||
<div class="code-header">
|
||||
<span class="code-lang">Sequence Flow Diagram</span>
|
||||
<button class="code-copy-btn">Copy</button>
|
||||
</div>
|
||||
<pre><code>┌─────────────┐ ┌─────────────┐
|
||||
│ Browser │ │ Server │
|
||||
│ (WASM) │ │ (ChronoSeal)│
|
||||
└──────┬──────┘ └──────┬──────┘
|
||||
│ │
|
||||
│ POST /init { public_key } │
|
||||
│─────────────────────────────────────────────────>│
|
||||
│ │
|
||||
│ 200 { session_id, salt, opcodes, │
|
||||
│ initial_hash, mutation_order } │
|
||||
│<─────────────────────────────────────────────────│
|
||||
│ │
|
||||
│ [Execute VM, Preview Mutation] │
|
||||
│ │
|
||||
│ POST /hb { prev_hash, timestamp, entropy, │
|
||||
│ stack_state, gene_commitment, │
|
||||
│ signature } │
|
||||
│─────────────────────────────────────────────────>│
|
||||
│ │
|
||||
│ [Verify: Signature → Hash → Mutation → Drift] │
|
||||
│ │
|
||||
│ 200 { status: "ok", next_salt, │
|
||||
│ next_mutation_step, next_order } │
|
||||
│<─────────────────────────────────────────────────│
|
||||
│ │
|
||||
│ [Commit Preview, Rotate State] │
|
||||
│ │
|
||||
▼ ▼</code></pre>
|
||||
</div>
|
||||
<div class="text-center" style="margin-top:24px">
|
||||
<a href="protocol.html" class="btn btn-secondary btn-sm">Read Protocol Docs</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Comparison Section -->
|
||||
<section id="comparison-sec" class="section animate">
|
||||
<div class="section-header">
|
||||
<h2>vs Popular Solutions</h2>
|
||||
<p>Why modern privacy-conscious teams choose ChronoSeal</p>
|
||||
</div>
|
||||
<div class="comparison-table-wrap">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Feature</th>
|
||||
<th>ChronoSeal</th>
|
||||
<th>Cloudflare Turnstile</th>
|
||||
<th>reCAPTCHA v3</th>
|
||||
<th>Enterprise WAFs</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Self-hosted & Air-gapped</td>
|
||||
<td class="check"><i class="fas fa-check-circle"></i> Yes</td>
|
||||
<td class="times"><i class="fas fa-times-circle"></i> No</td>
|
||||
<td class="times"><i class="fas fa-times-circle"></i> No</td>
|
||||
<td class="times"><i class="fas fa-times-circle"></i> No</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Privacy Focused</td>
|
||||
<td class="check"><i class="fas fa-check-circle"></i> Excellent</td>
|
||||
<td class="times"><i class="fas fa-times-circle"></i> Medium</td>
|
||||
<td class="times"><i class="fas fa-times-circle"></i> Poor</td>
|
||||
<td class="times"><i class="fas fa-times-circle"></i> Low</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Cryptographic Continuity</td>
|
||||
<td class="check"><i class="fas fa-check-circle"></i> Very High</td>
|
||||
<td class="times"><i class="fas fa-times-circle"></i> Medium</td>
|
||||
<td class="times"><i class="fas fa-times-circle"></i> Low</td>
|
||||
<td class="times"><i class="fas fa-times-circle"></i> Medium</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Cost Structure</td>
|
||||
<td class="check"><i class="fas fa-check-circle"></i> Free (FOSS)</td>
|
||||
<td class="times"><i class="fas fa-times-circle"></i> Freemium</td>
|
||||
<td class="times"><i class="fas fa-times-circle"></i> Free → Paid</td>
|
||||
<td class="times"><i class="fas fa-times-circle"></i> Extremely High</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>WASM Mutation Engine</td>
|
||||
<td class="check"><i class="fas fa-check-circle"></i> Unique</td>
|
||||
<td class="times"><i class="fas fa-times-circle"></i> No</td>
|
||||
<td class="times"><i class="fas fa-times-circle"></i> No</td>
|
||||
<td class="times"><i class="fas fa-times-circle"></i> No</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Silent Rejection Architecture</td>
|
||||
<td class="check"><i class="fas fa-check-circle"></i> Yes</td>
|
||||
<td class="times"><i class="fas fa-times-circle"></i> No</td>
|
||||
<td class="times"><i class="fas fa-times-circle"></i> No</td>
|
||||
<td class="times"><i class="fas fa-times-circle"></i> No</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="text-center" style="margin-top:24px">
|
||||
<a href="comparison.html" class="btn btn-secondary btn-sm">Detailed Comparison</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Quick Deployment Section -->
|
||||
<section id="deployment-sec" class="section animate">
|
||||
<div class="section-header">
|
||||
<h2>Quick Deployment</h2>
|
||||
<p>Spin up the daemon in under a minute</p>
|
||||
</div>
|
||||
<div class="cards-grid">
|
||||
<div class="card">
|
||||
<div class="card-icon"><i class="fab fa-docker"></i></div>
|
||||
<h3>Docker</h3>
|
||||
<div class="code-block" style="margin-top: 1rem; border: none; padding: 1rem; background: rgba(0,0,0,0.3);">
|
||||
<pre><code>docker run -d -p 3000:3000 \
|
||||
chronoseal/chronoseal:latest</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-icon"><i class="fas fa-cubes"></i></div>
|
||||
<h3>Docker Compose</h3>
|
||||
<div class="code-block" style="margin-top: 1rem; border: none; padding: 1rem; background: rgba(0,0,0,0.3);">
|
||||
<pre><code>bash scripts/build.sh
|
||||
docker compose up -d --build</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-icon"><i class="fab fa-linux"></i></div>
|
||||
<h3>Native (systemd)</h3>
|
||||
<div class="code-block" style="margin-top: 1rem; border: none; padding: 1rem; background: rgba(0,0,0,0.3);">
|
||||
<pre><code>curl -sL https://chronoseal.io/install.sh | sudo bash
|
||||
sudo systemctl enable --now chronoseal</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-icon"><i class="fas fa-terminal"></i></div>
|
||||
<h3>From Source</h3>
|
||||
<div class="code-block" style="margin-top: 1rem; border: none; padding: 1rem; background: rgba(0,0,0,0.3);">
|
||||
<pre><code>git clone https://github.com/thakares/chronoseal-rs
|
||||
cd chronoseal && cargo run --release</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-icon"><i class="fas fa-network-wired"></i></div>
|
||||
<h3>Nginx Proxy</h3>
|
||||
<div class="code-block" style="margin-top: 1rem; border: none; padding: 1rem; background: rgba(0,0,0,0.3);">
|
||||
<pre><code>location / {
|
||||
proxy_pass http://127.0.0.1:3000;
|
||||
proxy_http_version 1.1;
|
||||
}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-icon"><i class="fas fa-heartbeat"></i></div>
|
||||
<h3>Verification</h3>
|
||||
<div class="code-block" style="margin-top: 1rem; border: none; padding: 1rem; background: rgba(0,0,0,0.3);">
|
||||
<pre><code># Check daemon health status
|
||||
curl http://localhost:3000/health
|
||||
|
||||
# Query daemon CLI metrics
|
||||
chronoseal status --format json</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="footer">
|
||||
<div class="footer-content">
|
||||
<div class="footer-section">
|
||||
<h4>
|
||||
<img class="logobar" src="assets/logo.svg" alt="ChronoSeal Logo" style="width: 24px; height: 24px; vertical-align: middle;"> ChronoSeal
|
||||
</h4>
|
||||
<p>Next-generation browser attestation daemon built for the modern web.</p>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h4>Resources</h4>
|
||||
<a href="philosophy.html">Design Philosophy</a><br/>
|
||||
<a href="architecture.html">Architecture</a><br/>
|
||||
<a href="protocol.html">Protocol</a><br/>
|
||||
<a href="api.html">API Reference</a><br/>
|
||||
<a href="comparison.html">Comparison</a>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h4>Community</h4>
|
||||
<a href="https://github.com/thakares/chronoseal-rs" target="_blank" rel="noopener"><i class="fab fa-github"></i> GitHub</a><br/>
|
||||
<a href="deployment.html"><i class="fas fa-rocket"></i> Deployment</a><br/>
|
||||
<a href="testing.html"><i class="fas fa-vial"></i> Testing</a><br/>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h4>Legal & Security</h4>
|
||||
<a href="security.html">Security Policy</a>
|
||||
<a href="privacy.html">Privacy Policy</a>
|
||||
<a href="performance.html">Performance Tuning</a>
|
||||
<a href="operations.html">Operations Guide</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<p>© 2026 ChronoSeal Project. Built with 🦀 Rust and ❤️ for open source.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Search Overlay -->
|
||||
<div class="search-overlay">
|
||||
<div class="search-box">
|
||||
<div class="search-input-wrap">
|
||||
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
|
||||
<input type="text" class="search-input" placeholder="Search documentation... (Esc to close)">
|
||||
<span class="search-kbd">/</span>
|
||||
</div>
|
||||
<div class="search-results"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Back to top -->
|
||||
<a href="#" class="back-to-top" aria-label="Back to top">▲</a>
|
||||
|
||||
<!-- JS Scripts -->
|
||||
<script src="js/search.js"></script>
|
||||
<script src="js/app.js"></script>
|
||||
<script src="js/pwa.js"></script>
|
||||
<script src="js/diagrams.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,184 @@
|
||||
/* ============================================================
|
||||
ChronoSeal Website — app.js
|
||||
Core runtime: header scroll, mobile nav, scroll animations,
|
||||
code copy, back-to-top, keyboard shortcuts
|
||||
============================================================ */
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
/* ---- Header scroll state ---- */
|
||||
var header = document.querySelector('.navbar');
|
||||
if (header) {
|
||||
var onScroll = function () {
|
||||
header.classList.toggle('scrolled', window.scrollY > 20);
|
||||
};
|
||||
window.addEventListener('scroll', onScroll, { passive: true });
|
||||
onScroll();
|
||||
}
|
||||
|
||||
/* ---- Enhanced Mobile nav toggle ---- */
|
||||
var toggle = document.querySelector('.nav-toggle');
|
||||
var navLinks = document.querySelector('.nav-links');
|
||||
if (toggle && navLinks) {
|
||||
var menuIcon = toggle.querySelector('i');
|
||||
toggle.addEventListener('click', function () {
|
||||
var isActive = navLinks.classList.contains('active');
|
||||
if (isActive) {
|
||||
navLinks.classList.remove('active');
|
||||
if (menuIcon) {
|
||||
menuIcon.classList.remove('fa-times');
|
||||
menuIcon.classList.add('fa-bars');
|
||||
}
|
||||
} else {
|
||||
navLinks.classList.add('active');
|
||||
if (menuIcon) {
|
||||
menuIcon.classList.remove('fa-bars');
|
||||
menuIcon.classList.add('fa-times');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Close nav on link click
|
||||
navLinks.querySelectorAll('a').forEach(function (a) {
|
||||
a.addEventListener('click', function () {
|
||||
navLinks.classList.remove('active');
|
||||
if (menuIcon) {
|
||||
menuIcon.classList.remove('fa-times');
|
||||
menuIcon.classList.add('fa-bars');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/* ---- Doc sidebar mobile toggle ---- */
|
||||
var sidebarToggle = document.querySelector('.doc-sidebar-toggle');
|
||||
var sidebar = document.querySelector('.doc-sidebar');
|
||||
if (sidebarToggle && sidebar) {
|
||||
sidebarToggle.addEventListener('click', function () {
|
||||
sidebar.classList.toggle('open');
|
||||
});
|
||||
// Close sidebar on link click (mobile)
|
||||
sidebar.querySelectorAll('.doc-nav-item').forEach(function (a) {
|
||||
a.addEventListener('click', function () {
|
||||
if (window.innerWidth <= 900) sidebar.classList.remove('open');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/* ---- Intersection Observer for Scroll Animations ---- */
|
||||
if ('IntersectionObserver' in window) {
|
||||
var observerOptions = { threshold: 0.1, rootMargin: '0px 0px -50px 0px' };
|
||||
var observer = new IntersectionObserver(function (entries) {
|
||||
entries.forEach(function (entry) {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.classList.add('in-view');
|
||||
observer.unobserve(entry.target);
|
||||
}
|
||||
});
|
||||
}, observerOptions);
|
||||
|
||||
// Observe all animated elements
|
||||
document.querySelectorAll('.animate, .card, .stat-card, .step').forEach(function (el) {
|
||||
if (!el.classList.contains('animate')) {
|
||||
el.classList.add('animate');
|
||||
}
|
||||
observer.observe(el);
|
||||
});
|
||||
}
|
||||
|
||||
/* ---- Code block copy buttons ---- */
|
||||
document.querySelectorAll('.code-copy-btn').forEach(function (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
var pre = btn.closest('.code-block').querySelector('pre');
|
||||
if (!pre) return;
|
||||
var text = pre.textContent;
|
||||
if (navigator.clipboard) {
|
||||
navigator.clipboard.writeText(text).then(function () {
|
||||
btn.textContent = 'Copied!';
|
||||
setTimeout(function () { btn.textContent = 'Copy'; }, 1500);
|
||||
});
|
||||
} else {
|
||||
// Fallback
|
||||
var ta = document.createElement('textarea');
|
||||
ta.value = text;
|
||||
ta.style.cssText = 'position:fixed;left:-999px';
|
||||
document.body.appendChild(ta);
|
||||
ta.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(ta);
|
||||
btn.textContent = 'Copied!';
|
||||
setTimeout(function () { btn.textContent = 'Copy'; }, 1500);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/* ---- Back to top ---- */
|
||||
var btt = document.querySelector('.back-to-top');
|
||||
if (btt) {
|
||||
window.addEventListener('scroll', function () {
|
||||
btt.classList.toggle('visible', window.scrollY > 400);
|
||||
}, { passive: true });
|
||||
btt.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
});
|
||||
}
|
||||
|
||||
/* ---- Keyboard shortcut: / or Ctrl+K for search ---- */
|
||||
document.addEventListener('keydown', function (e) {
|
||||
if (e.key === '/' && !isInput(e.target)) {
|
||||
e.preventDefault();
|
||||
if (typeof ChronoSearch !== 'undefined') ChronoSearch.open();
|
||||
}
|
||||
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
|
||||
e.preventDefault();
|
||||
if (typeof ChronoSearch !== 'undefined') ChronoSearch.open();
|
||||
}
|
||||
if (e.key === 'Escape') {
|
||||
if (typeof ChronoSearch !== 'undefined') ChronoSearch.close();
|
||||
}
|
||||
});
|
||||
|
||||
function isInput(el) {
|
||||
var tag = el.tagName;
|
||||
return tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || el.isContentEditable;
|
||||
}
|
||||
|
||||
/* ---- Nav dropdown (desktop hover + mobile tap) ---- */
|
||||
document.querySelectorAll('.nav-dropdown').forEach(function (dd) {
|
||||
var trigger = dd.querySelector('.nav-link');
|
||||
if (!trigger) return;
|
||||
trigger.addEventListener('click', function (e) {
|
||||
if (window.innerWidth <= 768) {
|
||||
e.preventDefault();
|
||||
dd.classList.toggle('open');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/* ---- Active nav link highlight ---- */
|
||||
var currentPage = window.location.pathname.split('/').pop() || 'index.html';
|
||||
document.querySelectorAll('.nav-links a, .doc-nav-item').forEach(function (link) {
|
||||
var href = link.getAttribute('href');
|
||||
if (!href) return;
|
||||
var linkPage = href.split('/').pop().split('#')[0] || 'index.html';
|
||||
if (linkPage === currentPage) {
|
||||
link.classList.add('active');
|
||||
}
|
||||
});
|
||||
|
||||
/* ---- Smooth anchor scroll ---- */
|
||||
document.querySelectorAll('a[href^="#"]').forEach(function (a) {
|
||||
a.addEventListener('click', function (e) {
|
||||
var id = a.getAttribute('href').substring(1);
|
||||
var target = document.getElementById(id);
|
||||
if (target) {
|
||||
e.preventDefault();
|
||||
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
history.pushState(null, '', '#' + id);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
})();
|
||||
@@ -0,0 +1,160 @@
|
||||
/* ============================================================
|
||||
ChronoSeal Website — diagrams.js
|
||||
Generates SVG architecture & protocol flow diagrams inline
|
||||
============================================================ */
|
||||
|
||||
var ChronoDiagrams = (function () {
|
||||
'use strict';
|
||||
|
||||
function drawArchitecture(containerId) {
|
||||
var el = document.getElementById(containerId);
|
||||
if (!el) return;
|
||||
|
||||
var svg = '<svg viewBox="0 0 760 420" fill="none" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;max-width:760px">';
|
||||
|
||||
/* Background */
|
||||
svg += '<rect width="760" height="420" rx="12" fill="#131620"/>';
|
||||
|
||||
/* Browser box */
|
||||
svg += '<rect x="40" y="30" width="280" height="170" rx="8" fill="#1e2230" stroke="#2a2f40" stroke-width="1.5"/>';
|
||||
svg += '<text x="180" y="55" fill="#b0b2b8" font-size="11" font-weight="700" text-anchor="middle" font-family="Inter,sans-serif">BROWSER</text>';
|
||||
|
||||
svg += '<rect x="60" y="70" width="110" height="36" rx="5" fill="#252a3a" stroke="#2a2f40"/>';
|
||||
svg += '<text x="115" y="93" fill="#e1e1e4" font-size="10" text-anchor="middle" font-family="Inter,sans-serif">WASM Runtime</text>';
|
||||
|
||||
svg += '<rect x="190" y="70" width="110" height="36" rx="5" fill="#252a3a" stroke="#2a2f40"/>';
|
||||
svg += '<text x="245" y="93" fill="#e1e1e4" font-size="10" text-anchor="middle" font-family="Inter,sans-serif">heartbeat.js</text>';
|
||||
|
||||
svg += '<rect x="60" y="120" width="110" height="36" rx="5" fill="#252a3a" stroke="#2a2f40"/>';
|
||||
svg += '<text x="115" y="143" fill="#e1e1e4" font-size="10" text-anchor="middle" font-family="Inter,sans-serif">Ed25519 Keys</text>';
|
||||
|
||||
svg += '<rect x="190" y="120" width="110" height="36" rx="5" fill="#252a3a" stroke="#2a2f40"/>';
|
||||
svg += '<text x="245" y="143" fill="#e1e1e4" font-size="10" text-anchor="middle" font-family="Inter,sans-serif">Gene Engine</text>';
|
||||
|
||||
svg += '<text x="180" y="185" fill="#6b6f7a" font-size="9" text-anchor="middle" font-family="Inter,sans-serif">Entropy · Signing · VM · Mutation</text>';
|
||||
|
||||
/* Arrow: Browser → Server */
|
||||
svg += '<line x1="320" y1="115" x2="430" y2="115" stroke="#7b7e85" stroke-width="1.5" stroke-dasharray="6 3"/>';
|
||||
svg += '<polygon points="428,110 438,115 428,120" fill="#7b7e85"/>';
|
||||
svg += '<text x="375" y="105" fill="#6b6f7a" font-size="8" text-anchor="middle" font-family="Inter,sans-serif">POST /hb</text>';
|
||||
svg += '<text x="375" y="135" fill="#6b6f7a" font-size="8" text-anchor="middle" font-family="Inter,sans-serif">POST /init</text>';
|
||||
|
||||
/* Server box */
|
||||
svg += '<rect x="440" y="30" width="280" height="170" rx="8" fill="#1e2230" stroke="#2a2f40" stroke-width="1.5"/>';
|
||||
svg += '<text x="580" y="55" fill="#b0b2b8" font-size="11" font-weight="700" text-anchor="middle" font-family="Inter,sans-serif">SERVER (chronoseal)</text>';
|
||||
|
||||
svg += '<rect x="460" y="70" width="110" height="36" rx="5" fill="#252a3a" stroke="#2a2f40"/>';
|
||||
svg += '<text x="515" y="93" fill="#e1e1e4" font-size="10" text-anchor="middle" font-family="Inter,sans-serif">Axum Router</text>';
|
||||
|
||||
svg += '<rect x="590" y="70" width="110" height="36" rx="5" fill="#252a3a" stroke="#2a2f40"/>';
|
||||
svg += '<text x="645" y="93" fill="#e1e1e4" font-size="10" text-anchor="middle" font-family="Inter,sans-serif">Rate Limiter</text>';
|
||||
|
||||
svg += '<rect x="460" y="120" width="110" height="36" rx="5" fill="#252a3a" stroke="#2a2f40"/>';
|
||||
svg += '<text x="515" y="143" fill="#e1e1e4" font-size="10" text-anchor="middle" font-family="Inter,sans-serif">Verifier</text>';
|
||||
|
||||
svg += '<rect x="590" y="120" width="110" height="36" rx="5" fill="#252a3a" stroke="#2a2f40"/>';
|
||||
svg += '<text x="645" y="143" fill="#e1e1e4" font-size="10" text-anchor="middle" font-family="Inter,sans-serif">Gene Engine</text>';
|
||||
|
||||
svg += '<text x="580" y="185" fill="#6b6f7a" font-size="9" text-anchor="middle" font-family="Inter,sans-serif">Verify · Advance · Trust · CAS</text>';
|
||||
|
||||
/* Shared box (bottom center) */
|
||||
svg += '<rect x="230" y="240" width="300" height="60" rx="8" fill="#1e2230" stroke="#b0b2b8" stroke-width="1" stroke-dasharray="4 3"/>';
|
||||
svg += '<text x="380" y="266" fill="#b0b2b8" font-size="11" font-weight="700" text-anchor="middle" font-family="Inter,sans-serif">shared crate</text>';
|
||||
svg += '<text x="380" y="284" fill="#6b6f7a" font-size="9" text-anchor="middle" font-family="Inter,sans-serif">Protocol · Hashing · Gene · VM · Constants</text>';
|
||||
|
||||
/* Arrows to shared */
|
||||
svg += '<line x1="180" y1="200" x2="310" y2="244" stroke="#7b7e85" stroke-width="1" stroke-dasharray="4 3"/>';
|
||||
svg += '<line x1="580" y1="200" x2="450" y2="244" stroke="#7b7e85" stroke-width="1" stroke-dasharray="4 3"/>';
|
||||
|
||||
/* Storage box */
|
||||
svg += '<rect x="440" y="340" width="280" height="55" rx="8" fill="#1e2230" stroke="#2a2f40" stroke-width="1.5"/>';
|
||||
svg += '<text x="580" y="365" fill="#b0b2b8" font-size="11" font-weight="700" text-anchor="middle" font-family="Inter,sans-serif">STORAGE</text>';
|
||||
svg += '<text x="580" y="382" fill="#6b6f7a" font-size="9" text-anchor="middle" font-family="Inter,sans-serif">SQLite In-Memory · SQLite Disk · Valkey</text>';
|
||||
|
||||
/* Server → Storage arrow */
|
||||
svg += '<line x1="580" y1="200" x2="580" y2="340" stroke="#7b7e85" stroke-width="1.5" stroke-dasharray="6 3"/>';
|
||||
svg += '<polygon points="575,338 580,348 585,338" fill="#7b7e85"/>';
|
||||
|
||||
/* CLI box */
|
||||
svg += '<rect x="40" y="340" width="170" height="55" rx="8" fill="#1e2230" stroke="#2a2f40" stroke-width="1.5"/>';
|
||||
svg += '<text x="125" y="365" fill="#b0b2b8" font-size="11" font-weight="700" text-anchor="middle" font-family="Inter,sans-serif">CLI</text>';
|
||||
svg += '<text x="125" y="382" fill="#6b6f7a" font-size="9" text-anchor="middle" font-family="Inter,sans-serif">status · health · metrics · stats</text>';
|
||||
|
||||
svg += '</svg>';
|
||||
el.innerHTML = svg;
|
||||
}
|
||||
|
||||
function drawProtocolFlow(containerId) {
|
||||
var el = document.getElementById(containerId);
|
||||
if (!el) return;
|
||||
|
||||
var svg = '<svg viewBox="0 0 600 520" fill="none" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;max-width:600px">';
|
||||
svg += '<rect width="600" height="520" rx="12" fill="#131620"/>';
|
||||
|
||||
/* Columns */
|
||||
svg += '<text x="150" y="30" fill="#b0b2b8" font-size="12" font-weight="700" text-anchor="middle" font-family="Inter,sans-serif">Browser</text>';
|
||||
svg += '<text x="450" y="30" fill="#b0b2b8" font-size="12" font-weight="700" text-anchor="middle" font-family="Inter,sans-serif">Server</text>';
|
||||
|
||||
/* Lifelines */
|
||||
svg += '<line x1="150" y1="44" x2="150" y2="500" stroke="#2a2f40" stroke-width="1.5"/>';
|
||||
svg += '<line x1="450" y1="44" x2="450" y2="500" stroke="#2a2f40" stroke-width="1.5"/>';
|
||||
|
||||
var y = 70;
|
||||
var arrows = [
|
||||
{ from: 150, to: 450, label: 'Generate Ed25519 keypair', note: '', dir: 'self' },
|
||||
{ from: 150, to: 450, label: 'POST /init { public_key }', note: '', dir: 'right' },
|
||||
{ from: 450, to: 150, label: '{ session_id, salt, opcodes, gene_size, mutation_order }', note: '', dir: 'left' },
|
||||
{ from: 150, to: 450, label: 'Execute VM program', note: '', dir: 'self' },
|
||||
{ from: 150, to: 450, label: 'Collect entropy + sign payload', note: '', dir: 'self' },
|
||||
{ from: 150, to: 450, label: 'Preview gene commitment', note: '', dir: 'self' },
|
||||
{ from: 150, to: 450, label: 'POST /hb { session_id, hash, signature, gene_commitment, … }', note: '', dir: 'right' },
|
||||
{ from: 450, to: 150, label: 'Verify chain + signature + gene + trust', note: '', dir: 'selfr' },
|
||||
{ from: 450, to: 150, label: '{ next_salt, next_mutation_step, next_mutation_order }', note: 'accepted', dir: 'left' },
|
||||
{ from: 450, to: 150, label: '{ status: "ok" }', note: 'rejected (silent)', dir: 'left' },
|
||||
];
|
||||
|
||||
for (var i = 0; i < arrows.length; i++) {
|
||||
var a = arrows[i];
|
||||
if (a.dir === 'right') {
|
||||
svg += '<line x1="155" y1="' + y + '" x2="445" y2="' + y + '" stroke="#7b7e85" stroke-width="1.2"/>';
|
||||
svg += '<polygon points="443,' + (y - 4) + ' 450,' + y + ' 443,' + (y + 4) + '" fill="#7b7e85"/>';
|
||||
svg += '<text x="300" y="' + (y - 8) + '" fill="#e1e1e4" font-size="8.5" text-anchor="middle" font-family="Inter,sans-serif">' + a.label + '</text>';
|
||||
} else if (a.dir === 'left') {
|
||||
svg += '<line x1="445" y1="' + y + '" x2="155" y2="' + y + '" stroke="#7b7e85" stroke-width="1.2"/>';
|
||||
svg += '<polygon points="157,' + (y - 4) + ' 150,' + y + ' 157,' + (y + 4) + '" fill="#7b7e85"/>';
|
||||
svg += '<text x="300" y="' + (y - 8) + '" fill="#e1e1e4" font-size="8.5" text-anchor="middle" font-family="Inter,sans-serif">' + a.label + '</text>';
|
||||
if (a.note) {
|
||||
svg += '<text x="300" y="' + (y + 14) + '" fill="#6b6f7a" font-size="8" text-anchor="middle" font-style="italic" font-family="Inter,sans-serif">' + a.note + '</text>';
|
||||
y += 8;
|
||||
}
|
||||
} else if (a.dir === 'self') {
|
||||
svg += '<rect x="60" y="' + (y - 12) + '" width="180" height="22" rx="4" fill="#252a3a" stroke="#2a2f40"/>';
|
||||
svg += '<text x="150" y="' + (y + 3) + '" fill="#9a9da6" font-size="8.5" text-anchor="middle" font-family="Inter,sans-serif">' + a.label + '</text>';
|
||||
} else if (a.dir === 'selfr') {
|
||||
svg += '<rect x="360" y="' + (y - 12) + '" width="180" height="22" rx="4" fill="#252a3a" stroke="#2a2f40"/>';
|
||||
svg += '<text x="450" y="' + (y + 3) + '" fill="#9a9da6" font-size="8.5" text-anchor="middle" font-family="Inter,sans-serif">' + a.label + '</text>';
|
||||
}
|
||||
y += 44;
|
||||
}
|
||||
|
||||
svg += '</svg>';
|
||||
el.innerHTML = svg;
|
||||
}
|
||||
|
||||
/* ---- Auto-init ---- */
|
||||
function init() {
|
||||
drawArchitecture('diagram-architecture');
|
||||
drawProtocolFlow('diagram-protocol');
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
|
||||
return {
|
||||
drawArchitecture: drawArchitecture,
|
||||
drawProtocolFlow: drawProtocolFlow
|
||||
};
|
||||
})();
|
||||
@@ -0,0 +1,41 @@
|
||||
/* ============================================================
|
||||
ChronoSeal Website — pwa.js
|
||||
Progressive Web App: service worker registration + install
|
||||
============================================================ */
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
if ('serviceWorker' in navigator) {
|
||||
window.addEventListener('load', function () {
|
||||
navigator.serviceWorker.register('/sw.js').then(function (reg) {
|
||||
/* Update found — silent refresh */
|
||||
reg.addEventListener('updatefound', function () {
|
||||
var worker = reg.installing;
|
||||
if (!worker) return;
|
||||
worker.addEventListener('statechange', function () {
|
||||
if (worker.state === 'activated' && navigator.serviceWorker.controller) {
|
||||
/* New version available — user can refresh */
|
||||
}
|
||||
});
|
||||
});
|
||||
}).catch(function () {
|
||||
/* SW registration failed — app still works */
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/* ---- Installable PWA banner (A2HS) ---- */
|
||||
var deferredPrompt = null;
|
||||
|
||||
window.addEventListener('beforeinstallprompt', function (e) {
|
||||
e.preventDefault();
|
||||
deferredPrompt = e;
|
||||
/* Could show a custom install banner here */
|
||||
});
|
||||
|
||||
window.addEventListener('appinstalled', function () {
|
||||
deferredPrompt = null;
|
||||
});
|
||||
|
||||
})();
|
||||
@@ -0,0 +1,144 @@
|
||||
/* ============================================================
|
||||
ChronoSeal Website — search.js
|
||||
Client-side full-text search across all pages
|
||||
============================================================ */
|
||||
|
||||
var ChronoSearch = (function () {
|
||||
'use strict';
|
||||
|
||||
/* ---- Search index ---- */
|
||||
var pages = [
|
||||
{ title: 'Home', url: 'index.html', desc: 'ChronoSeal overview, features, and quick start', keywords: 'home overview features quick start install' },
|
||||
{ title: 'Architecture', url: 'architecture.html', desc: 'System architecture, crate layout, and data flow', keywords: 'architecture crates workspace wasm shared server' },
|
||||
{ title: 'Protocol', url: 'protocol.html', desc: 'Heartbeat protocol, hash chain, and mutation engine', keywords: 'protocol heartbeat hash chain blake3 ed25519 mutation gene' },
|
||||
{ title: 'API Reference', url: 'api.html', desc: 'HTTP endpoints: /init, /hb, /health, /metrics, /stats', keywords: 'api http post init heartbeat health metrics stats json' },
|
||||
{ title: 'Deployment', url: 'deployment.html', desc: 'Installation, systemd, Docker, nginx reverse proxy', keywords: 'deploy install systemd docker nginx proxy production' },
|
||||
{ title: 'Threat Model', url: 'threat-model.html', desc: 'Security threat model and defense boundaries', keywords: 'threat model security attack replay automation defense' },
|
||||
{ title: 'Performance', url: 'performance.html', desc: 'Performance tuning, benchmarks, and optimization', keywords: 'performance tuning benchmark latency throughput' },
|
||||
{ title: 'Philosophy', url: 'philosophy.html', desc: 'Design philosophy and engineering priorities', keywords: 'philosophy design unix privacy operator control' },
|
||||
{ title: 'Privacy Policy', url: 'privacy.html', desc: 'Data handling, storage, and privacy commitments', keywords: 'privacy policy data collection storage session' },
|
||||
{ title: 'Security', url: 'security.html', desc: 'Security assumptions, hardening, and response headers', keywords: 'security assumptions headers hardening csp' },
|
||||
{ title: 'Operations', url: 'operations.html', desc: 'Monitoring, health checks, metrics, and logging', keywords: 'operations health status metrics stats logging monitor' },
|
||||
{ title: 'Testing', url: 'testing.html', desc: 'Test suite, fuzzing, and validation coverage', keywords: 'testing tests cargo fuzz validation fingerprint' },
|
||||
{ title: 'Comparison', url: 'comparison.html', desc: 'ChronoSeal vs commercial anti-bot solutions', keywords: 'comparison cloudflare akamai recaptcha datadome kasada' },
|
||||
];
|
||||
|
||||
var overlay = null;
|
||||
var input = null;
|
||||
var results = null;
|
||||
|
||||
function init() {
|
||||
overlay = document.querySelector('.search-overlay');
|
||||
input = document.querySelector('.search-input');
|
||||
results = document.querySelector('.search-results');
|
||||
if (!overlay || !input || !results) return;
|
||||
|
||||
input.addEventListener('input', debounce(onInput, 150));
|
||||
overlay.addEventListener('click', function (e) {
|
||||
if (e.target === overlay) close();
|
||||
});
|
||||
|
||||
// Keyboard nav inside results
|
||||
input.addEventListener('keydown', function (e) {
|
||||
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
|
||||
e.preventDefault();
|
||||
navigateResults(e.key === 'ArrowDown' ? 1 : -1);
|
||||
}
|
||||
if (e.key === 'Enter') {
|
||||
var sel = results.querySelector('.search-result.selected');
|
||||
if (sel) { window.location.href = sel.getAttribute('href'); close(); }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function open() {
|
||||
if (!overlay) init();
|
||||
if (!overlay) return;
|
||||
overlay.classList.add('open');
|
||||
input.value = '';
|
||||
results.innerHTML = renderHints();
|
||||
setTimeout(function () { input.focus(); }, 100);
|
||||
}
|
||||
|
||||
function close() {
|
||||
if (overlay) overlay.classList.remove('open');
|
||||
}
|
||||
|
||||
function onInput() {
|
||||
var q = input.value.trim().toLowerCase();
|
||||
if (!q) { results.innerHTML = renderHints(); return; }
|
||||
|
||||
var matches = [];
|
||||
for (var i = 0; i < pages.length; i++) {
|
||||
var p = pages[i];
|
||||
var hay = (p.title + ' ' + p.desc + ' ' + p.keywords).toLowerCase();
|
||||
if (hay.indexOf(q) !== -1) {
|
||||
matches.push(p);
|
||||
}
|
||||
}
|
||||
|
||||
if (matches.length === 0) {
|
||||
results.innerHTML = '<div class="search-empty">No results for “' + escHtml(q) + '”</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
var html = '';
|
||||
for (var j = 0; j < matches.length; j++) {
|
||||
var m = matches[j];
|
||||
html += '<a class="search-result' + (j === 0 ? ' selected' : '') + '" href="' + m.url + '">';
|
||||
html += '<div class="search-result-title">' + highlightMatch(m.title, q) + '</div>';
|
||||
html += '<div class="search-result-desc">' + highlightMatch(m.desc, q) + '</div>';
|
||||
html += '</a>';
|
||||
}
|
||||
results.innerHTML = html;
|
||||
}
|
||||
|
||||
function navigateResults(dir) {
|
||||
var items = results.querySelectorAll('.search-result');
|
||||
if (!items.length) return;
|
||||
var idx = -1;
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
if (items[i].classList.contains('selected')) { idx = i; break; }
|
||||
}
|
||||
if (idx >= 0) items[idx].classList.remove('selected');
|
||||
idx = Math.max(0, Math.min(items.length - 1, idx + dir));
|
||||
items[idx].classList.add('selected');
|
||||
items[idx].scrollIntoView({ block: 'nearest' });
|
||||
}
|
||||
|
||||
function renderHints() {
|
||||
return '<div class="search-empty">Type to search documentation…<br><span style="font-size:.78rem;color:var(--text-muted)">↑↓ Navigate · ↵ Open · Esc Close</span></div>';
|
||||
}
|
||||
|
||||
function highlightMatch(text, q) {
|
||||
var idx = text.toLowerCase().indexOf(q);
|
||||
if (idx === -1) return escHtml(text);
|
||||
return escHtml(text.substring(0, idx)) +
|
||||
'<mark style="background:rgba(176,178,184,.2);color:var(--text-primary);border-radius:2px;padding:0 2px">' +
|
||||
escHtml(text.substring(idx, idx + q.length)) + '</mark>' +
|
||||
escHtml(text.substring(idx + q.length));
|
||||
}
|
||||
|
||||
function escHtml(s) {
|
||||
var div = document.createElement('div');
|
||||
div.appendChild(document.createTextNode(s));
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
function debounce(fn, ms) {
|
||||
var t;
|
||||
return function () {
|
||||
clearTimeout(t);
|
||||
t = setTimeout(fn, ms);
|
||||
};
|
||||
}
|
||||
|
||||
/* ---- Auto-init ---- */
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
|
||||
return { open: open, close: close };
|
||||
})();
|
||||
@@ -0,0 +1,127 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.1"
|
||||
id="Layer_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="296.99997mm"
|
||||
viewBox="0 0 1122.5196 793.7008"
|
||||
enable-background="new 0 0 1254 1254"
|
||||
xml:space="preserve"
|
||||
height="210mm"
|
||||
sodipodi:docname="logo1.svg"
|
||||
inkscape:export-filename="logo1.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96"
|
||||
inkscape:version="1.4.4 (dcaf3e7d9e, 2026-05-05)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:zoom="1.1654266"
|
||||
inkscape:cx="561.16791"
|
||||
inkscape:cy="396.85039"
|
||||
inkscape:window-width="2048"
|
||||
inkscape:window-height="1205"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="Layer_1" /><defs
|
||||
id="defs44" />
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<path
|
||||
fill="none"
|
||||
opacity="1"
|
||||
stroke="none"
|
||||
d="m 791.89377,632.01366 c 1.04068,-1.03091 1.90903,-2.38956 3.14896,-3.04166 11.48425,-6.03955 22.98721,-12.04745 34.57742,-17.88034 8.52002,-4.28775 14.37558,-10.49713 14.23218,-20.46771 -0.14127,-9.82202 -6.25309,-15.51495 -14.66742,-19.5827 -61.76651,-29.85993 -123.47361,-59.84277 -185.21183,-89.76129 -10.48242,-5.07981 -20.81335,-10.5555 -31.58477,-14.93764 -19.71186,-8.01936 -39.87799,-8.40881 -59.18305,0.84148 -71.77527,34.39209 -143.33667,69.23047 -215.00567,103.84463 -8.40815,4.06091 -14.32166,9.84101 -14.39978,19.74008 -0.079,10.00498 5.52282,16.22797 14.08978,20.54743 12.3277,6.21567 24.51239,12.71484 37.34228,19.41369 7.51429,3.93381 14.42273,7.5896 21.37854,11.15274 31.68775,16.23208 63.40558,32.40548 95.07782,48.66778 14.943,7.6726 29.74964,15.6123 44.73053,23.2091 8.44464,4.2823 16.79547,9.1534 25.75525,11.9324 18.81989,5.8372 37.10712,3.9335 54.83185,-5.5578 25.51599,-13.6634 51.41541,-26.6141 77.2135,-39.7465 30.82071,-15.6894 61.7009,-31.26196 92.58359,-46.82907 1.56006,-0.78641 3.38776,-1.0419 5.09082,-1.54462 z"
|
||||
id="path2" /><path
|
||||
fill="#4b4d51"
|
||||
opacity="1"
|
||||
stroke="none"
|
||||
d="m 791.89376,550.60089 c 1.04068,-1.03091 1.90903,-2.38956 3.14896,-3.04166 11.48425,-6.03955 22.98721,-12.04745 34.57742,-17.88034 8.52002,-4.28775 14.37558,-10.49713 14.23218,-20.46771 -0.14127,-9.82202 -6.25309,-15.51495 -14.66742,-19.5827 -61.76651,-29.85993 -123.47361,-59.84277 -185.21183,-89.76129 -10.48242,-5.07981 -20.81335,-10.5555 -31.58477,-14.93764 -19.71186,-8.01936 -39.87799,-8.40881 -59.18305,0.84148 -71.77522,34.39209 -143.33662,69.23047 -215.00562,103.84463 -8.4082,4.06091 -14.3217,9.84101 -14.3998,19.74008 -0.079,10.00498 5.5228,16.22797 14.0898,20.54743 12.3277,6.21567 24.5123,12.71484 37.3422,19.41369 7.5143,3.93381 14.4228,7.5896 21.3786,11.15274 31.6877,16.23212 63.4056,32.40552 95.0778,48.66782 14.943,7.67261 29.7496,15.61227 44.73052,23.20911 8.44464,4.28232 16.79547,9.15341 25.75525,11.93237 18.81989,5.83722 37.10712,3.93353 54.83185,-5.55777 25.51599,-13.66336 51.41541,-26.61407 77.2135,-39.74655 30.82071,-15.68933 61.7009,-31.26196 92.58359,-46.82907 1.56006,-0.78641 3.38776,-1.0419 5.09082,-1.54462 z"
|
||||
id="path45"
|
||||
style="fill:#e1e1e4;fill-opacity:1" /><path
|
||||
fill="#4b4d51"
|
||||
opacity="1"
|
||||
stroke="none"
|
||||
d="m 791.89377,469.18814 c 1.04068,-1.03091 1.90903,-2.38956 3.14896,-3.04166 11.48425,-6.03955 22.98721,-12.04745 34.57742,-17.88034 8.52002,-4.28775 14.37558,-10.49713 14.23218,-20.46771 -0.14127,-9.82202 -6.25309,-15.51495 -14.66742,-19.5827 C 767.4184,378.3558 705.7113,348.37296 643.97308,318.45444 c -10.48242,-5.07981 -20.81335,-10.5555 -31.58477,-14.93764 -19.71186,-8.01936 -39.87799,-8.40881 -59.18305,0.84148 -71.77527,34.39209 -143.33667,69.23047 -215.00567,103.84463 -8.40815,4.06091 -14.32166,9.84101 -14.39978,19.74008 -0.079,10.00498 5.52282,16.22797 14.08978,20.54743 12.3277,6.21567 24.51239,12.71484 37.34228,19.41369 7.51429,3.93381 14.42273,7.5896 21.37854,11.15274 31.68775,16.23212 63.40558,32.40552 95.07782,48.66782 14.943,7.67261 29.74964,15.61227 44.73053,23.20911 8.44464,4.28232 16.79547,9.15341 25.75525,11.93237 18.81989,5.83722 37.10712,3.93353 54.83185,-5.55777 25.51599,-13.66336 51.41541,-26.61407 77.2135,-39.74655 30.82071,-15.68933 61.7009,-31.26196 92.58359,-46.82907 1.56006,-0.78641 3.38776,-1.0419 5.09082,-1.54462 z"
|
||||
id="path46"
|
||||
style="fill:#b0b2b8;fill-opacity:1" /><path
|
||||
fill="#4b4d51"
|
||||
opacity="1"
|
||||
stroke="none"
|
||||
d="m 791.89377,387.77539 c 1.04068,-1.03091 1.90903,-2.38956 3.14896,-3.04166 11.48425,-6.03955 22.98721,-12.04745 34.57742,-17.88034 8.52002,-4.28775 14.37558,-10.49713 14.23218,-20.46771 -0.14127,-9.82202 -6.25309,-15.51495 -14.66742,-19.5827 -61.76651,-29.85993 -123.47361,-59.84277 -185.21183,-89.76129 -10.48242,-5.07981 -20.81335,-10.5555 -31.58477,-14.93764 -19.71186,-8.01936 -39.87799,-8.40881 -59.18305,0.84148 -71.77527,34.39209 -143.33667,69.23047 -215.00567,103.84463 -8.40815,4.06091 -14.32166,9.84101 -14.39978,19.74008 -0.079,10.00498 5.52282,16.22797 14.08978,20.54743 12.3277,6.21567 24.51239,12.71484 37.34228,19.41369 7.51429,3.93381 14.42273,7.5896 21.37854,11.15274 31.68775,16.23212 63.40558,32.40552 95.07782,48.66782 14.943,7.67261 29.74964,15.61227 44.73053,23.20911 8.44464,4.28232 16.79547,9.15341 25.75525,11.93237 18.81989,5.83722 37.10712,3.93353 54.83185,-5.55777 25.51599,-13.66336 51.41541,-26.61407 77.2135,-39.74655 30.82071,-15.68933 61.7009,-31.26196 92.58359,-46.82907 1.56006,-0.78641 3.38776,-1.0419 5.09082,-1.54462 z"
|
||||
id="path47"
|
||||
style="fill:#7b7e85;fill-opacity:1" /><text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:160px;font-family:'URW Gothic';-inkscape-font-specification:'URW Gothic, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;writing-mode:lr-tb;direction:ltr;fill:#e1e1e4;fill-opacity:1;stroke-width:39.1"
|
||||
x="154.54701"
|
||||
y="550.13623"
|
||||
id="text47"><tspan
|
||||
id="tspan47"
|
||||
x="154.54701"
|
||||
y="550.13623" /><tspan
|
||||
id="tspan48"
|
||||
x="154.54701"
|
||||
y="750.13623"
|
||||
style="font-style:normal;font-variant:normal;font-weight:600;font-stretch:normal;font-size:160px;font-family:'URW Gothic';-inkscape-font-specification:'URW Gothic, Semi-Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:600;font-stretch:normal;font-size:160px;font-family:'URW Gothic';-inkscape-font-specification:'URW Gothic, Semi-Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1f2024;fill-opacity:1"
|
||||
id="tspan49">chrono</tspan>seal</tspan></text><path
|
||||
fill="#4b4d51"
|
||||
opacity="1"
|
||||
stroke="none"
|
||||
d="m 791.89376,306.36264 c 1.04068,-1.03091 1.90903,-2.38956 3.14896,-3.04166 11.48425,-6.03955 22.98721,-12.04745 34.57742,-17.88034 8.52002,-4.28775 14.37558,-10.49713 14.23218,-20.46771 -0.14127,-9.82202 -6.25309,-15.51495 -14.66742,-19.5827 -61.76651,-29.85993 -123.47361,-59.84277 -185.21183,-89.76129 -10.48242,-5.07981 -20.81335,-10.5555 -31.58477,-14.93764 -19.71186,-8.01936 -39.87799,-8.40881 -59.18305,0.84148 -71.77522,34.39209 -143.33662,69.23047 -215.00562,103.84463 -8.4082,4.06091 -14.3217,9.84101 -14.3998,19.74008 -0.079,10.00498 5.5228,16.22797 14.0898,20.54743 12.3277,6.21567 24.5123,12.71484 37.3422,19.41369 7.5143,3.93381 14.4228,7.5896 21.3786,11.15274 31.6877,16.23212 63.4056,32.40552 95.0778,48.66782 14.943,7.67261 29.7496,15.61227 44.73052,23.20911 8.44464,4.28232 16.79547,9.15341 25.75525,11.93237 18.81989,5.83722 37.10712,3.93353 54.83185,-5.55777 25.51599,-13.66336 51.41541,-26.61407 77.2135,-39.74655 30.82071,-15.68933 61.7009,-31.26196 92.58359,-46.82907 1.56006,-0.78641 3.38776,-1.0419 5.09082,-1.54462 z"
|
||||
id="path1"
|
||||
style="fill:#4a4d53;fill-opacity:1" /></svg>
|
||||
|
After Width: | Height: | Size: 8.1 KiB |
|
After Width: | Height: | Size: 108 KiB |
@@ -0,0 +1,304 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
||||
<title>Operations Handbook | ChronoSeal Documentation</title>
|
||||
<meta name="description" content="ChronoSeal Operations Handbook, detailing deployment, monitoring, scaling, failure diagnosis, and debugging options.">
|
||||
<link rel="stylesheet" href="css/chronoseal.css">
|
||||
<link rel="stylesheet" href="css/docs.css">
|
||||
<link rel="stylesheet" href="css/print.css" media="print">
|
||||
<link rel="manifest" href="site.webmanifest">
|
||||
<link rel="icon" href="assets/logo.svg" type="image/svg+xml">
|
||||
|
||||
<!-- Font Awesome for Icons -->
|
||||
<link rel="preconnect" href="https://cdnjs.cloudflare.com" crossorigin />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
|
||||
integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw=="
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Floating Background Spheres -->
|
||||
<div class="bg-animation">
|
||||
<div class="gradient-sphere sphere-1"></div>
|
||||
<div class="gradient-sphere sphere-2"></div>
|
||||
<div class="gradient-sphere sphere-3"></div>
|
||||
</div>
|
||||
|
||||
<!-- Header / Navbar -->
|
||||
<nav class="navbar" id="navbar">
|
||||
<div class="nav-container">
|
||||
<a href="index.html" class="logo">
|
||||
<img class="logobar" src="assets/logo.svg" alt="ChronoSeal Logo">
|
||||
<span>ChronoSeal</span>
|
||||
</a>
|
||||
<div class="nav-links" id="navLinks">
|
||||
<a href="philosophy.html">Philosophy</a>
|
||||
<a href="architecture.html">Architecture</a>
|
||||
<a href="protocol.html">Protocol</a>
|
||||
<a href="comparison.html">Comparison</a>
|
||||
<div class="nav-dropdown">
|
||||
<a href="#" class="nav-link">More <small>▼</small></a>
|
||||
<div class="nav-dropdown-menu">
|
||||
<a href="api.html">API Reference</a>
|
||||
<a href="deployment.html">Deployment Guide</a>
|
||||
<a href="operations.html" class="active">Operations Guide</a>
|
||||
<a href="testing.html">Testing Strategy</a>
|
||||
<a href="performance.html">Performance Tuning</a>
|
||||
<a href="threat-model.html">Threat Model</a>
|
||||
<a href="security.html">Security Policy</a>
|
||||
<a href="privacy.html">Privacy Policy</a>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-ghost btn-sm btn-icon search-trigger" aria-label="Search" onclick="ChronoSearch.open()">
|
||||
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" width="16" height="16"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
|
||||
</button>
|
||||
<a href="https://github.com/thakares/chronoseal-rs" class="github-btn" target="_blank" rel="noopener">
|
||||
<i class="fab fa-github"></i> GitHub
|
||||
</a>
|
||||
</div>
|
||||
<button class="nav-toggle" id="menuBtn" aria-label="Toggle Navigation">
|
||||
<i class="fas fa-bars"></i>
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Doc Page Shell -->
|
||||
<div class="doc-page">
|
||||
<div class="doc-layout">
|
||||
|
||||
<!-- Sidebar -->
|
||||
<aside class="doc-sidebar">
|
||||
<button class="doc-sidebar-toggle">
|
||||
<i class="fas fa-bars"></i> Sidebar Menu
|
||||
</button>
|
||||
<div class="doc-nav-group">
|
||||
<div class="doc-nav-label">Core Concepts</div>
|
||||
<a href="philosophy.html" class="doc-nav-item">Design Philosophy</a>
|
||||
<a href="architecture.html" class="doc-nav-item">Architecture Overview</a>
|
||||
<a href="comparison.html" class="doc-nav-item">ChronoSeal vs Others</a>
|
||||
</div>
|
||||
<div class="doc-nav-group">
|
||||
<div class="doc-nav-label">Protocol & API</div>
|
||||
<a href="protocol.html" class="doc-nav-item">Protocol Specification</a>
|
||||
<a href="api.html" class="doc-nav-item">API Reference</a>
|
||||
</div>
|
||||
<div class="doc-nav-group">
|
||||
<div class="doc-nav-label">Operations & Security</div>
|
||||
<a href="threat-model.html" class="doc-nav-item">Threat Model</a>
|
||||
<a href="security.html" class="doc-nav-item">Security Policy</a>
|
||||
<a href="privacy.html" class="doc-nav-item">Privacy Policy</a>
|
||||
<a href="performance.html" class="doc-nav-item">Performance Tuning</a>
|
||||
</div>
|
||||
<div class="doc-nav-group">
|
||||
<div class="doc-nav-label">Lifecycle & Dev</div>
|
||||
<a href="deployment.html" class="doc-nav-item">Deployment Guide</a>
|
||||
<a href="operations.html" class="doc-nav-item active">Operations Handbook</a>
|
||||
<a href="testing.html" class="doc-nav-item">Testing Strategy</a>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content Area -->
|
||||
<main class="doc-main">
|
||||
<nav class="doc-breadcrumb" aria-label="Breadcrumb">
|
||||
<a href="index.html">Home</a>
|
||||
<span class="sep">/</span>
|
||||
<a href="#">Lifecycle & Dev</a>
|
||||
<span class="sep">/</span>
|
||||
<span>Operations Handbook</span>
|
||||
</nav>
|
||||
|
||||
<article class="doc-content">
|
||||
<h1>Operations & Diagnostics Handbook</h1>
|
||||
<p class="doc-subtitle">This guide details how to monitor, scale, maintain, and debug the ChronoSeal daemon (<code>chronoseal</code>) in production environments.</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Systemd Service Configuration</h2>
|
||||
<p>On single-node Linux systems, run the daemon under systemd. The standard service unit file is configured at <code>/etc/systemd/system/chronoseal.service</code>:</p>
|
||||
|
||||
<div class="code-block">
|
||||
<div class="code-header">
|
||||
<span class="code-lang">systemd Unit File</span>
|
||||
<button class="code-copy-btn">Copy</button>
|
||||
</div>
|
||||
<pre><code>[Unit]
|
||||
Description=ChronoSeal Attestation Daemon
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=chronoseal
|
||||
Group=chronoseal
|
||||
WorkingDirectory=/var/lib/chronoseal
|
||||
ExecStart=/usr/local/bin/chronoseal run --config /etc/chronoseal.toml
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
LimitNOFILE=65536
|
||||
|
||||
# Hardening
|
||||
ProtectSystem=full
|
||||
ProtectHome=true
|
||||
NoNewPrivileges=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target</code></pre>
|
||||
</div>
|
||||
|
||||
<p>Reload and enable the service:</p>
|
||||
<div class="code-block">
|
||||
<div class="code-header">
|
||||
<span class="code-lang">Shell</span>
|
||||
<button class="code-copy-btn">Copy</button>
|
||||
</div>
|
||||
<pre><code>sudo systemctl daemon-reload
|
||||
sudo systemctl enable --now chronoseal</code></pre>
|
||||
</div>
|
||||
|
||||
<h2>Reverse Proxy & TLS Termination</h2>
|
||||
<p>Do not expose the raw ChronoSeal port directly to the internet. Always terminate TLS using Nginx, HAProxy, or a cloud load balancer. A sample Nginx setup is shown below:</p>
|
||||
|
||||
<div class="code-block">
|
||||
<div class="code-header">
|
||||
<span class="code-lang">Nginx config</span>
|
||||
<button class="code-copy-btn">Copy</button>
|
||||
</div>
|
||||
<pre><code>server {
|
||||
listen 443 ssl http2;
|
||||
server_name attestation.example.com;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:3000;
|
||||
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;
|
||||
}
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<h2>Storage & Horizontal Scaling</h2>
|
||||
|
||||
<h3>Single Node (SQLite)</h3>
|
||||
<p>Set <code>db_type = "sqlite-in-disk"</code> and configure a writeable path in <code>db_path</code>. Optimistic Compare-And-Swap (CAS) locking prevents concurrency errors, but high transaction loads may face write lock queuing.</p>
|
||||
|
||||
<h3>Multi-Node Scaling (Valkey / Redis)</h3>
|
||||
<p>For high availability, run multiple stateless <code>chronoseal</code> instances behind a load balancer and set <code>db_type = "valkey"</code>. Connect all nodes to the same shared Valkey/Redis instance using <code>CHRONOSEAL_VALKEY_ADDR</code>. This keeps sessions consistent across all server nodes.</p>
|
||||
|
||||
<h2>Monitoring & Observability</h2>
|
||||
<p>Configure Prometheus to scrape operational metrics from `/metrics`:</p>
|
||||
<div class="code-block">
|
||||
<div class="code-header">
|
||||
<span class="code-lang">Prometheus config</span>
|
||||
<button class="code-copy-btn">Copy</button>
|
||||
</div>
|
||||
<pre><code>scrape_configs:
|
||||
- job_name: 'chronoseal'
|
||||
static_configs:
|
||||
- targets: ['localhost:3000']</code></pre>
|
||||
</div>
|
||||
<p>Set alerts for these key metrics:</p>
|
||||
<ul>
|
||||
<li><code>chronoseal_sessions</code>: Spike suggests rate limit thresholds should be reviewed or a scraper campaign is initiating.</li>
|
||||
<li><code>chronoseal_max_chain_length</code>: Tracks session duration. Low values suggest clients are failing attestation.</li>
|
||||
</ul>
|
||||
|
||||
<h2>Failure Diagnostic Guide</h2>
|
||||
<p>Because ChronoSeal returns a silent success response <code>{"status":"ok"}</code> on heartbeat rejections, check the following variables when debugging client integration issues:</p>
|
||||
|
||||
<h3>A. Clock Drift (<code>TimestampDrift</code>)</h3>
|
||||
<p><strong>Cause:</strong> Client machine clock differs from the server clock by more than 30 seconds (<code>max_timestamp_drift_ms</code>). Sync both clocks using NTP.</p>
|
||||
|
||||
<h3>B. Out-of-Sequence Replays (<code>ChainBroken</code>)</h3>
|
||||
<p><strong>Cause:</strong> Client submitted a <code>prev_hash</code> that does not match the server's <code>last_hash</code>. This occurs when network packet drops cause retries from outdated states, or if an attacker attempts replay. The client must restart liveness via <code>/init</code>.</p>
|
||||
|
||||
<h3>C. Signature Mismatch (<code>Signature</code>)</h3>
|
||||
<p><strong>Cause:</strong> The Ed25519 signature over the canonical JSON payload is invalid. Verify that the client orders JSON keys alphabetically (<code>entropyData</code>, <code>fingerprint</code>, <code>geneCommitment</code>, <code>mutationStep</code>, <code>prevHash</code>, <code>sessionId</code>, <code>stackState</code>, <code>timestamp</code>) and serializes types correctly.</p>
|
||||
|
||||
<h3>D. VM State Mismatch (<code>VmStackMismatch</code>)</h3>
|
||||
<p><strong>Cause:</strong> Client's VM <code>stack_state</code> differs from server re-execution results. Ensure the browser VM implementation wraps 32-bit math correctly matching <code>shared/src/vm.rs</code>.</p>
|
||||
</article>
|
||||
|
||||
<!-- Pager -->
|
||||
<nav class="doc-pager" aria-label="Doc navigation Pager">
|
||||
<a href="performance.html" class="doc-pager-link">
|
||||
<div class="doc-pager-label">Previous</div>
|
||||
<div class="doc-pager-title">Performance Tuning</div>
|
||||
</a>
|
||||
<a href="deployment.html" class="doc-pager-link doc-pager-link--next">
|
||||
<div class="doc-pager-label">Next</div>
|
||||
<div class="doc-pager-title">Deployment Guide</div>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<div class="doc-meta">
|
||||
Last Updated: June 2026 (v1.0.2)
|
||||
</div>
|
||||
</main>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="footer">
|
||||
<div class="footer-content">
|
||||
<div class="footer-section">
|
||||
<h4>
|
||||
<img class="logobar" src="assets/logo.svg" alt="ChronoSeal Logo" style="width: 24px; height: 24px; vertical-align: middle;"> ChronoSeal
|
||||
</h4>
|
||||
<p>Next-generation browser attestation daemon built for the modern web.</p>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h4>Resources</h4>
|
||||
<a href="philosophy.html">Design Philosophy</a>
|
||||
<a href="architecture.html">Architecture</a>
|
||||
<a href="protocol.html">Protocol</a>
|
||||
<a href="api.html">API Reference</a>
|
||||
<a href="comparison.html">Comparison</a>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h4>Community</h4>
|
||||
<a href="https://github.com/thakares/chronoseal-rs" target="_blank" rel="noopener"><i class="fab fa-github"></i> GitHub</a>
|
||||
<a href="deployment.html"><i class="fas fa-rocket"></i> Deployment</a>
|
||||
<a href="testing.html"><i class="fas fa-vial"></i> Testing</a>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h4>Legal & Security</h4>
|
||||
<a href="security.html">Security Policy</a>
|
||||
<a href="privacy.html">Privacy Policy</a>
|
||||
<a href="performance.html">Performance Tuning</a>
|
||||
<a href="operations.html">Operations Guide</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<p>© 2026 ChronoSeal Project. Built with 🦀 Rust and ❤️ for open source.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Search Overlay -->
|
||||
<div class="search-overlay">
|
||||
<div class="search-box">
|
||||
<div class="search-input-wrap">
|
||||
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
|
||||
<input type="text" class="search-input" placeholder="Search documentation... (Esc to close)">
|
||||
<span class="search-kbd">/</span>
|
||||
</div>
|
||||
<div class="search-results"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Back to top -->
|
||||
<a href="#" class="back-to-top" aria-label="Back to top">▲</a>
|
||||
|
||||
<!-- JS Scripts -->
|
||||
<script src="js/search.js"></script>
|
||||
<script src="js/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,300 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
||||
<title>Performance Tuning Guide | ChronoSeal Documentation</title>
|
||||
<meta name="description" content="ChronoSeal Performance Tuning Guide, detailing mutation parameters, recommended setups, diagnostic metrics, and optimization profiles.">
|
||||
<link rel="stylesheet" href="css/chronoseal.css">
|
||||
<link rel="stylesheet" href="css/docs.css">
|
||||
<link rel="stylesheet" href="css/print.css" media="print">
|
||||
<link rel="manifest" href="site.webmanifest">
|
||||
<link rel="icon" href="assets/logo.svg" type="image/svg+xml">
|
||||
|
||||
<!-- Font Awesome for Icons -->
|
||||
<link rel="preconnect" href="https://cdnjs.cloudflare.com" crossorigin />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
|
||||
integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw=="
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Floating Background Spheres -->
|
||||
<div class="bg-animation">
|
||||
<div class="gradient-sphere sphere-1"></div>
|
||||
<div class="gradient-sphere sphere-2"></div>
|
||||
<div class="gradient-sphere sphere-3"></div>
|
||||
</div>
|
||||
|
||||
<!-- Header / Navbar -->
|
||||
<nav class="navbar" id="navbar">
|
||||
<div class="nav-container">
|
||||
<a href="index.html" class="logo">
|
||||
<img class="logobar" src="assets/logo.svg" alt="ChronoSeal Logo">
|
||||
<span>ChronoSeal</span>
|
||||
</a>
|
||||
<div class="nav-links" id="navLinks">
|
||||
<a href="philosophy.html">Philosophy</a>
|
||||
<a href="architecture.html">Architecture</a>
|
||||
<a href="protocol.html">Protocol</a>
|
||||
<a href="comparison.html">Comparison</a>
|
||||
<div class="nav-dropdown">
|
||||
<a href="#" class="nav-link">More <small>▼</small></a>
|
||||
<div class="nav-dropdown-menu">
|
||||
<a href="api.html">API Reference</a>
|
||||
<a href="deployment.html">Deployment Guide</a>
|
||||
<a href="operations.html">Operations Guide</a>
|
||||
<a href="testing.html">Testing Strategy</a>
|
||||
<a href="performance.html" class="active">Performance Tuning</a>
|
||||
<a href="threat-model.html">Threat Model</a>
|
||||
<a href="security.html">Security Policy</a>
|
||||
<a href="privacy.html">Privacy Policy</a>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-ghost btn-sm btn-icon search-trigger" aria-label="Search" onclick="ChronoSearch.open()">
|
||||
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" width="16" height="16"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
|
||||
</button>
|
||||
<a href="https://github.com/thakares/chronoseal-rs" class="github-btn" target="_blank" rel="noopener">
|
||||
<i class="fab fa-github"></i> GitHub
|
||||
</a>
|
||||
</div>
|
||||
<button class="nav-toggle" id="menuBtn" aria-label="Toggle Navigation">
|
||||
<i class="fas fa-bars"></i>
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Doc Page Shell -->
|
||||
<div class="doc-page">
|
||||
<div class="doc-layout">
|
||||
|
||||
<!-- Sidebar -->
|
||||
<aside class="doc-sidebar">
|
||||
<button class="doc-sidebar-toggle">
|
||||
<i class="fas fa-bars"></i> Sidebar Menu
|
||||
</button>
|
||||
<div class="doc-nav-group">
|
||||
<div class="doc-nav-label">Core Concepts</div>
|
||||
<a href="philosophy.html" class="doc-nav-item">Design Philosophy</a>
|
||||
<a href="architecture.html" class="doc-nav-item">Architecture Overview</a>
|
||||
<a href="comparison.html" class="doc-nav-item">ChronoSeal vs Others</a>
|
||||
</div>
|
||||
<div class="doc-nav-group">
|
||||
<div class="doc-nav-label">Protocol & API</div>
|
||||
<a href="protocol.html" class="doc-nav-item">Protocol Specification</a>
|
||||
<a href="api.html" class="doc-nav-item">API Reference</a>
|
||||
</div>
|
||||
<div class="doc-nav-group">
|
||||
<div class="doc-nav-label">Operations & Security</div>
|
||||
<a href="threat-model.html" class="doc-nav-item">Threat Model</a>
|
||||
<a href="security.html" class="doc-nav-item">Security Policy</a>
|
||||
<a href="privacy.html" class="doc-nav-item">Privacy Policy</a>
|
||||
<a href="performance.html" class="doc-nav-item active">Performance Tuning</a>
|
||||
</div>
|
||||
<div class="doc-nav-group">
|
||||
<div class="doc-nav-label">Lifecycle & Dev</div>
|
||||
<a href="deployment.html" class="doc-nav-item">Deployment Guide</a>
|
||||
<a href="operations.html" class="doc-nav-item">Operations Handbook</a>
|
||||
<a href="testing.html" class="doc-nav-item">Testing Strategy</a>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content Area -->
|
||||
<main class="doc-main">
|
||||
<nav class="doc-breadcrumb" aria-label="Breadcrumb">
|
||||
<a href="index.html">Home</a>
|
||||
<span class="sep">/</span>
|
||||
<a href="#">Operations & Security</a>
|
||||
<span class="sep">/</span>
|
||||
<span>Performance Tuning</span>
|
||||
</nav>
|
||||
|
||||
<article class="doc-content">
|
||||
<h1>Performance Tuning Guide</h1>
|
||||
<p class="doc-subtitle">ChronoSeal uses a deterministic Synthetic Gene Mutation Engine. This guide explains how to tune the mutation parameters to balance security, server throughput, and client mobile CPU limits.</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Primary Tuning Parameters</h2>
|
||||
<p>Two configuration constants in the ChronoSeal daemon dictate the complexity of synthetic mutation progression:</p>
|
||||
<ul>
|
||||
<li><strong><code>gene_size</code>:</strong> The size of the synthetic gene byte buffer (configured from 1 to 4096 bytes). Larger values increase state vector complexity.</li>
|
||||
<li><strong><code>mutation_rounds</code>:</strong> The number of mutation iterations executed per heartbeat (configured from 1 to 10 rounds). Higher counts increase server and browser CPU operations.</li>
|
||||
</ul>
|
||||
|
||||
<h2>Recommended Profiles</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Profile</th>
|
||||
<th><code>gene_size</code></th>
|
||||
<th><code>mutation_rounds</code></th>
|
||||
<th>Security Strength</th>
|
||||
<th>Recommended Environment</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Default</td>
|
||||
<td>512</td>
|
||||
<td>4</td>
|
||||
<td>Moderate</td>
|
||||
<td>Development and local testing</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Recommended</strong></td>
|
||||
<td><strong>2048</strong></td>
|
||||
<td><strong>4</strong></td>
|
||||
<td><strong>Strong</strong></td>
|
||||
<td><strong>Most production deployments</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>High Security</td>
|
||||
<td>4096</td>
|
||||
<td>8</td>
|
||||
<td>Very Strong</td>
|
||||
<td>Sensitive financial or auth APIs</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Max Practical</td>
|
||||
<td>4096</td>
|
||||
<td>10</td>
|
||||
<td>Extremely Strong</td>
|
||||
<td>High-value targets under active abuse</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="doc-alert doc-alert--note">
|
||||
<div class="doc-alert-icon">ℹ️</div>
|
||||
<div class="doc-alert-body">
|
||||
<strong>v1.0.2 Protocol Limits:</strong> The validator enforces maximum bounds of <code>gene_size = 4096</code> and <code>mutation_rounds = 10</code>. Values exceeding these ranges are rejected during configuration verification.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Monitoring Performance</h2>
|
||||
|
||||
<h3>1. Server-Side Observability</h3>
|
||||
<p>Check the effective parameters and database write latencies using the daemon CLI:</p>
|
||||
<div class="code-block">
|
||||
<div class="code-header">
|
||||
<span class="code-lang">Shell</span>
|
||||
<button class="code-copy-btn">Copy</button>
|
||||
</div>
|
||||
<pre><code>chronoseal config check --format yaml
|
||||
chronoseal stats --format json</code></pre>
|
||||
</div>
|
||||
<p>Avoid running the daemon with <code>CHRONOSEAL_LOG=debug</code> in production, as debug logging session buffers degrades server throughput.</p>
|
||||
|
||||
<h3>2. Client-Side Time Measurement</h3>
|
||||
<p>Measure WASM execution time inside your browser console to verify mobile compatibility:</p>
|
||||
<div class="code-block">
|
||||
<div class="code-header">
|
||||
<span class="code-lang">JavaScript</span>
|
||||
<button class="code-copy-btn">Copy</button>
|
||||
</div>
|
||||
<pre><code>console.time("gene-mutation");
|
||||
const commitment = preview_gene_commitment(
|
||||
order_b64,
|
||||
session_id,
|
||||
mutation_step,
|
||||
rounds
|
||||
);
|
||||
console.timeEnd("gene-mutation");</code></pre>
|
||||
</div>
|
||||
|
||||
<h2>Tuning Progression Strategy</h2>
|
||||
<p>When upgrading mutation strength in production, we recommend a step-by-step approach to monitor mobile battery impact:</p>
|
||||
<ol>
|
||||
<li>Start at our standard baseline: <code>gene_size = 2048</code>, <code>mutation_rounds = 4</code>.</li>
|
||||
<li>Deploy and monitor the heartbeat verification success metrics. Look for increases in client timeouts.</li>
|
||||
<li>Increase state size first (e.g. to <code>4096</code>) before raising the number of execution rounds.</li>
|
||||
<li>Perform testing on older Android/iOS mobile devices. Desktops are highly forgiving of high WASM execution loads; low-end mobile CPUs are not.</li>
|
||||
</ol>
|
||||
|
||||
<h2>Storage Backend Impact</h2>
|
||||
<p>The choice of storage backend has a significant impact on transaction throughput:</p>
|
||||
<ul>
|
||||
<li><strong><code>sqlite-in-memory</code>:</strong> Highest performance, zero write latency. Ideal for ephemeral deployments.</li>
|
||||
<li><strong><code>sqlite-in-disk</code>:</strong> Bounded by disk write capacity and database file locks.</li>
|
||||
<li><strong><code>valkey</code>:</strong> Scales horizontally across multiple stateless daemon instances connecting to a shared Redis/Valkey cluster.</li>
|
||||
</ul>
|
||||
</article>
|
||||
|
||||
<!-- Pager -->
|
||||
<nav class="doc-pager" aria-label="Doc navigation Pager">
|
||||
<a href="threat-model.html" class="doc-pager-link">
|
||||
<div class="doc-pager-label">Previous</div>
|
||||
<div class="doc-pager-title">Threat Model</div>
|
||||
</a>
|
||||
<a href="operations.html" class="doc-pager-link doc-pager-link--next">
|
||||
<div class="doc-pager-label">Next</div>
|
||||
<div class="doc-pager-title">Operations Handbook</div>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<div class="doc-meta">
|
||||
Last Updated: June 2026 (v1.0.2)
|
||||
</div>
|
||||
</main>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="footer">
|
||||
<div class="footer-content">
|
||||
<div class="footer-section">
|
||||
<h4>
|
||||
<img class="logobar" src="assets/logo.svg" alt="ChronoSeal Logo" style="width: 24px; height: 24px; vertical-align: middle;"> ChronoSeal
|
||||
</h4>
|
||||
<p>Next-generation browser attestation daemon built for the modern web.</p>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h4>Resources</h4>
|
||||
<a href="philosophy.html">Design Philosophy</a>
|
||||
<a href="architecture.html">Architecture</a>
|
||||
<a href="protocol.html">Protocol</a>
|
||||
<a href="api.html">API Reference</a>
|
||||
<a href="comparison.html">Comparison</a>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h4>Community</h4>
|
||||
<a href="https://github.com/thakares/chronoseal-rs" target="_blank" rel="noopener"><i class="fab fa-github"></i> GitHub</a>
|
||||
<a href="deployment.html"><i class="fas fa-rocket"></i> Deployment</a>
|
||||
<a href="testing.html"><i class="fas fa-vial"></i> Testing</a>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h4>Legal & Security</h4>
|
||||
<a href="security.html">Security Policy</a>
|
||||
<a href="privacy.html">Privacy Policy</a>
|
||||
<a href="performance.html">Performance Tuning</a>
|
||||
<a href="operations.html">Operations Guide</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<p>© 2026 ChronoSeal Project. Built with 🦀 Rust and ❤️ for open source.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Search Overlay -->
|
||||
<div class="search-overlay">
|
||||
<div class="search-box">
|
||||
<div class="search-input-wrap">
|
||||
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
|
||||
<input type="text" class="search-input" placeholder="Search documentation... (Esc to close)">
|
||||
<span class="search-kbd">/</span>
|
||||
</div>
|
||||
<div class="search-results"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Back to top -->
|
||||
<a href="#" class="back-to-top" aria-label="Back to top">▲</a>
|
||||
|
||||
<!-- JS Scripts -->
|
||||
<script src="js/search.js"></script>
|
||||
<script src="js/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,247 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
||||
<title>Design Philosophy | ChronoSeal Documentation</title>
|
||||
<meta name="description" content="ChronoSeal design philosophy, engineering priorities, non-goals, and cost-raising anti-automation security biases.">
|
||||
<link rel="stylesheet" href="css/chronoseal.css">
|
||||
<link rel="stylesheet" href="css/docs.css">
|
||||
<link rel="stylesheet" href="css/print.css" media="print">
|
||||
<link rel="manifest" href="site.webmanifest">
|
||||
<link rel="icon" href="assets/logo.svg" type="image/svg+xml">
|
||||
|
||||
<!-- Font Awesome for Icons -->
|
||||
<link rel="preconnect" href="https://cdnjs.cloudflare.com" crossorigin />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
|
||||
integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw=="
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Floating Background Spheres -->
|
||||
<div class="bg-animation">
|
||||
<div class="gradient-sphere sphere-1"></div>
|
||||
<div class="gradient-sphere sphere-2"></div>
|
||||
<div class="gradient-sphere sphere-3"></div>
|
||||
</div>
|
||||
|
||||
<!-- Header / Navbar -->
|
||||
<nav class="navbar" id="navbar">
|
||||
<div class="nav-container">
|
||||
<a href="index.html" class="logo">
|
||||
<img class="logobar" src="assets/logo.svg" alt="ChronoSeal Logo">
|
||||
<span>ChronoSeal</span>
|
||||
</a>
|
||||
<div class="nav-links" id="navLinks">
|
||||
<a href="philosophy.html" class="active">Philosophy</a>
|
||||
<a href="architecture.html">Architecture</a>
|
||||
<a href="protocol.html">Protocol</a>
|
||||
<a href="comparison.html">Comparison</a>
|
||||
<div class="nav-dropdown">
|
||||
<a href="#" class="nav-link">More <small>▼</small></a>
|
||||
<div class="nav-dropdown-menu">
|
||||
<a href="api.html">API Reference</a>
|
||||
<a href="deployment.html">Deployment Guide</a>
|
||||
<a href="operations.html">Operations Guide</a>
|
||||
<a href="testing.html">Testing Strategy</a>
|
||||
<a href="performance.html">Performance Tuning</a>
|
||||
<a href="threat-model.html">Threat Model</a>
|
||||
<a href="security.html">Security Policy</a>
|
||||
<a href="privacy.html">Privacy Policy</a>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-ghost btn-sm btn-icon search-trigger" aria-label="Search" onclick="ChronoSearch.open()">
|
||||
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" width="16" height="16"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
|
||||
</button>
|
||||
<a href="https://github.com/thakares/chronoseal-rs" class="github-btn" target="_blank" rel="noopener">
|
||||
<i class="fab fa-github"></i> GitHub
|
||||
</a>
|
||||
</div>
|
||||
<button class="nav-toggle" id="menuBtn" aria-label="Toggle Navigation">
|
||||
<i class="fas fa-bars"></i>
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Doc Page Shell -->
|
||||
<div class="doc-page">
|
||||
<div class="doc-layout">
|
||||
|
||||
<!-- Sidebar -->
|
||||
<aside class="doc-sidebar">
|
||||
<button class="doc-sidebar-toggle">
|
||||
<i class="fas fa-bars"></i> Sidebar Menu
|
||||
</button>
|
||||
<div class="doc-nav-group">
|
||||
<div class="doc-nav-label">Core Concepts</div>
|
||||
<a href="philosophy.html" class="doc-nav-item active">Design Philosophy</a>
|
||||
<a href="architecture.html" class="doc-nav-item">Architecture Overview</a>
|
||||
<a href="comparison.html" class="doc-nav-item">ChronoSeal vs Others</a>
|
||||
</div>
|
||||
<div class="doc-nav-group">
|
||||
<div class="doc-nav-label">Protocol & API</div>
|
||||
<a href="protocol.html" class="doc-nav-item">Protocol Specification</a>
|
||||
<a href="api.html" class="doc-nav-item">API Reference</a>
|
||||
</div>
|
||||
<div class="doc-nav-group">
|
||||
<div class="doc-nav-label">Operations & Security</div>
|
||||
<a href="threat-model.html" class="doc-nav-item">Threat Model</a>
|
||||
<a href="security.html" class="doc-nav-item">Security Policy</a>
|
||||
<a href="privacy.html" class="doc-nav-item">Privacy Policy</a>
|
||||
<a href="performance.html" class="doc-nav-item">Performance Tuning</a>
|
||||
</div>
|
||||
<div class="doc-nav-group">
|
||||
<div class="doc-nav-label">Lifecycle & Dev</div>
|
||||
<a href="deployment.html" class="doc-nav-item">Deployment Guide</a>
|
||||
<a href="operations.html" class="doc-nav-item">Operations Handbook</a>
|
||||
<a href="testing.html" class="doc-nav-item">Testing Strategy</a>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content Area -->
|
||||
<main class="doc-main">
|
||||
<nav class="doc-breadcrumb" aria-label="Breadcrumb">
|
||||
<a href="index.html">Home</a>
|
||||
<span class="sep">/</span>
|
||||
<a href="#">Core Concepts</a>
|
||||
<span class="sep">/</span>
|
||||
<span>Design Philosophy</span>
|
||||
</nav>
|
||||
|
||||
<article class="doc-content">
|
||||
<h1>ChronoSeal Design Philosophy</h1>
|
||||
<p class="doc-subtitle">ChronoSeal is designed for operators who want a local, inspectable, Unix-native browser attestation layer rather than a hosted anti-bot black box.</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Core Position</h2>
|
||||
<p>ChronoSeal is infrastructure software. It should feel closer to <code>nginx</code>, <code>redis-server</code>, or a small system daemon than to a third-party analytics platform.</p>
|
||||
<p>Our core design priorities include:</p>
|
||||
<ul>
|
||||
<li><strong>CLI-first operation:</strong> Simple commands for service validation and control.</li>
|
||||
<li><strong>Explicit configuration:</strong> Plain text parameters, clear priorities, and no hidden magic.</li>
|
||||
<li><strong>Deterministic protocol:</strong> Verifiable, cryptographic state progressions shared between browser WASM and server Rust.</li>
|
||||
<li><strong>Small runtime surface:</strong> Low footprint, lightweight execution boundaries.</li>
|
||||
<li><strong>Privacy-preserving:</strong> Short-lived sessions with zero long-term profiling or persistent databases.</li>
|
||||
<li><strong>Observable:</strong> Native health probes, JSON statistics, and Prometheus metrics.</li>
|
||||
<li><strong>Telemetry-free:</strong> Absolutely no hidden dashboards, tracking tags, or phone-home systems.</li>
|
||||
</ul>
|
||||
|
||||
<h2>What ChronoSeal Optimizes For</h2>
|
||||
|
||||
<h3>1. Operator Control</h3>
|
||||
<p>Operators should be able to build, run, inspect, configure, monitor, and stop the service with ordinary Unix tools. This is why ChronoSeal provides first-class integrations and commands like <code>chronoseal run</code>, <code>status</code>, <code>health</code>, and native systemd configurations.</p>
|
||||
|
||||
<h3>2. Determinism</h3>
|
||||
<p>The core protocol depends on strict mathematical agreement between server Rust and browser WASM. To guarantee this, all deterministic execution logic is kept in the <code>shared/</code> crate, including hash chains, synthetic gene models, and VM instructions. This eliminates client/server runtime drift.</p>
|
||||
|
||||
<h3>3. Cost Escalation</h3>
|
||||
<p>ChronoSeal does not claim impossible security. We recognize that any client code can eventually be decompiled or emulated. Instead, we focus on cost escalation—making automation expensive by forcing clients to maintain authentic signatures, stack execution history, mutation steps, and interaction signals. The objective is to make cheap automation brittle and expensive automation highly complex to maintain.</p>
|
||||
|
||||
<blockquote>
|
||||
<p><strong>Silent Rejection Semantics:</strong> Heartbeat rejection is intentionally ambiguous. Invalid requests receive the same basic response structure as accepted heartbeats but omit the necessary next-state tokens. This prevents the API from acting as an oracle to guide attackers.</p>
|
||||
</blockquote>
|
||||
|
||||
<h3>4. Privacy by Default</h3>
|
||||
<p>ChronoSeal is not a tracking or analytics engine. It avoids long-term identifiers, cross-site identity graphs, behavioral profiling, and fingerprint history. The database retains only the minimal, ephemeral session state required to validate continuous liveness.</p>
|
||||
|
||||
<h2>Non-Goals</h2>
|
||||
<p>ChronoSeal is explicitly <strong>not</strong>:</p>
|
||||
<ul>
|
||||
<li>A CAPTCHA replacement with interactive puzzles.</li>
|
||||
<li>A fraud scoring system utilizing machine learning risk weights.</li>
|
||||
<li>An authentication provider or OAuth server.</li>
|
||||
<li>A hosted SaaS product under vendor control.</li>
|
||||
<li>A complete defense against fully resourced, human-operated browser farms.</li>
|
||||
</ul>
|
||||
|
||||
<h2>Engineering Biases</h2>
|
||||
<p>When the project faces design trade-offs, we adhere to the following biases:</p>
|
||||
<ul>
|
||||
<li>Prefer <strong>explicit configuration</strong> over implicit magic.</li>
|
||||
<li>Prefer <strong>server-side recomputation</strong> over trusting browser claims.</li>
|
||||
<li>Prefer <strong>bounded execution limits</strong> over unbounded heuristics.</li>
|
||||
<li>Prefer <strong>clear CLI output</strong> over hidden cloud dashboards.</li>
|
||||
<li>Prefer <strong>local self-hosting</strong> over mandatory third-party API dependencies.</li>
|
||||
<li>Prefer <strong>data minimization</strong> over policies and promises alone.</li>
|
||||
</ul>
|
||||
</article>
|
||||
|
||||
<!-- Pager -->
|
||||
<nav class="doc-pager" aria-label="Doc navigation Pager">
|
||||
<a href="index.html" class="doc-pager-link">
|
||||
<div class="doc-pager-label">Previous</div>
|
||||
<div class="doc-pager-title">Home Page</div>
|
||||
</a>
|
||||
<a href="architecture.html" class="doc-pager-link doc-pager-link--next">
|
||||
<div class="doc-pager-label">Next</div>
|
||||
<div class="doc-pager-title">Architecture Overview</div>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<div class="doc-meta">
|
||||
Last Updated: June 2026 (v1.0.2)
|
||||
</div>
|
||||
</main>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="footer">
|
||||
<div class="footer-content">
|
||||
<div class="footer-section">
|
||||
<h4>
|
||||
<img class="logobar" src="assets/logo.svg" alt="ChronoSeal Logo" style="width: 24px; height: 24px; vertical-align: middle;"> ChronoSeal
|
||||
</h4>
|
||||
<p>Next-generation browser attestation daemon built for the modern web.</p>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h4>Resources</h4>
|
||||
<a href="philosophy.html">Design Philosophy</a>
|
||||
<a href="architecture.html">Architecture</a>
|
||||
<a href="protocol.html">Protocol</a>
|
||||
<a href="api.html">API Reference</a>
|
||||
<a href="comparison.html">Comparison</a>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h4>Community</h4>
|
||||
<a href="https://github.com/thakares/chronoseal-rs" target="_blank" rel="noopener"><i class="fab fa-github"></i> GitHub</a>
|
||||
<a href="deployment.html"><i class="fas fa-rocket"></i> Deployment</a>
|
||||
<a href="testing.html"><i class="fas fa-vial"></i> Testing</a>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h4>Legal & Security</h4>
|
||||
<a href="security.html">Security Policy</a>
|
||||
<a href="privacy.html">Privacy Policy</a>
|
||||
<a href="performance.html">Performance Tuning</a>
|
||||
<a href="operations.html">Operations Guide</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<p>© 2026 ChronoSeal Project. Built with 🦀 Rust and ❤️ for open source.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Search Overlay -->
|
||||
<div class="search-overlay">
|
||||
<div class="search-box">
|
||||
<div class="search-input-wrap">
|
||||
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
|
||||
<input type="text" class="search-input" placeholder="Search documentation... (Esc to close)">
|
||||
<span class="search-kbd">/</span>
|
||||
</div>
|
||||
<div class="search-results"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Back to top -->
|
||||
<a href="#" class="back-to-top" aria-label="Back to top">▲</a>
|
||||
|
||||
<!-- JS Scripts -->
|
||||
<script src="js/search.js"></script>
|
||||
<script src="js/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,288 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
||||
<title>Privacy Policy | ChronoSeal Documentation</title>
|
||||
<meta name="description" content="ChronoSeal privacy policy, ephemeral data parameters, storage options, and client-side key-generation boundaries.">
|
||||
<link rel="stylesheet" href="css/chronoseal.css">
|
||||
<link rel="stylesheet" href="css/docs.css">
|
||||
<link rel="stylesheet" href="css/print.css" media="print">
|
||||
<link rel="manifest" href="site.webmanifest">
|
||||
<link rel="icon" href="assets/logo.svg" type="image/svg+xml">
|
||||
|
||||
<!-- Font Awesome for Icons -->
|
||||
<link rel="preconnect" href="https://cdnjs.cloudflare.com" crossorigin />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
|
||||
integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw=="
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Floating Background Spheres -->
|
||||
<div class="bg-animation">
|
||||
<div class="gradient-sphere sphere-1"></div>
|
||||
<div class="gradient-sphere sphere-2"></div>
|
||||
<div class="gradient-sphere sphere-3"></div>
|
||||
</div>
|
||||
|
||||
<!-- Header / Navbar -->
|
||||
<nav class="navbar" id="navbar">
|
||||
<div class="nav-container">
|
||||
<a href="index.html" class="logo">
|
||||
<img class="logobar" src="assets/logo.svg" alt="ChronoSeal Logo">
|
||||
<span>ChronoSeal</span>
|
||||
</a>
|
||||
<div class="nav-links" id="navLinks">
|
||||
<a href="philosophy.html">Philosophy</a>
|
||||
<a href="architecture.html">Architecture</a>
|
||||
<a href="protocol.html">Protocol</a>
|
||||
<a href="comparison.html">Comparison</a>
|
||||
<div class="nav-dropdown">
|
||||
<a href="#" class="nav-link">More <small>▼</small></a>
|
||||
<div class="nav-dropdown-menu">
|
||||
<a href="api.html">API Reference</a>
|
||||
<a href="deployment.html">Deployment Guide</a>
|
||||
<a href="operations.html">Operations Guide</a>
|
||||
<a href="testing.html">Testing Strategy</a>
|
||||
<a href="performance.html">Performance Tuning</a>
|
||||
<a href="threat-model.html">Threat Model</a>
|
||||
<a href="security.html">Security Policy</a>
|
||||
<a href="privacy.html" class="active">Privacy Policy</a>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-ghost btn-sm btn-icon search-trigger" aria-label="Search" onclick="ChronoSearch.open()">
|
||||
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" width="16" height="16"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
|
||||
</button>
|
||||
<a href="https://github.com/thakares/chronoseal-rs" class="github-btn" target="_blank" rel="noopener">
|
||||
<i class="fab fa-github"></i> GitHub
|
||||
</a>
|
||||
</div>
|
||||
<button class="nav-toggle" id="menuBtn" aria-label="Toggle Navigation">
|
||||
<i class="fas fa-bars"></i>
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Doc Page Shell -->
|
||||
<div class="doc-page">
|
||||
<div class="doc-layout">
|
||||
|
||||
<!-- Sidebar -->
|
||||
<aside class="doc-sidebar">
|
||||
<button class="doc-sidebar-toggle">
|
||||
<i class="fas fa-bars"></i> Sidebar Menu
|
||||
</button>
|
||||
<div class="doc-nav-group">
|
||||
<div class="doc-nav-label">Core Concepts</div>
|
||||
<a href="philosophy.html" class="doc-nav-item">Design Philosophy</a>
|
||||
<a href="architecture.html" class="doc-nav-item">Architecture Overview</a>
|
||||
<a href="comparison.html" class="doc-nav-item">ChronoSeal vs Others</a>
|
||||
</div>
|
||||
<div class="doc-nav-group">
|
||||
<div class="doc-nav-label">Protocol & API</div>
|
||||
<a href="protocol.html" class="doc-nav-item">Protocol Specification</a>
|
||||
<a href="api.html" class="doc-nav-item">API Reference</a>
|
||||
</div>
|
||||
<div class="doc-nav-group">
|
||||
<div class="doc-nav-label">Operations & Security</div>
|
||||
<a href="threat-model.html" class="doc-nav-item">Threat Model</a>
|
||||
<a href="security.html" class="doc-nav-item">Security Policy</a>
|
||||
<a href="privacy.html" class="doc-nav-item active">Privacy Policy</a>
|
||||
<a href="performance.html" class="doc-nav-item">Performance Tuning</a>
|
||||
</div>
|
||||
<div class="doc-nav-group">
|
||||
<div class="doc-nav-label">Lifecycle & Dev</div>
|
||||
<a href="deployment.html" class="doc-nav-item">Deployment Guide</a>
|
||||
<a href="operations.html" class="doc-nav-item">Operations Handbook</a>
|
||||
<a href="testing.html" class="doc-nav-item">Testing Strategy</a>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content Area -->
|
||||
<main class="doc-main">
|
||||
<nav class="doc-breadcrumb" aria-label="Breadcrumb">
|
||||
<a href="index.html">Home</a>
|
||||
<span class="sep">/</span>
|
||||
<a href="#">Operations & Security</a>
|
||||
<span class="sep">/</span>
|
||||
<span>Privacy Policy</span>
|
||||
</nav>
|
||||
|
||||
<article class="doc-content">
|
||||
<h1>ChronoSeal Privacy Policy</h1>
|
||||
<p class="doc-subtitle">ChronoSeal is a privacy-oriented browser attestation system. It is designed to validate short-lived session continuity without creating persistent user profiles.</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<p>This document describes what ChronoSeal itself collects and stores. Applications that integrate ChronoSeal may collect additional data under their own policies.</p>
|
||||
|
||||
<h2>Data ChronoSeal Processes</h2>
|
||||
<p>ChronoSeal processes only the minimum protocol parameters needed to validate a live browser session. We collect no personal data, identifiers, or tracking tokens.</p>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Data Parameter</th>
|
||||
<th>Core Purpose</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>session_id</code></td>
|
||||
<td>Opaque session lookup key. Generated randomly, unrelated to user identity.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Public Key</td>
|
||||
<td>Required to verify signed heartbeats for the specific session.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>salt</code></td>
|
||||
<td>Rotated material used for secure hash-chain progression.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>initial_hash</code> / <code>prev_hash</code></td>
|
||||
<td>Provides replay-resistant cryptographic continuity.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Timestamp</td>
|
||||
<td>Enforces drift bounds and liveness thresholds.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Mouse Event Samples</td>
|
||||
<td>Lightweight validation of input activity plausibility.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>VM Stack State</td>
|
||||
<td>Verifies correct execution of the randomized math program.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Fingerprint Fields</td>
|
||||
<td>Sanity validation (Screen aspect ratio, device pixel ratio, concurrency CPU count).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Gene Commitment</td>
|
||||
<td>Verifies client execution of server-issued synthetic gene mutations.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Expiration Time</td>
|
||||
<td>Manages session lifecycle and garbage-collection checks.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2>Data ChronoSeal Does Not Collect</h2>
|
||||
<p>ChronoSeal is built on the principle of data minimization. It does <strong>not</strong> collect, process, or build databases of:</p>
|
||||
<ul>
|
||||
<li>Personal information (names, emails, logins, accounts).</li>
|
||||
<li>Browser history or page navigation tracks.</li>
|
||||
<li>IP address history or device fingerprints across multiple sessions.</li>
|
||||
<li>Location history or geological coordinate lookups.</li>
|
||||
<li>Cross-site tracking identifiers or cookie graphs.</li>
|
||||
</ul>
|
||||
|
||||
<h2>Session Lifetime & Expiration</h2>
|
||||
<p>Sessions are short-lived. By default, they expire after <strong>30 minutes</strong> (configurable via <code>expiration_minutes</code>). Expired sessions are continuously purged from memory and storage by background cleanup workers. If the daemon runs in-memory, all session history is immediately lost when the process exits.</p>
|
||||
|
||||
<h2>Storage Modes & Persistence</h2>
|
||||
<p>Storage is fully configured by the operator and supports the following modes:</p>
|
||||
<ul>
|
||||
<li><strong>sqlite-in-memory:</strong> Default mode. Process memory only, completely ephemeral.</li>
|
||||
<li><strong>sqlite-in-disk:</strong> Persisted to local SQLite file for recovery across restarts.</li>
|
||||
<li><strong>valkey:</strong> Distributed storage, persisted according to Valkey deployment settings.</li>
|
||||
</ul>
|
||||
|
||||
<h2>Client-Side Cryptographic Key Custody</h2>
|
||||
<p>The browser WASM runtime generates an Ed25519 keypair locally when the session starts.</p>
|
||||
<ul>
|
||||
<li>The <strong>public key</strong> is transmitted to the server during the <code>/init</code> handshake.</li>
|
||||
<li>The <strong>private key</strong> is retained in browser WASM memory and is <strong>never transmitted</strong> over the network.</li>
|
||||
<li>Heartbeat payloads are signed locally in the browser before submission.</li>
|
||||
</ul>
|
||||
<p>This provides strong session continuity without creating long-term tracking identifiers.</p>
|
||||
|
||||
<div class="doc-alert doc-alert--note">
|
||||
<div class="doc-alert-icon"><i class="fas fa-info-circle"></i></div>
|
||||
<div class="doc-alert-body">
|
||||
<strong>Silent Rejection:</strong> To minimize feedback for attackers, ChronoSeal returns identical success responses <code>{"status":"ok"}</code> for accepted and rejected heartbeat requests, omitting next-state fields on rejection.
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<!-- Pager -->
|
||||
<nav class="doc-pager" aria-label="Doc navigation Pager">
|
||||
<a href="philosophy.html" class="doc-pager-link">
|
||||
<div class="doc-pager-label">Previous</div>
|
||||
<div class="doc-pager-title">Design Philosophy</div>
|
||||
</a>
|
||||
<a href="security.html" class="doc-pager-link doc-pager-link--next">
|
||||
<div class="doc-pager-label">Next</div>
|
||||
<div class="doc-pager-title">Security Policy</div>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<div class="doc-meta">
|
||||
Last Updated: June 2026 (v1.0.2)
|
||||
</div>
|
||||
</main>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="footer">
|
||||
<div class="footer-content">
|
||||
<div class="footer-section">
|
||||
<h4>
|
||||
<img class="logobar" src="assets/logo.svg" alt="ChronoSeal Logo" style="width: 24px; height: 24px; vertical-align: middle;"> ChronoSeal
|
||||
</h4>
|
||||
<p>Next-generation browser attestation daemon built for the modern web.</p>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h4>Resources</h4>
|
||||
<a href="philosophy.html">Design Philosophy</a>
|
||||
<a href="architecture.html">Architecture</a>
|
||||
<a href="protocol.html">Protocol</a>
|
||||
<a href="api.html">API Reference</a>
|
||||
<a href="comparison.html">Comparison</a>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h4>Community</h4>
|
||||
<a href="https://github.com/thakares/chronoseal-rs" target="_blank" rel="noopener"><i class="fab fa-github"></i> GitHub</a>
|
||||
<a href="deployment.html"><i class="fas fa-rocket"></i> Deployment</a>
|
||||
<a href="testing.html"><i class="fas fa-vial"></i> Testing</a>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h4>Legal & Security</h4>
|
||||
<a href="security.html">Security Policy</a>
|
||||
<a href="privacy.html">Privacy Policy</a>
|
||||
<a href="performance.html">Performance Tuning</a>
|
||||
<a href="operations.html">Operations Guide</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<p>© 2026 ChronoSeal Project. Built with 🦀 Rust and ❤️ for open source.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Search Overlay -->
|
||||
<div class="search-overlay">
|
||||
<div class="search-box">
|
||||
<div class="search-input-wrap">
|
||||
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
|
||||
<input type="text" class="search-input" placeholder="Search documentation... (Esc to close)">
|
||||
<span class="search-kbd">/</span>
|
||||
</div>
|
||||
<div class="search-results"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Back to top -->
|
||||
<a href="#" class="back-to-top" aria-label="Back to top">▲</a>
|
||||
|
||||
<!-- JS Scripts -->
|
||||
<script src="js/search.js"></script>
|
||||
<script src="js/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,261 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
||||
<title>Protocol Specification | ChronoSeal Documentation</title>
|
||||
<meta name="description" content="ChronoSeal cryptographic wire protocol specifications, state transition mechanics, VM instruction opcodes, and stability guidelines.">
|
||||
<link rel="stylesheet" href="css/chronoseal.css">
|
||||
<link rel="stylesheet" href="css/docs.css">
|
||||
<link rel="stylesheet" href="css/print.css" media="print">
|
||||
<link rel="manifest" href="site.webmanifest">
|
||||
<link rel="icon" href="assets/logo.svg" type="image/svg+xml">
|
||||
|
||||
<!-- Font Awesome for Icons -->
|
||||
<link rel="preconnect" href="https://cdnjs.cloudflare.com" crossorigin />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
|
||||
integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw=="
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Floating Background Spheres -->
|
||||
<div class="bg-animation">
|
||||
<div class="gradient-sphere sphere-1"></div>
|
||||
<div class="gradient-sphere sphere-2"></div>
|
||||
<div class="gradient-sphere sphere-3"></div>
|
||||
</div>
|
||||
|
||||
<!-- Header / Navbar -->
|
||||
<nav class="navbar" id="navbar">
|
||||
<div class="nav-container">
|
||||
<a href="index.html" class="logo">
|
||||
<img class="logobar" src="assets/logo.svg" alt="ChronoSeal Logo">
|
||||
<span>ChronoSeal</span>
|
||||
</a>
|
||||
<div class="nav-links" id="navLinks">
|
||||
<a href="philosophy.html">Philosophy</a>
|
||||
<a href="architecture.html">Architecture</a>
|
||||
<a href="protocol.html" class="active">Protocol</a>
|
||||
<a href="comparison.html">Comparison</a>
|
||||
<div class="nav-dropdown">
|
||||
<a href="#" class="nav-link">More <small>▼</small></a>
|
||||
<div class="nav-dropdown-menu">
|
||||
<a href="api.html">API Reference</a>
|
||||
<a href="deployment.html">Deployment Guide</a>
|
||||
<a href="operations.html">Operations Guide</a>
|
||||
<a href="testing.html">Testing Strategy</a>
|
||||
<a href="performance.html">Performance Tuning</a>
|
||||
<a href="threat-model.html">Threat Model</a>
|
||||
<a href="security.html">Security Policy</a>
|
||||
<a href="privacy.html">Privacy Policy</a>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-ghost btn-sm btn-icon search-trigger" aria-label="Search" onclick="ChronoSearch.open()">
|
||||
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" width="16" height="16"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
|
||||
</button>
|
||||
<a href="https://github.com/thakares/chronoseal-rs" class="github-btn" target="_blank" rel="noopener">
|
||||
<i class="fab fa-github"></i> GitHub
|
||||
</a>
|
||||
</div>
|
||||
<button class="nav-toggle" id="menuBtn" aria-label="Toggle Navigation">
|
||||
<i class="fas fa-bars"></i>
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Doc Page Shell -->
|
||||
<div class="doc-page">
|
||||
<div class="doc-layout">
|
||||
|
||||
<!-- Sidebar -->
|
||||
<aside class="doc-sidebar">
|
||||
<button class="doc-sidebar-toggle">
|
||||
<i class="fas fa-bars"></i> Sidebar Menu
|
||||
</button>
|
||||
<div class="doc-nav-group">
|
||||
<div class="doc-nav-label">Core Concepts</div>
|
||||
<a href="philosophy.html" class="doc-nav-item">Design Philosophy</a>
|
||||
<a href="architecture.html" class="doc-nav-item">Architecture Overview</a>
|
||||
<a href="comparison.html" class="doc-nav-item">ChronoSeal vs Others</a>
|
||||
</div>
|
||||
<div class="doc-nav-group">
|
||||
<div class="doc-nav-label">Protocol & API</div>
|
||||
<a href="protocol.html" class="doc-nav-item active">Protocol Specification</a>
|
||||
<a href="api.html" class="doc-nav-item">API Reference</a>
|
||||
</div>
|
||||
<div class="doc-nav-group">
|
||||
<div class="doc-nav-label">Operations & Security</div>
|
||||
<a href="threat-model.html" class="doc-nav-item">Threat Model</a>
|
||||
<a href="security.html" class="doc-nav-item">Security Policy</a>
|
||||
<a href="privacy.html" class="doc-nav-item">Privacy Policy</a>
|
||||
<a href="performance.html" class="doc-nav-item">Performance Tuning</a>
|
||||
</div>
|
||||
<div class="doc-nav-group">
|
||||
<div class="doc-nav-label">Lifecycle & Dev</div>
|
||||
<a href="deployment.html" class="doc-nav-item">Deployment Guide</a>
|
||||
<a href="operations.html" class="doc-nav-item">Operations Handbook</a>
|
||||
<a href="testing.html" class="doc-nav-item">Testing Strategy</a>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content Area -->
|
||||
<main class="doc-main">
|
||||
<nav class="doc-breadcrumb" aria-label="Breadcrumb">
|
||||
<a href="index.html">Home</a>
|
||||
<span class="sep">/</span>
|
||||
<a href="#">Protocol & API</a>
|
||||
<span class="sep">/</span>
|
||||
<span>Protocol Specification</span>
|
||||
</nav>
|
||||
|
||||
<article class="doc-content">
|
||||
<h1>ChronoSeal Protocol Specification</h1>
|
||||
<p class="doc-subtitle">This document defines the formal wire protocol, state transitions, cryptographic primitives, and execution invariants of the ChronoSeal attestation system.</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Sequence Flow</h2>
|
||||
<p>ChronoSeal operates as a stateful, sequential challenge-response sequence over HTTP/REST between the client browser (WASM/JS) and the native server daemon:</p>
|
||||
|
||||
<!-- Inline Protocol Flow Container -->
|
||||
<div id="diagram-protocol" class="arch-diagram" style="margin-top:24px"></div>
|
||||
|
||||
<h2>Cryptographic Transitions</h2>
|
||||
|
||||
<h3>1. Handshake Phase (<code>/init</code>)</h3>
|
||||
<p>The client registers a 32-byte Ed25519 verifying key represented as a hex string. The server then performs the following steps:</p>
|
||||
<ol>
|
||||
<li>Generates a 32-byte session ID (<code>session_id</code>) and a 16-byte initial salt (<code>S_0</code>).</li>
|
||||
<li>Computes the initial hash chain head:<br>
|
||||
<code>H_0 = Blake3(session_id || public_key || S_0)</code>
|
||||
</li>
|
||||
<li>Generates a randomized VM program (between 8 and 16 bytes of opcodes).</li>
|
||||
<li>Creates the initial mutation order program <code>M_1</code>.</li>
|
||||
<li>Persists the initial session record.</li>
|
||||
</ol>
|
||||
|
||||
<h3>2. Heartbeat Progression (<code>/hb</code>)</h3>
|
||||
<p>For each heartbeat step <code>n >= 1</code>, the client submits:
|
||||
<code>prev_hash</code> (H_n-1), <code>timestamp</code>, <code>entropy_data</code>, <code>stack_state</code> (VM stack + instruction pointer), <code>gene_commitment</code>, and the <code>signature</code>.
|
||||
</p>
|
||||
<p>The server receives the request and executes these validations:</p>
|
||||
<ol>
|
||||
<li>Loads the session record from storage, enforcing an optimistic concurrency lock (CAS) to confirm that the database <code>last_hash</code> matches the request <code>prev_hash</code>.</li>
|
||||
<li>Validates the Ed25519 signature against the canonical alphabetical serialization.</li>
|
||||
<li>Re-executes the session's VM opcodes and asserts the client's VM <code>stack_state</code> matches the output.</li>
|
||||
<li>Applies the mutation order <code>M_n</code> to the stored gene buffer, computes the expected commitment:<br>
|
||||
<code>C_n = Blake3(CandidateGene || session_id || n)</code><br>
|
||||
Asserts the client's <code>gene_commitment</code> matches.
|
||||
</li>
|
||||
<li>Validates clock alignment: <code>|timestamp_server - timestamp_client| <= max_drift</code>.</li>
|
||||
<li>Advances the cryptographic hash chain head:<br>
|
||||
<code>H_n = Blake3(H_n-1 || timestamp || Blake3(entropy_data) || Blake3(S_n) || S_n-1)</code>
|
||||
</li>
|
||||
<li>Rotates the salt to <code>S_n</code> and compiles the next mutation program <code>M_n+1</code>.</li>
|
||||
</ol>
|
||||
|
||||
<h2>VM Instruction Specification</h2>
|
||||
<p>The browser VM executes opcodes sequentially on a 32-bit unsigned integer stack. The supported instruction set consists of:</p>
|
||||
<ul>
|
||||
<li><code>0x00</code>: Pushes the next 4 bytes in the opcode stream onto the stack as a <code>u32</code> (little-endian).</li>
|
||||
<li><code>0x01</code>: Wrapping Add (<code>a.wrapping_add(b)</code>). Requires at least 2 stack elements.</li>
|
||||
<li><code>0x02</code>: Wrapping Sub (<code>a.wrapping_sub(b)</code>). Requires at least 2 stack elements.</li>
|
||||
<li><code>0x03</code>: Wrapping Mul (<code>a.wrapping_mul(b)</code>). Requires at least 2 stack elements.</li>
|
||||
<li><code>0x04</code>: Bitwise XOR (<code>a ^ b</code>). Requires at least 2 stack elements.</li>
|
||||
<li><code>0x05</code>: Bitwise AND (<code>a & b</code>). Requires at least 2 stack elements.</li>
|
||||
<li><code>0x06</code>: Bitwise OR (<code>a | b</code>). Requires at least 2 stack elements.</li>
|
||||
<li><code>0x07</code>: Rotate Left (<code>a.rotate_left(b % 32)</code>). Requires at least 2 stack elements.</li>
|
||||
<li><code>0x08</code>: Unary Bitwise NOT (<code>!a</code>). Requires at least 1 stack element.</li>
|
||||
<li><code>0x09</code>: Hash Stack. Hashes all stack elements using BLAKE3 and reduces it to a single <code>u32</code> value, clearing the stack and pushing the result.</li>
|
||||
<li><em>Any other opcode:</em> Terminates VM execution immediately.</li>
|
||||
</ul>
|
||||
|
||||
<h2>Protocol Stability Policy</h2>
|
||||
<p>The public surface of ChronoSeal is frozen at version 1.0. This includes:</p>
|
||||
<ul>
|
||||
<li><strong>Wire API:</strong> The JSON schemas and routes of <code>POST /init</code> and <code>POST /hb</code>.</li>
|
||||
<li><strong>State Transition:</strong> Hash chain folding, VM opcodes, and gene mutation mechanics.</li>
|
||||
<li><strong>CLI & Config:</strong> Daemon commands and TOML configuration keys.</li>
|
||||
</ul>
|
||||
<p>Internal details are subject to change without notice. Integrations must not depend on database schemas, Valkey key structures, or internal Rust SDK APIs.</p>
|
||||
</article>
|
||||
|
||||
<!-- Pager -->
|
||||
<nav class="doc-pager" aria-label="Doc navigation Pager">
|
||||
<a href="architecture.html" class="doc-pager-link">
|
||||
<div class="doc-pager-label">Previous</div>
|
||||
<div class="doc-pager-title">Architecture Overview</div>
|
||||
</a>
|
||||
<a href="api.html" class="doc-pager-link doc-pager-link--next">
|
||||
<div class="doc-pager-label">Next</div>
|
||||
<div class="doc-pager-title">API Reference</div>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<div class="doc-meta">
|
||||
Last Updated: June 2026 (v1.0.2)
|
||||
</div>
|
||||
</main>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="footer">
|
||||
<div class="footer-content">
|
||||
<div class="footer-section">
|
||||
<h4>
|
||||
<img class="logobar" src="assets/logo.svg" alt="ChronoSeal Logo" style="width: 24px; height: 24px; vertical-align: middle;"> ChronoSeal
|
||||
</h4>
|
||||
<p>Next-generation browser attestation daemon built for the modern web.</p>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h4>Resources</h4>
|
||||
<a href="philosophy.html">Design Philosophy</a>
|
||||
<a href="architecture.html">Architecture</a>
|
||||
<a href="protocol.html">Protocol</a>
|
||||
<a href="api.html">API Reference</a>
|
||||
<a href="comparison.html">Comparison</a>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h4>Community</h4>
|
||||
<a href="https://github.com/thakares/chronoseal-rs" target="_blank" rel="noopener"><i class="fab fa-github"></i> GitHub</a>
|
||||
<a href="deployment.html"><i class="fas fa-rocket"></i> Deployment</a>
|
||||
<a href="testing.html"><i class="fas fa-vial"></i> Testing</a>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h4>Legal & Security</h4>
|
||||
<a href="security.html">Security Policy</a>
|
||||
<a href="privacy.html">Privacy Policy</a>
|
||||
<a href="performance.html">Performance Tuning</a>
|
||||
<a href="operations.html">Operations Guide</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<p>© 2026 ChronoSeal Project. Built with 🦀 Rust and ❤️ for open source.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Search Overlay -->
|
||||
<div class="search-overlay">
|
||||
<div class="search-box">
|
||||
<div class="search-input-wrap">
|
||||
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
|
||||
<input type="text" class="search-input" placeholder="Search documentation... (Esc to close)">
|
||||
<span class="search-kbd">/</span>
|
||||
</div>
|
||||
<div class="search-results"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Back to top -->
|
||||
<a href="#" class="back-to-top" aria-label="Back to top">▲</a>
|
||||
|
||||
<!-- JS Scripts -->
|
||||
<script src="js/search.js"></script>
|
||||
<script src="js/app.js"></script>
|
||||
<script src="js/diagrams.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,6 @@
|
||||
# www.robotstxt.org/
|
||||
|
||||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
Sitemap: https://chronoseal.nx9.in/sitemap.xml
|
||||
|
After Width: | Height: | Size: 192 KiB |
@@ -0,0 +1,237 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
||||
<title>Security Policy | ChronoSeal Documentation</title>
|
||||
<meta name="description" content="ChronoSeal security policy, reporting vulnerabilities, response workflow, and security boundaries.">
|
||||
<link rel="stylesheet" href="css/chronoseal.css">
|
||||
<link rel="stylesheet" href="css/docs.css">
|
||||
<link rel="stylesheet" href="css/print.css" media="print">
|
||||
<link rel="manifest" href="site.webmanifest">
|
||||
<link rel="icon" href="assets/logo.svg" type="image/svg+xml">
|
||||
|
||||
<!-- Font Awesome for Icons -->
|
||||
<link rel="preconnect" href="https://cdnjs.cloudflare.com" crossorigin />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
|
||||
integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw=="
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Floating Background Spheres -->
|
||||
<div class="bg-animation">
|
||||
<div class="gradient-sphere sphere-1"></div>
|
||||
<div class="gradient-sphere sphere-2"></div>
|
||||
<div class="gradient-sphere sphere-3"></div>
|
||||
</div>
|
||||
|
||||
<!-- Header / Navbar -->
|
||||
<nav class="navbar" id="navbar">
|
||||
<div class="nav-container">
|
||||
<a href="index.html" class="logo">
|
||||
<img class="logobar" src="assets/logo.svg" alt="ChronoSeal Logo">
|
||||
<span>ChronoSeal</span>
|
||||
</a>
|
||||
<div class="nav-links" id="navLinks">
|
||||
<a href="philosophy.html">Philosophy</a>
|
||||
<a href="architecture.html">Architecture</a>
|
||||
<a href="protocol.html">Protocol</a>
|
||||
<a href="comparison.html">Comparison</a>
|
||||
<div class="nav-dropdown">
|
||||
<a href="#" class="nav-link">More <small>▼</small></a>
|
||||
<div class="nav-dropdown-menu">
|
||||
<a href="api.html">API Reference</a>
|
||||
<a href="deployment.html">Deployment Guide</a>
|
||||
<a href="operations.html">Operations Guide</a>
|
||||
<a href="testing.html">Testing Strategy</a>
|
||||
<a href="performance.html">Performance Tuning</a>
|
||||
<a href="threat-model.html">Threat Model</a>
|
||||
<a href="security.html" class="active">Security Policy</a>
|
||||
<a href="privacy.html">Privacy Policy</a>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-ghost btn-sm btn-icon search-trigger" aria-label="Search" onclick="ChronoSearch.open()">
|
||||
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" width="16" height="16"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
|
||||
</button>
|
||||
<a href="https://github.com/thakares/chronoseal-rs" class="github-btn" target="_blank" rel="noopener">
|
||||
<i class="fab fa-github"></i> GitHub
|
||||
</a>
|
||||
</div>
|
||||
<button class="nav-toggle" id="menuBtn" aria-label="Toggle Navigation">
|
||||
<i class="fas fa-bars"></i>
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Doc Page Shell -->
|
||||
<div class="doc-page">
|
||||
<div class="doc-layout">
|
||||
|
||||
<!-- Sidebar -->
|
||||
<aside class="doc-sidebar">
|
||||
<button class="doc-sidebar-toggle">
|
||||
<i class="fas fa-bars"></i> Sidebar Menu
|
||||
</button>
|
||||
<div class="doc-nav-group">
|
||||
<div class="doc-nav-label">Core Concepts</div>
|
||||
<a href="philosophy.html" class="doc-nav-item">Design Philosophy</a>
|
||||
<a href="architecture.html" class="doc-nav-item">Architecture Overview</a>
|
||||
<a href="comparison.html" class="doc-nav-item">ChronoSeal vs Others</a>
|
||||
</div>
|
||||
<div class="doc-nav-group">
|
||||
<div class="doc-nav-label">Protocol & API</div>
|
||||
<a href="protocol.html" class="doc-nav-item">Protocol Specification</a>
|
||||
<a href="api.html" class="doc-nav-item">API Reference</a>
|
||||
</div>
|
||||
<div class="doc-nav-group">
|
||||
<div class="doc-nav-label">Operations & Security</div>
|
||||
<a href="threat-model.html" class="doc-nav-item">Threat Model</a>
|
||||
<a href="security.html" class="doc-nav-item active">Security Policy</a>
|
||||
<a href="privacy.html" class="doc-nav-item">Privacy Policy</a>
|
||||
<a href="performance.html" class="doc-nav-item">Performance Tuning</a>
|
||||
</div>
|
||||
<div class="doc-nav-group">
|
||||
<div class="doc-nav-label">Lifecycle & Dev</div>
|
||||
<a href="deployment.html" class="doc-nav-item">Deployment Guide</a>
|
||||
<a href="operations.html" class="doc-nav-item">Operations Handbook</a>
|
||||
<a href="testing.html" class="doc-nav-item">Testing Strategy</a>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content Area -->
|
||||
<main class="doc-main">
|
||||
<nav class="doc-breadcrumb" aria-label="Breadcrumb">
|
||||
<a href="index.html">Home</a>
|
||||
<span class="sep">/</span>
|
||||
<a href="#">Operations & Security</a>
|
||||
<span class="sep">/</span>
|
||||
<span>Security Policy</span>
|
||||
</nav>
|
||||
|
||||
<article class="doc-content">
|
||||
<h1>Security Policy</h1>
|
||||
<p class="doc-subtitle">This document outlines responsible disclosure practices, project security scopes, and hardening details for ChronoSeal.</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Reporting Vulnerabilities</h2>
|
||||
<p>Please do not disclose security vulnerabilities publicly before coordinate disclosure. If you identify a potential security issue in ChronoSeal, contact the maintainers privately.</p>
|
||||
<p>When reporting, please include the following details to assist our review:</p>
|
||||
<ul>
|
||||
<li>Detailed description of the issue and potential impact.</li>
|
||||
<li>Step-by-step reproduction guide or proof-of-concept (PoC) script.</li>
|
||||
<li>Affected versions of the daemon, WASM package, or shared components.</li>
|
||||
<li>Any proposed mitigations or code patches.</li>
|
||||
</ul>
|
||||
|
||||
<div class="doc-alert doc-alert--warn">
|
||||
<div class="doc-alert-icon"><i class="fas fa-exclamation-triangle"></i></div>
|
||||
<div class="doc-alert-body">
|
||||
<strong>Do not file public GitHub issues for security vulnerabilities.</strong> Email or message the project maintainers directly via security contacts defined in the project repository.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Project Scope</h2>
|
||||
<p>ChronoSeal operates as a cost-raising security layer, specifically:</p>
|
||||
<ul>
|
||||
<li><strong>Anti-scraper middleware:</strong> Restricts low-resource, simple command-line scripts and scrapers.</li>
|
||||
<li><strong>Behavioral attestation layer:</strong> Asserts basic input sanity signals.</li>
|
||||
<li><strong>Cryptographic continuity verifier:</strong> Guarantees that heartbeats progress chronologically along a server-controlled path.</li>
|
||||
</ul>
|
||||
<p>We actively harden the following boundaries and welcome reports regarding:</p>
|
||||
<ul>
|
||||
<li>Bypassing the hash-chain sequence without having session secrets.</li>
|
||||
<li>Replaying previously accepted heartbeat requests.</li>
|
||||
<li>Causing server panics, database memory leaks, or execution exhaustion.</li>
|
||||
<li>Bypassing stateful synthetic gene mutations.</li>
|
||||
<li>Security response header bypasses or CSP evasion.</li>
|
||||
</ul>
|
||||
|
||||
<h2>Hardened Security Features (v1.0.2)</h2>
|
||||
<p>The latest version includes the following security hardening implementations:</p>
|
||||
<ul>
|
||||
<li><strong>Sanitized Fingerprints:</strong> Strict validation of aspect ratio, DPR, and CPU concurrency limits to reject malformed inputs.</li>
|
||||
<li><strong>Stack-Depth Protection:</strong> Bounded stack limits (capped at 512) to prevent VM buffer overflows.</li>
|
||||
<li><strong>DashMap Rate Limiter:</strong> Hardened concurrent rate limiting to mitigate denial-of-service attempts.</li>
|
||||
<li><strong>Response Headers:</strong> Automatic inject of Content-Security-Policy (CSP), X-Frame-Options, and Referrer-Policy headers to secure browser pages.</li>
|
||||
</ul>
|
||||
</article>
|
||||
|
||||
<!-- Pager -->
|
||||
<nav class="doc-pager" aria-label="Doc navigation Pager">
|
||||
<a href="privacy.html" class="doc-pager-link">
|
||||
<div class="doc-pager-label">Previous</div>
|
||||
<div class="doc-pager-title">Privacy Policy</div>
|
||||
</a>
|
||||
<a href="comparison.html" class="doc-pager-link doc-pager-link--next">
|
||||
<div class="doc-pager-label">Next</div>
|
||||
<div class="doc-pager-title">ChronoSeal vs Others</div>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<div class="doc-meta">
|
||||
Last Updated: June 2026 (v1.0.2)
|
||||
</div>
|
||||
</main>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="footer">
|
||||
<div class="footer-content">
|
||||
<div class="footer-section">
|
||||
<h4>
|
||||
<img class="logobar" src="assets/logo.svg" alt="ChronoSeal Logo" style="width: 24px; height: 24px; vertical-align: middle;"> ChronoSeal
|
||||
</h4>
|
||||
<p>Next-generation browser attestation daemon built for the modern web.</p>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h4>Resources</h4>
|
||||
<a href="philosophy.html">Design Philosophy</a>
|
||||
<a href="architecture.html">Architecture</a>
|
||||
<a href="protocol.html">Protocol</a>
|
||||
<a href="api.html">API Reference</a>
|
||||
<a href="comparison.html">Comparison</a>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h4>Community</h4>
|
||||
<a href="https://github.com/thakares/chronoseal-rs" target="_blank" rel="noopener"><i class="fab fa-github"></i> GitHub</a>
|
||||
<a href="deployment.html"><i class="fas fa-rocket"></i> Deployment</a>
|
||||
<a href="testing.html"><i class="fas fa-vial"></i> Testing</a>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h4>Legal & Security</h4>
|
||||
<a href="security.html">Security Policy</a>
|
||||
<a href="privacy.html">Privacy Policy</a>
|
||||
<a href="performance.html">Performance Tuning</a>
|
||||
<a href="operations.html">Operations Guide</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<p>© 2026 ChronoSeal Project. Built with 🦀 Rust and ❤️ for open source.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Search Overlay -->
|
||||
<div class="search-overlay">
|
||||
<div class="search-box">
|
||||
<div class="search-input-wrap">
|
||||
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
|
||||
<input type="text" class="search-input" placeholder="Search documentation... (Esc to close)">
|
||||
<span class="search-kbd">/</span>
|
||||
</div>
|
||||
<div class="search-results"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Back to top -->
|
||||
<a href="#" class="back-to-top" aria-label="Back to top">▲</a>
|
||||
|
||||
<!-- JS Scripts -->
|
||||
<script src="js/search.js"></script>
|
||||
<script src="js/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "ChronoSeal",
|
||||
"short_name": "ChronoSeal",
|
||||
"description": "Unix-native cryptographic attestation daemon for browser session continuity",
|
||||
"start_url": "/index.html",
|
||||
"display": "standalone",
|
||||
"background_color": "#0d0f14",
|
||||
"theme_color": "#0a0c10",
|
||||
"orientation": "portrait-primary",
|
||||
"categories": ["developer", "security", "utilities"],
|
||||
"icons": [
|
||||
{
|
||||
"src": "/assets/logo.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "any maskable"
|
||||
},
|
||||
{
|
||||
"src": "/assets/logo.svg",
|
||||
"sizes": "any",
|
||||
"type": "image/svg+xml",
|
||||
"purpose": "any maskable"
|
||||
}
|
||||
]
|
||||
}
|
||||