Files
quicproquo/playbooks
Christian Nennemann 394199b19b fix: security hardening — 40 findings from full codebase review
Full codebase review by 4 independent agents (security, architecture,
code quality, correctness) identified ~80 findings. This commit fixes 40
of them across all workspace crates.

Critical fixes:
- Federation service: validate origin against mTLS cert CN/SAN (C1)
- WS bridge: add DM channel auth, size limits, rate limiting (C2)
- hpke_seal: panic on error instead of silent empty ciphertext (C3)
- hpke_setup_sender_and_export: error on parse fail, no PQ downgrade (C7)

Security fixes:
- Zeroize: seed_bytes() returns Zeroizing<[u8;32]>, private_to_bytes()
  returns Zeroizing<Vec<u8>>, ClientAuth.access_token, SessionState.password,
  conversation hex_key all wrapped in Zeroizing
- Keystore: 0o600 file permissions on Unix
- MeshIdentity: 0o600 file permissions on Unix
- Timing floors: resolveIdentity + WS bridge resolve_user get 5ms floor
- Mobile: TLS verification gated behind insecure-dev feature flag
- Proto: from_bytes default limit tightened from 64 MiB to 8 MiB

Correctness fixes:
- fetch_wait: register waiter before fetch to close TOCTOU window
- MeshEnvelope: exclude hop_count from signature (forwarding no longer
  invalidates sender signature)
- BroadcastChannel: encrypt returns Result instead of panicking
- transcript: rename verify_transcript_chain → validate_transcript_structure
- group.rs: extract shared process_incoming() for receive_message variants
- auth_ops: remove spurious RegistrationRequest deserialization
- MeshStore.seen: bounded to 100K with FIFO eviction

Quality fixes:
- FFI error classification: typed downcast instead of string matching
- Plugin HookVTable: SAFETY documentation for unsafe Send+Sync
- clippy::unwrap_used: warn → deny workspace-wide
- Various .unwrap_or("") → proper error returns

Review report: docs/REVIEW-2026-03-04.md
152 tests passing (72 core + 35 server + 14 E2E + 1 doctest + 30 P2P)
2026-03-04 07:52:12 +01:00
..

quicproquo Playbooks

YAML-based scripted command sequences for the qpq client.

Running a playbook

# Build with playbook support
cargo build -p quicproquo-client --features playbook

# Run a playbook
cargo run -p quicproquo-client --features playbook -- \
  run playbooks/smoke-test.yaml \
  --server 127.0.0.1:7000 \
  --username alice --password hunter2 \
  --danger-accept-invalid-certs

# Override variables
cargo run -p quicproquo-client --features playbook -- \
  run playbooks/smoke-test.yaml \
  -V recipient=charlie -V server=remote.example.com:7000

YAML format

name: "My playbook"
description: "Optional description"
variables:
  key: "value"              # available as $key in args
steps:
  - command: dm              # any slash command name (without /)
    args:
      username: "$key"       # variable substitution
  - command: send
    args:
      text: "Hello!"
  - command: assert
    condition: message_count # connected, logged_in, in_conversation, message_count, member_count
    op: gte                  # eq, ne, gt, lt, gte, lte (or ==, !=, >, <, >=, <=)
    value: 1

Variable substitution

  • $varname — replaced with the variable value
  • ${VAR:-default} — replaced with VAR, falling back to env var, then default
  • Variables from --var KEY=VALUE override playbook defaults
  • _server and _username are auto-injected

Step options

Field Type Description
command string Command name (required)
args map Command arguments
condition string For assert steps
op string Comparison operator for asserts
value any Expected value for asserts
capture string Store output in this variable
on_error string fail (default), skip, continue
loop object Repeat with {var, from, to}

Loop syntax

  - command: send
    args:
      text: "Message $i"
    loop:
      var: i
      from: 1
      to: 10

Available commands

All REPL slash commands (without the /): help, quit, whoami, list, switch, dm, create-group, invite, remove, leave, join, members, group-info, rename, history, verify, update-key, typing, react, edit, delete, send-file, download, delete-account, disappear, privacy, verify-fs, rotate-all-keys, devices, register-device, revoke-device, mesh-peers, mesh-send, mesh-broadcast, mesh-subscribe, mesh-route, mesh-identity, mesh-store.

Plus lifecycle commands: send (send a chat message), wait (pause), assert (check condition), set-var (set variable).

Programmatic Rust API

use quicproquo_client::{PlaybookRunner, Command, CommandRegistry, CommandResult};

// From YAML file
let mut runner = PlaybookRunner::from_file(Path::new("playbook.yaml"))?;
runner.set_var("recipient", "bob");
let report = runner.run(&mut session, &client).await;
println!("{report}");

// Single command execution
let cmd = Command::Dm { username: "alice".into() };
let result = CommandRegistry::execute(&cmd, &mut session, &client).await;
assert!(result.success);

Example playbooks

File Description
health-check.yaml Verify server connectivity
smoke-test.yaml Quick E2E: DM + send + history
register-and-dm.yaml DM exchange between two users
group-lifecycle.yaml Create/invite/send/leave group
stress-send.yaml Send 100 messages in a loop