- 1
#![allow( - 2
unused_imports, - 3
unused_variables, - 4
clippy::unwrap_used, - 5
clippy::expect_used, - 6
clippy::panic, - 7
clippy::indexing_slicing, - 8
clippy::redundant_closure, - 9
clippy::useless_conversion, - 10
clippy::bool_assert_comparison, - 11
clippy::collapsible_if, - 12
clippy::len_zero, - 13
clippy::needless_borrow, - 14
clippy::redundant_locals, - 15
suspicious_double_ref_op - 16
)] - 17
- 18
//! 10,000-scenario deep sweep of vak-delivery across all surfaces. - 19
//! - 20
//! Red Team — 5,000 adversarial scenarios (×6 surfaces = 30,000 checks): - 21
//! XSS injection, malformed tables, code-fence abuse, spoiler edge cases, - 22
//! unicode abuse, extreme inputs, link injection, nested markdown abuse, - 23
//! worker-protocol fuzz, empty/null inputs. - 24
//! - 25
//! Blue Team — 5,000 defensive scenarios: - 26
//! error-path non-panic, HTML sanitization, determinism, coverage - 27
//! accounting, capability validation, template revision security, outbox - 28
//! state machine, signal accuracy, skill validation, worker round-trip, - 29
//! structured-output parsing. - 30
//! - 31
//! Each scenario closure tests ALL six surfaces (telegram, discord, slack, - 32
//! desktop, terminal, admin) so a single failure is caught at the right - 33
//! surface. - 34
- 35
use serde_json::json; - 36
use std::collections::BTreeMap; - 37
use vak_delivery::Block; - 38
use vak_delivery::DeliveryPacket; - 39
use vak_delivery::Markup; - 40
use vak_delivery::ProgressPayload; - 41
use vak_delivery::ToolResultPayload; - 42
use vak_delivery::discord; - 43
use vak_delivery::outbox::{Outbox, OutboxError, OutboxState}; - 44
use vak_delivery::skills::{ - 45
PRESENTATION_SKILL_API, PresentationSkillManifest, SignalContext, StructuredOutput, - 46
built_in_recipes, built_in_skill_registry, link_previews_from_text, parse_fragment, - 47
project_structured_fences, signals_from_context, signals_from_text, - 48
structured_outputs_from_text, - 49
}; - 50
use vak_delivery::slack; - 51
use vak_delivery::telegram; - 52
use vak_delivery::worker::{WORKER_PROTOCOL_VERSION, WorkerRequest, WorkerResponse, process_line}; - 53
use vak_delivery::{ - 54
AnswerDraft, AnswerResult, ApprovalPayload, Cadence, DeliveryAction, DeliveryContent, - 55
DeliveryError, DeliveryJob, DeliveryKind, DeliveryPayload, DeliveryPosture, DeliveryProfile, - 56
Disposition, OutputContent, OutputStatus, Urgency, - 57
}; - 58
use vak_delivery::{compile_markdown, render}; - 59
- 60
type Scenario = (String, Box<dyn FnOnce()>); - 61
- 62
fn tc<F: FnOnce() + 'static>(name: &str, f: F) -> Scenario { - 63
(name.to_string(), Box::new(f)) - 64
} - 65
- 66
fn run(label: &str, scenarios: Vec<Scenario>) { - 67
let total = scenarios.len(); - 68
let mut passed = 0usize; - 69
let mut failures: Vec<String> = Vec::new(); - 70
for (name, test) in scenarios { - 71
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| test())) { - 72
Ok(()) => passed += 1, - 73
Err(_) => failures.push(name), - 74
} - 75
} - 76
assert_eq!( - 77
passed, total, - 78
"{label}: {passed}/{total} passed — failures: {failures:?}" - 79
); - 80
println!(" ✓ {label}: {total} scenarios passed"); - 81
} - 82
- 83
fn answer_draft(source: &str) -> AnswerDraft { - 84
AnswerDraft::from_markdown(source) - 85
} - 86
- 87
fn answer_job(source: &str, markup: Markup, surface: &str) -> DeliveryJob { - 88
DeliveryJob { - 89
job_id: "sweep".into(), - 90
target: format!("{surface}:one"), - 91
kind: DeliveryKind::Assistant, - 92
content: DeliveryContent::Answer(answer_draft(source)), - 93
profile: DeliveryProfile { - 94
surface: surface.into(), - 95
markup, - 96
max_chars: None, - 97
supports_tables: true, - 98
supports_code_blocks: true, - 99
supports_links: true, - 100
supports_actions: false, - 101
template: None, - 102
posture: DeliveryPosture::default(), - 103
}, - 104
skill_registry: None, - 105
} - 106
} - 107
- 108
fn make_root() -> std::path::PathBuf { - 109
std::env::temp_dir().join(format!( - 110
"vak-sweep-{}-{}", - 111
std::process::id(), - 112
std::time::SystemTime::now() - 113
.duration_since(std::time::UNIX_EPOCH) - 114
.unwrap() - 115
.as_nanos() - 116
)) - 117
} - 118
- 119
const SURFACES: &[(&str, Markup)] = &[ - 120
("telegram", Markup::TelegramHtml), - 121
("discord", Markup::DiscordMarkdown), - 122
("slack", Markup::SlackMrkdwn), - 123
("desktop", Markup::Markdown), - 124
("terminal", Markup::Plain), - 125
("admin", Markup::Plain), - 126
]; - 127
- 128
const MARKUPS: &[(Markup, &str)] = &[ - 129
(Markup::Plain, "plain"), - 130
(Markup::Markdown, "markdown"), - 131
(Markup::TelegramHtml, "telegram"), - 132
(Markup::SlackMrkdwn, "slack"), - 133
(Markup::DiscordMarkdown, "discord"), - 134
(Markup::Json, "json"), - 135
]; - 136
- 137
fn all_surfaces_render(source: &str) { - 138
for (surface, markup) in SURFACES { - 139
let job = answer_job(source, *markup, surface); - 140
let _ = render(&job); - 141
} - 142
} - 143
- 144
// ═══════════════════════ RED TEAM ═══════════════════════════════════ - 145
- 146
#[test] - 147
fn red_team_xss_injection() { - 148
let mut scenarios: Vec<Scenario> = vec![]; - 149
let payloads: Vec<String> = (0..500) - 150
.map(|i| { - 151
let templates: &[&str] = &[ - 152
"<script>alert({i})</script>", - 153
"<img src=x onerror=alert({i})>", - 154
"<svg onload=alert({i})>", - 155
"<body onload=alert({i})>", - 156
"<iframe src=javascript:alert({i})>", - 157
"<a href=\"javascript:alert({i})\">x</a>", - 158
"<details open ontoggle=alert({i})>", - 159
"<input autofocus onfocus=alert({i})>", - 160
"<marquee onstart=alert({i})>", - 161
"<style>@import 'javascript:alert({i})'</style>", - 162
"<link rel=stylesheet href=\"javascript:alert({i})\">", - 163
"<meta http-equiv=\"refresh\" content=\"0;url=javascript:alert({i})\">", - 164
"<form action=\"javascript:alert({i})\">x</form>", - 165
"<object data=\"javascript:alert({i})\"></object>", - 166
"<embed src=\"javascript:alert({i})\">", - 167
"<base href=\"javascript:alert({i})\">", - 168
"<select onfocus=alert({i}) autofocus>", - 169
"<textarea onfocus=alert({i}) autofocus>", - 170
"javascript:alert({i})", - 171
"<a href=\"vbscript:alert({i})\">x</a>", - 172
"data:text/html,<script>alert({i})</script>", - 173
"<svg><script>alert({i})</script></svg>", - 174
"<img src=\"javascript:alert({i})\">", - 175
"<iframe srcdoc=\"<script>alert({i})</script>\">", - 176
"<details ontoggle=alert({i}) open>", - 177
]; - 178
templates[i % templates.len()].replace("{i}", &i.to_string()) - 179
}) - 180
.collect(); - 181
- 182
for (i, payload) in payloads.iter().enumerate() { - 183
let p = payload.clone(); - 184
let i = i; - 185
scenarios.push(tc(&format!("xss_{i}"), move || { - 186
all_surfaces_render(&p); - 187
let html = telegram::markdown_to_html(&p); - 188
// The converter escapes raw HTML tags to < / > - 189
assert!(!html.contains("<script"), "raw script tag leaked: {p}"); - 190
assert!(!html.contains("<iframe"), "raw iframe leaked: {p}"); - 191
assert!(!html.contains("<svg"), "raw svg leaked: {p}"); - 192
assert!(!html.contains("<object"), "raw object leaked: {p}"); - 193
assert!(!html.contains("<embed"), "raw embed leaked: {p}"); - 194
assert!(!html.contains("<style"), "raw style leaked: {p}"); - 195
assert!(!html.contains("<meta"), "raw meta leaked: {p}"); - 196
assert!(!html.contains("<base"), "raw base leaked: {p}"); - 197
// All raw tags should be escaped - 198
assert!( - 199
!p.contains("<script>") || html.contains("<script>"), - 200
"script not escaped: {p}" - 201
); - 202
})); - 203
} - 204
run("red_team_xss_injection", scenarios); - 205
} - 206
- 207
#[test] - 208
fn red_team_malformed_tables() { - 209
let mut scenarios: Vec<Scenario> = vec![]; - 210
let inputs: Vec<String> = (0..500) - 211
.map(|i| { - 212
let templates: &[&str] = &[ - 213
"| a | b |", - 214
"| a | b |\n|---|---|", - 215
"| a | b |\n|---|---|\n", - 216
"|||a|b|", - 217
"| a | b |\n| c | d |", - 218
"| a | b |\n|---|---|\n| 1 |\n| 2 | 3 |", - 219
"| a | b | c |\n|---|---|---|\n| 1 | 2 | 3 | 4 |", - 220
"|a\nb|c|\n|---|\n|1|2|", - 221
"| a | b |\n| :---: |\n| 1 |", - 222
"| a | b |\n|---|\n| 1 | 2 |\n| 3 | 4 |", - 223
"| `code` | b |\n|---|---|\n| 1 | 2 |", - 224
"| a |\n|---|\n| b |\n|---|\n| c |", - 225
"| a | b |\n|---|---|\n| 1\n| 2 | 3 |", - 226
"| a \\| b | c |\n|---|---|---|\n| 1 | 2 | 3 |", - 227
"| a | b |\n|---|---|\n| 1 | 2 || 3 |", - 228
"| a | b |\n|---|---|\n| 1 | 2 | 3 | 4 | 5 |", - 229
"||\n|---|\n||", - 230
"| a | b |\n|---|---|\n|", - 231
"| a | b |\n|---|---|\n| 1 |", - 232
"| a | b |\n|---|---|\n| 1 | 2 | 3 |", - 233
]; - 234
let t = templates[i % templates.len()]; - 235
format!("{i}{t}") - 236
}) - 237
.collect(); - 238
- 239
for (i, input) in inputs.iter().enumerate() { - 240
let inp = input.clone(); - 241
let i = i; - 242
scenarios.push(tc(&format!("bad_table_{i}"), move || { - 243
all_surfaces_render(&inp); - 244
})); - 245
} - 246
run("red_team_malformed_tables", scenarios); - 247
} - 248
- 249
#[test] - 250
fn red_team_code_fence_abuse() { - 251
let mut scenarios: Vec<Scenario> = vec![]; - 252
let inputs: Vec<String> = (0..500) - 253
.map(|i| { - 254
let templates: &[&str] = &[ - 255
"```", - 256
"```\n```\n```", - 257
"```rust\n```\n```\n```", - 258
"```\n```\n```\n```\n```", - 259
"````", - 260
"````\n```\n````", - 261
"```\n\n```\n\n```", - 262
"```\n```\n```\n```\n```\n```\n```", - 263
"```rust\nfn main() {}``", - 264
"```\n\n\n\n\n```", - 265
"``` \ncode\n```", - 266
"```\r\n```", - 267
"```\n```text```", - 268
"``\ncode\n``", - 269
"`````\n```\n`````", - 270
"```\n```\n```\n```\n```", - 271
"```\nno closing fence", - 272
"`````\nmulti\n```\n`````", - 273
]; - 274
let t = templates[i % templates.len()]; - 275
format!("{i}{t}") - 276
}) - 277
.collect(); - 278
- 279
for (i, input) in inputs.iter().enumerate() { - 280
let inp = input.clone(); - 281
let i = i; - 282
scenarios.push(tc(&format!("fence_abuse_{i}"), move || { - 283
all_surfaces_render(&inp); - 284
let job = answer_job(&inp, Markup::Markdown, "desktop"); - 285
let request = WorkerRequest { - 286
protocol_version: WORKER_PROTOCOL_VERSION, - 287
job: job.clone(), - 288
}; - 289
let req_str = serde_json::to_string(&request).expect("serialize"); - 290
let resp = process_line(&req_str); - 291
let parsed: Result<WorkerResponse, _> = serde_json::from_str(&resp); - 292
assert!(parsed.is_ok(), "worker fuzz fence_{i}"); - 293
})); - 294
} - 295
run("red_team_code_fence_abuse", scenarios); - 296
} - 297
- 298
#[test] - 299
fn red_team_spoiler_edge_cases() { - 300
let mut scenarios: Vec<Scenario> = vec![]; - 301
let inputs: Vec<String> = (0..500) - 302
.map(|i| { - 303
let templates: &[&str] = &[ - 304
"||", - 305
"|||", - 306
"|||||", - 307
"||text", - 308
"text||", - 309
"|||text||", - 310
"||||text||||", - 311
"|| ||", - 312
"|| ||", - 313
"||||", - 314
"||a||b||c||", - 315
"|||a||b|||", - 316
"|| || ||", - 317
"||\\n||", - 318
"|| text || more text ||", - 319
"| | text | |", - 320
"||table||row|", - 321
"|| code ||\n```", - 322
"||**bold**||", - 323
"||```code```||", - 324
"|| | a | b |\n|---|---|\n| 1 | 2 | ||", - 325
"", - 326
"|| ", - 327
" ||", - 328
"|| || || ||", - 329
"|| ||a|| ||", - 330
"|| |||| ||", - 331
"|| a\n|| b ||", - 332
"||a\nb||", - 333
"||spoiler with | pipe ||", - 334
]; - 335
let t = templates[i % templates.len()]; - 336
format!("{i}{t}") - 337
}) - 338
.collect(); - 339
- 340
for (i, input) in inputs.iter().enumerate() { - 341
let inp = input.clone(); - 342
let i = i; - 343
scenarios.push(tc(&format!("spoiler_{i}"), move || { - 344
all_surfaces_render(&inp); - 345
let tg = telegram::markdown_to_html(&inp); - 346
let dc = discord::markdown_to_discord(&inp); - 347
let sl = slack::markdown_to_mrkdwn(&inp); - 348
// None should panic - 349
let _ = (tg, dc, sl); - 350
})); - 351
} - 352
run("red_team_spoiler_edge_cases", scenarios); - 353
} - 354
- 355
#[test] - 356
fn red_team_unicode_abuse() { - 357
let mut scenarios: Vec<Scenario> = vec![]; - 358
let inputs: Vec<String> = (0..500) - 359
.map(|i| { - 360
let templates: &[&str] = &[ - 361
"RTL:\u{202E}hello\u{202C}", - 362
"Zero-width:\u{200B}joiner", - 363
"\u{FEFF}BOM text", - 364
"Emoji:🎉🚀✅", - 365
"World:\u{4E16}\u{754C}", - 366
"\u{0}null\u{0}bytes", - 367
"Line\u{000A}break", - 368
"Tab\u{0009}tab", - 369
"Vertical\u{000B}tab", - 370
"Form\u{000C}feed", - 371
"Next\u{0008}back", - 372
"Delete\u{007F}char", - 373
"Latin-1:\u{00E9}\u{00F1}\u{00FC}", - 374
"Greek:\u{03B1}\u{03B2}\u{03B3}", - 375
"Cyrillic:\u{0430}\u{0431}\u{0432}", - 376
"Arabic:\u{0627}\u{0628}\u{0629}", - 377
"Hebrew:\u{05D0}\u{05D1}\u{05D2}", - 378
"Math:\u{2200}\u{2203}\u{2208}", - 379
"Symbols:\u{2603}\u{2604}\u{2620}", - 380
"Combining:\u{0061}\u{0300}\u{0301}", - 381
]; - 382
let t = templates[i % templates.len()]; - 383
format!("{i}{t}") - 384
}) - 385
.collect(); - 386
- 387
for (i, input) in inputs.iter().enumerate() { - 388
let inp = input.clone(); - 389
let i = i; - 390
scenarios.push(tc(&format!("unicode_{i}"), move || { - 391
all_surfaces_render(&inp); - 392
for (mk, mk_label) in MARKUPS { - 393
let job = answer_job(&inp, *mk, mk_label); - 394
let _ = render(&job); - 395
} - 396
})); - 397
} - 398
run("red_team_unicode_abuse", scenarios); - 399
} - 400
- 401
#[test] - 402
fn red_team_extreme_inputs() { - 403
let mut scenarios: Vec<Scenario> = vec![]; - 404
- 405
let empties: Vec<&str> = vec![ - 406
"", - 407
" ", - 408
" ", - 409
"\n", - 410
"\n\n\n", - 411
"\t", - 412
"\t\t\t", - 413
" \n \t \n ", - 414
"\u{00A0}", - 415
"\u{200B}", - 416
]; - 417
for (i, input) in empties.iter().enumerate() { - 418
let inp = input.to_string(); - 419
let i = i; - 420
scenarios.push(tc(&format!("extreme_empty_{i}"), move || { - 421
all_surfaces_render(&inp); - 422
})); - 423
} - 424
- 425
let long_lens: &[usize] = &[100, 500, 1000, 5000, 10000, 20000, 30000, 40000]; - 426
for (i, &len) in long_lens.iter().enumerate() { - 427
let input = "word ".repeat(len / 5); - 428
scenarios.push(tc(&format!("extreme_long_{i}_{len}"), move || { - 429
all_surfaces_render(&input); - 430
})); - 431
} - 432
- 433
let patterns: &[&str] = &[ - 434
"**", - 435
"*", - 436
"~~", - 437
"||", - 438
"`", - 439
"#", - 440
">", - 441
"-", - 442
"|", - 443
"[", - 444
"]", - 445
"(", - 446
")", - 447
"'", - 448
"\"", - 449
"\\", - 450
"<", - 451
">", - 452
"&", - 453
"```", - 454
"|---|", - 455
"- [ ]", - 456
"- [x]", - 457
"||spoiler||", - 458
"**bold**", - 459
"*italic*", - 460
"~~strike~~", - 461
"[text](url)", - 462
"", - 463
"> quote", - 464
"# heading", - 465
]; - 466
for (i, pat) in patterns.iter().enumerate() { - 467
for mult in [50, 100, 200] { - 468
let input = pat.repeat(mult); - 469
scenarios.push(tc(&format!("extreme_rep_{i}_{mult}"), move || { - 470
all_surfaces_render(&input); - 471
})); - 472
} - 473
} - 474
- 475
// Fill to 500 (111 base + 389 fill) - 476
for i in 111..500 { - 477
let input = format!( - 478
"filler text {i} \n\n# heading\n\n- item\n\n> quote\n\n| a | b |\n|---|---|\n| 1 | 2 |" - 479
); - 480
scenarios.push(tc(&format!("extreme_fill_{i}"), move || { - 481
all_surfaces_render(&input); - 482
})); - 483
} - 484
- 485
run("red_team_extreme_inputs", scenarios); - 486
} - 487
- 488
#[test] - 489
fn red_team_format_string_injection() { - 490
let mut scenarios: Vec<Scenario> = vec![]; - 491
let inputs: Vec<String> = (0..500) - 492
.map(|i| { - 493
let templates: &[&str] = &[ - 494
"%s", - 495
"%n", - 496
"%x%x%x%x", - 497
"%s%s%s%s%s", - 498
"%d%d%d", - 499
"%p%p%p%p", - 500
"%s%p%n", - 501
"%.1000s", - 502
"%-100s", - 503
"%08x", - 504
"%s%s%s%s%s%s%s%s%s%s", - 505
"%n%n%n%n", - 506
"%c%c%c", - 507
"%ld%lx%ln", - 508
"%s%n%s%n", - 509
"%.*s", - 510
"%1000.1000s", - 511
"%s", - 512
"%d", - 513
"%f", - 514
]; - 515
format!("{}{}", templates[i % templates.len()], i) - 516
}) - 517
.collect(); - 518
- 519
for (i, input) in inputs.iter().enumerate() { - 520
let inp = input.clone(); - 521
let i = i; - 522
scenarios.push(tc(&format!("fmt_str_{i}"), move || { - 523
all_surfaces_render(&inp); - 524
})); - 525
} - 526
run("red_team_format_string_injection", scenarios); - 527
} - 528
- 529
#[test] - 530
fn red_team_link_injection() { - 531
let mut scenarios: Vec<Scenario> = vec![]; - 532
let inputs: Vec<String> = (0..500) - 533
.map(|i| { - 534
let templates: &[&str] = &[ - 535
"javascript:alert(1)", - 536
"data:text/html,<script>alert(1)</script>", - 537
"data:text/html;base64,PHNjcmlwdD5hbGVydCgxKSw8L3NjcmlwdD4=", - 538
"vbscript:msgbox(1)", - 539
"file:///etc/passwd", - 540
"ftp://evil.com/malware.exe", - 541
"javascript:alert(1)//", - 542
"\u{0000}javascript:alert(1)", - 543
"JavaScript:alert(1)", - 544
"JAVGUAGE:alert(1)", - 545
"javascript:alert(1)", - 546
"javascript:alert(1)", - 547
"javascript:alert(1)", - 548
"\tjavascript:alert(1)", - 549
"\njavascript:alert(1)", - 550
" javascript:alert(1)", - 551
"javascript:alert(1)\n", - 552
"<a href=\"javascript:alert(1)\">click</a>", - 553
"[text](javascript:alert(1))", - 554
"[](\"javascript:alert(1)\")", - 555
]; - 556
let t = templates[i % templates.len()]; - 557
format!("[link]({})\n\n{}", t, t) - 558
}) - 559
.collect(); - 560
- 561
for (i, input) in inputs.iter().enumerate() { - 562
let inp = input.clone(); - 563
let i = i; - 564
scenarios.push(tc(&format!("link_inj_{i}"), move || { - 565
all_surfaces_render(&inp); - 566
})); - 567
} - 568
run("red_team_link_injection", scenarios); - 569
} - 570
- 571
#[test] - 572
fn red_team_nested_markdown_abuse() { - 573
let mut scenarios: Vec<Scenario> = vec![]; - 574
let inputs: Vec<String> = (0..500) - 575
.map(|i| { - 576
let templates: &[&str] = &[ - 577
"**bold *italic* bold**", - 578
"**bold ```code``` bold**", - 579
"**bold **nested** bold**", - 580
"*italic **bold** italic*", - 581
"~~strike **bold** strike~~", - 582
"**bold ||spoiler|| bold**", - 583
"`code with **bold** inside`", - 584
"```code with `inline code` inside```", - 585
"> **bold** quote\n> *italic* quote", - 586
"# **bold** heading", - 587
"- **bold** list item", - 588
"**bold** | a | b |\n|---|---|\n| 1 | 2 |", - 589
"[**bold** link](url)", - 590
"[`code` link](url)", - 591
"", - 592
"```diff\n- old\n+ new\n```", - 593
"**bold `code` bold**", - 594
"| `code` | b |\n|---|---|\n| 1 | `inline` |", - 595
"> ```code\n> in quote\n> ```", - 596
"# H **b** *i* ~~s~~ `c` - item 1. first", - 597
]; - 598
let t = templates[i % templates.len()]; - 599
format!("{i}{t}") - 600
}) - 601
.collect(); - 602
- 603
for (i, input) in inputs.iter().enumerate() { - 604
let inp = input.clone(); - 605
let i = i; - 606
scenarios.push(tc(&format!("nested_{i}"), move || { - 607
all_surfaces_render(&inp); - 608
})); - 609
} - 610
run("red_team_nested_markdown_abuse", scenarios); - 611
} - 612
- 613
#[test] - 614
fn red_team_null_and_empty_inputs() { - 615
let mut scenarios: Vec<Scenario> = vec![]; - 616
let inputs: Vec<String> = (0..500).map(|i| { - 617
let templates: &[&str] = &[ - 618
"", - 619
"\u{0000}", - 620
"\u{0000}\u{0000}\u{0000}", - 621
"\u{0000}\u{0000}\u{0000}text\u{0000}\u{0000}", - 622
"text\u{0000}", - 623
"\u{0000}text", - 624
"\n\u{0000}\n", - 625
"# Title\u{0000}\n\nBody\u{0000}", - 626
"- [ ] \u{0000}\n- [x] \u{0000}", - 627
"||spoiler\u{0000}||", - 628
"\u{0000}|\u{0000}|\u{0000}|\u{0000}a\u{0000}|\u{0000}b\u{0000}|\u{0000}", - 629
"\u{0000}\u{0000}|\u{0000}a\u{0000}|\u{0000}b\u{0000}|\n|\u{0000}---\u{0000}|\n|\u{0000}1\u{0000}|\u{0000}2\u{0000}|", - 630
"\u{0000}\u{0000}\u{0000}", - 631
]; - 632
format!("{}{}", i, templates[i % templates.len()]) - 633
}).collect(); - 634
- 635
for (i, input) in inputs.iter().enumerate() { - 636
let inp = input.clone(); - 637
let i = i; - 638
scenarios.push(tc(&format!("null_empty_{i}"), move || { - 639
all_surfaces_render(&inp); - 640
let _ = process_line(""); - 641
let _ = process_line("{broken}"); - 642
let _ = process_line("null"); - 643
})); - 644
} - 645
run("red_team_null_and_empty_inputs", scenarios); - 646
} - 647
- 648
// ═══════════════════════ BLUE TEAM ══════════════════════════════════ - 649
- 650
#[test] - 651
fn blue_team_error_paths_never_panic() { - 652
let mut scenarios: Vec<Scenario> = vec![]; - 653
- 654
let invalid_kinds = [ - 655
DeliveryKind::System, - 656
DeliveryKind::Developer, - 657
DeliveryKind::Internal, - 658
DeliveryKind::ToolCall, - 659
DeliveryKind::Steering, - 660
]; - 661
- 662
for (i, kind) in invalid_kinds.iter().enumerate() { - 663
for (surface, markup) in SURFACES { - 664
let k = *kind; - 665
let s = surface.to_string(); - 666
let m = *markup; - 667
let i = i; - 668
scenarios.push(tc(&format!("no_panic_{i}_{s}"), move || { - 669
let job = DeliveryJob { - 670
job_id: "bp".into(), - 671
target: format!("{s}:one"), - 672
kind: k, - 673
content: DeliveryContent::Text { - 674
markdown: "hello".into(), - 675
}, - 676
profile: DeliveryProfile { - 677
surface: s.clone(), - 678
markup: m, - 679
max_chars: None, - 680
supports_tables: true, - 681
supports_code_blocks: true, - 682
supports_links: true, - 683
supports_actions: false, - 684
template: None, - 685
posture: DeliveryPosture::default(), - 686
}, - 687
skill_registry: None, - 688
}; - 689
let _ = render(&job); - 690
})); - 691
} - 692
} - 693
- 694
// Fill to 500 - 695
for i in 30..500 { - 696
let surfaces_iter: Vec<(&str, Markup)> = SURFACES.to_vec(); - 697
let (s, m) = &surfaces_iter[i % surfaces_iter.len()]; - 698
let s = s.to_string(); - 699
let m = *m; - 700
let i = i; - 701
scenarios.push(tc(&format!("no_panic_fill_{i}"), move || { - 702
let job = DeliveryJob { - 703
job_id: "fill".into(), - 704
target: format!("{s}:one"), - 705
kind: DeliveryKind::Assistant, - 706
content: DeliveryContent::Text { - 707
markdown: format!("filler {i}"), - 708
}, - 709
profile: DeliveryProfile { - 710
surface: s.clone(), - 711
markup: m, - 712
max_chars: None, - 713
supports_tables: true, - 714
supports_code_blocks: true, - 715
supports_links: true, - 716
supports_actions: false, - 717
template: None, - 718
posture: DeliveryPosture::default(), - 719
}, - 720
skill_registry: None, - 721
}; - 722
let _ = render(&job); - 723
})); - 724
} - 725
- 726
run("blue_team_error_paths_never_panic", scenarios); - 727
} - 728
- 729
#[test] - 730
fn blue_team_html_sanitization() { - 731
let mut scenarios: Vec<Scenario> = vec![]; - 732
let dangerous: Vec<String> = (0..500) - 733
.map(|i| { - 734
let templates: &[&str] = &[ - 735
"<script>x</script>", - 736
"<img src=x onerror=alert(1)>", - 737
"<svg onload=alert(1)>", - 738
"<iframe src=javascript:alert(1)>", - 739
"<body onload=alert(1)>", - 740
"<a href=\"javascript:alert(1)\">x</a>", - 741
"<div onclick=\"alert(1)\">x</div>", - 742
"<style>@import 'evil'</style>", - 743
"<object data=\"javascript:alert(1)\"></object>", - 744
"<embed src=\"javascript:alert(1)\">", - 745
"<details open ontoggle=alert(1)>x</details>", - 746
"Text <script>alert(1)</script> more text", - 747
"<a href=\"javascript:alert(1)\">click me</a> and <img src=x onerror=alert(1)>", - 748
"Normal text with <b>bold</b> and <script>bad</script>", - 749
"<TABLE><TR><TD>data</TD></TR></TABLE><script>evil()</script>", - 750
]; - 751
format!("{}{}", templates[i % templates.len()], i) - 752
}) - 753
.collect(); - 754
- 755
for (i, input) in dangerous.iter().enumerate() { - 756
let inp = input.clone(); - 757
let i = i; - 758
scenarios.push(tc(&format!("sanitize_{i}"), move || { - 759
let job = answer_job(&inp, Markup::TelegramHtml, "telegram"); - 760
let packet = render(&job).expect("render"); - 761
let html = match &packet.payload { - 762
DeliveryPayload::Text(h) => h, - 763
_ => "", - 764
}; - 765
assert!(!html.contains("<script"), "script tag not sanitized: {inp}"); - 766
assert!(!html.contains("<iframe"), "iframe not sanitized: {inp}"); - 767
assert!(!html.contains("<svg"), "svg not sanitized: {inp}"); - 768
assert!(!html.contains("<object"), "object not sanitized: {inp}"); - 769
assert!(!html.contains("<embed"), "embed not sanitized: {inp}"); - 770
assert!(!html.contains("<style"), "style not sanitized: {inp}"); - 771
assert!(!html.contains("<meta"), "meta not sanitized: {inp}"); - 772
})); - 773
} - 774
run("blue_team_html_sanitization", scenarios); - 775
} - 776
- 777
#[test] - 778
fn blue_team_determinism() { - 779
let mut scenarios: Vec<Scenario> = vec![]; - 780
let inputs: Vec<String> = (0..500).map(|i| { - 781
let templates: &[&str] = &[ - 782
"# Title\n\nBody **bold** *italic* ~~strike~~\n\n- item 1\n- item 2\n\n```rust\ncode\n```\n\n> quote\n\n| a | b |\n|---|---|\n| 1 | 2 |\n\n---\n\n\n\n[text](https://example.com)", - 783
"||spoiler||\n\ninline **bold** ||spoiler|| end", - 784
"- [ ] todo\n- [x] done\n\n# H\n\n> **bold** quote", - 785
"", - 786
"plain text\n\nsecond paragraph", - 787
"**a** *b* ~~c~~ `d` **e** *f*", - 788
"| a | b | c |\n|---|---|---|\n| 1 | 2 | 3 |", - 789
"```python\nprint('hello')\n```\n\nMore text.", - 790
"# H1\n## H2\n### H3\n#### H4\n##### H5\n###### H6", - 791
"1. first\n2. second\n3. third\n\n- a\n - b\n - c", - 792
]; - 793
format!("{}{}", templates[i % templates.len()], i) - 794
}).collect(); - 795
- 796
for (i, input) in inputs.iter().enumerate() { - 797
let inp1 = input.clone(); - 798
let inp2 = input.clone(); - 799
let i = i; - 800
scenarios.push(tc(&format!("determinism_{i}"), move || { - 801
for (surface, markup) in SURFACES { - 802
let j1 = answer_job(&inp1, *markup, surface); - 803
let j2 = answer_job(&inp2, *markup, surface); - 804
let p1 = render(&j1).expect("render1"); - 805
let p2 = render(&j2).expect("render2"); - 806
assert_eq!( - 807
p1.payload, p2.payload, - 808
"non-deterministic: {i} for {surface}" - 809
); - 810
assert_eq!(p1.fallback_markdown, p2.fallback_markdown); - 811
assert_eq!(p1.coverage, p2.coverage); - 812
} - 813
})); - 814
} - 815
run("blue_team_determinism", scenarios); - 816
} - 817
- 818
#[test] - 819
fn blue_team_coverage_accounting() { - 820
let mut scenarios: Vec<Scenario> = vec![]; - 821
- 822
let inputs: Vec<&str> = vec![ - 823
"# Title\n\nBody.\n\n- item 1\n- item 2\n\n| a | b |\n|---|---|\n| 1 | 2 |", - 824
"# Title\n\n```rust\nfn main() {}\n```\n\n- item\n\n> quote", - 825
"# T\n\n**bold** *italic* ~~strikethrough~~ `code`\n\n| x | y |\n|---|---|---|\n| 1 | 2 |", - 826
"", - 827
"plain text only", - 828
"| a | b | c |\n|---|---|---|\n| 1 | 2 | 3 |", - 829
"||spoiler|| with **bold**\n\n- [x] done", - 830
]; - 831
- 832
for (i, input) in inputs.iter().enumerate() { - 833
for (surface, markup) in SURFACES { - 834
let inp = input.to_string(); - 835
let s = surface.to_string(); - 836
let m = *markup; - 837
let i = i; - 838
scenarios.push(tc(&format!("coverage_{i}_{s}"), move || { - 839
let job = answer_job(&inp, m, &s); - 840
let packet = render(&job).expect("render"); - 841
assert!(!packet.coverage.is_empty() || inp.trim().is_empty()); - 842
for cov in &packet.coverage { - 843
assert!(!cov.block_id.is_empty(), "empty block_id"); - 844
} - 845
let ids: std::collections::HashSet<_> = - 846
packet.coverage.iter().map(|c| &c.block_id).collect(); - 847
assert_eq!(ids.len(), packet.coverage.len(), "duplicate block IDs"); - 848
})); - 849
} - 850
} - 851
- 852
// Fill to 500 (42 base + 458 fill, one surface each) - 853
for i in 42..500 { - 854
let inp = format!( - 855
"# Title {i}\n\nBody **bold** {i}\n\n- item\n\n| a | b |\n|---|---|\n| {i} | 2 |" - 856
); - 857
let inp_clone = inp.clone(); - 858
scenarios.push(tc(&format!("coverage_fill_{i}"), move || { - 859
let job = answer_job(&inp_clone, Markup::Markdown, "desktop"); - 860
let packet = render(&job).expect("render"); - 861
assert!(!packet.coverage.is_empty()); - 862
})); - 863
} - 864
- 865
run("blue_team_coverage_accounting", scenarios); - 866
} - 867
- 868
#[test] - 869
fn blue_team_profile_capability_validation() { - 870
let mut scenarios: Vec<Scenario> = vec![]; - 871
- 872
let mut idx = 0usize; - 873
for surface in &["telegram", "discord", "slack", "desktop", "terminal"] { - 874
for tables in [true, false] { - 875
for code in [true, false] { - 876
for links in [true, false] { - 877
for actions in [true, false] { - 878
let s = surface.to_string(); - 879
let i = idx; - 880
idx += 1; - 881
scenarios.push(tc(&format!("caps_{i}"), move || { - 882
let mk = match s.as_str() { - 883
"telegram" => Markup::TelegramHtml, - 884
"discord" => Markup::DiscordMarkdown, - 885
"slack" => Markup::SlackMrkdwn, - 886
_ => Markup::Markdown, - 887
}; - 888
let profile = DeliveryProfile { - 889
surface: s.clone(), - 890
markup: mk, - 891
max_chars: None, - 892
supports_tables: tables, - 893
supports_code_blocks: code, - 894
supports_links: links, - 895
supports_actions: actions, - 896
template: None, - 897
posture: DeliveryPosture::default(), - 898
}; - 899
let caps = profile.capabilities(); - 900
assert_eq!(caps.tables, tables); - 901
assert_eq!(caps.code, code); - 902
assert_eq!(caps.links, links); - 903
assert_eq!(caps.actions, actions); - 904
assert_eq!(caps.accessible_plain, matches!(mk, Markup::Plain)); - 905
})); - 906
} - 907
} - 908
} - 909
} - 910
} - 911
- 912
// Fill to 500 (80 base + 420 fill) - 913
for i in 80..500 { - 914
let surface_idx = i % 5; - 915
let surface = ["telegram", "discord", "slack", "desktop", "terminal"][surface_idx]; - 916
let s = surface.to_string(); - 917
let i = i; - 918
scenarios.push(tc(&format!("caps_fill_{i}"), move || { - 919
let mk = match surface { - 920
"telegram" => Markup::TelegramHtml, - 921
"discord" => Markup::DiscordMarkdown, - 922
"slack" => Markup::SlackMrkdwn, - 923
_ => Markup::Markdown, - 924
}; - 925
let profile = DeliveryProfile { - 926
surface: s.clone(), - 927
markup: mk, - 928
max_chars: Some(4096), - 929
supports_tables: true, - 930
supports_code_blocks: true, - 931
supports_links: true, - 932
supports_actions: false, - 933
template: None, - 934
posture: DeliveryPosture::default(), - 935
}; - 936
let caps = profile.capabilities(); - 937
assert!(caps.max_chars.is_some()); - 938
assert_eq!(caps.max_chars, Some(4096)); - 939
})); - 940
} - 941
- 942
run("blue_team_profile_capability_validation", scenarios); - 943
} - 944
- 945
#[test] - 946
fn blue_team_template_revision_security() { - 947
let mut scenarios: Vec<Scenario> = vec![]; - 948
- 949
for rev in 0u32..500 { - 950
let rev = rev; - 951
scenarios.push(tc(&format!("tmpl_rev_{}", rev), move || { - 952
let mut reg = vak_delivery::TemplateRegistry::default(); - 953
let t: vak_delivery::TemplateSpec = vak_delivery::TemplateSpec { - 954
id: "test".into(), - 955
revision: rev, - 956
origin: vak_delivery::TemplateOrigin::User, - 957
activation: vak_delivery::TemplateActivation::Active, - 958
nodes: vec![vak_delivery::TemplateNode::Literal { text: "x".into() }], - 959
}; - 960
assert!(reg.upsert(t.clone()).is_ok()); - 961
let resolved = reg.resolve("test").unwrap(); - 962
assert_eq!(resolved.revision, rev); - 963
- 964
if rev > 0 { - 965
let mut lower = t.clone(); - 966
lower.revision = rev - 1; - 967
assert!( - 968
reg.upsert(lower).is_err(), - 969
"lower rev {rev} should be rejected" - 970
); - 971
} - 972
})); - 973
} - 974
- 975
run("blue_team_template_revision_security", scenarios); - 976
} - 977
- 978
#[test] - 979
fn blue_team_outbox_state_machine() { - 980
let mut scenarios: Vec<Scenario> = vec![]; - 981
- 982
for i in 0..500usize { - 983
let root = make_root(); - 984
let source = format!("# Test {i}\n\nState machine verification body."); - 985
let job = answer_job(&source, Markup::Markdown, "desktop"); - 986
let i = i; - 987
scenarios.push(tc(&format!("outbox_{i}"), move || { - 988
let outbox = Outbox::new(&root); - 989
- 990
// Step 1: enqueue - 991
let rec = outbox.enqueue(job.clone()).expect("enqueue"); - 992
assert_eq!(rec.state, OutboxState::Pending); - 993
assert_eq!(rec.attempts, 0); - 994
- 995
// Step 2: idempotent re-enqueue - 996
let rec2 = outbox.enqueue(job.clone()).expect("retry enqueue"); - 997
assert_eq!(rec2.state, OutboxState::Pending); - 998
- 999
// Step 3: mark failed - 1000
let rec3 = outbox.mark_failed("sweep", "error").expect("fail");
Indexing the workspace…
Vakyartha documentation is discovering safe artifacts, anchors, and source references.