Spaces:
Sleeping
Sleeping
Builder117 commited on
Commit Β·
0d9cf35
1
Parent(s): ca1a04f
Redesign UI: Adversarial Guardrail pipeline dashboard (5 tabs)
Browse files- app.py +235 -328
- pipeline_status.json +24 -0
app.py
CHANGED
|
@@ -499,361 +499,268 @@ PII_EXAMPLES = [
|
|
| 499 |
["The quarterly earnings report shows 12% growth. No personal data here."],
|
| 500 |
]
|
| 501 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 502 |
CSS = """
|
| 503 |
/* ββ Reset & Base ββ */
|
| 504 |
-
body, .gradio-container { background: #
|
| 505 |
-
.gradio-container { max-width:
|
| 506 |
footer { display: none !important; }
|
| 507 |
|
| 508 |
-
/* ββ
|
| 509 |
-
.
|
| 510 |
-
|
| 511 |
-
|
| 512 |
-
|
| 513 |
-
|
| 514 |
-
|
| 515 |
-
|
| 516 |
-
|
| 517 |
-
|
| 518 |
-
|
| 519 |
-
.hero::after {
|
| 520 |
-
content: '';
|
| 521 |
-
position: absolute;
|
| 522 |
-
top: 0; right: 0;
|
| 523 |
-
width: 300px; height: 100%;
|
| 524 |
-
background: radial-gradient(ellipse at 80% 50%, rgba(99,102,241,0.08) 0%, transparent 70%);
|
| 525 |
-
pointer-events: none;
|
| 526 |
-
}
|
| 527 |
-
.hero h1 {
|
| 528 |
-
font-size: 2.4em !important;
|
| 529 |
-
font-weight: 800 !important;
|
| 530 |
-
background: linear-gradient(90deg, #4f46e5, #7c3aed, #db2777);
|
| 531 |
-
-webkit-background-clip: text !important;
|
| 532 |
-
-webkit-text-fill-color: transparent !important;
|
| 533 |
-
background-clip: text !important;
|
| 534 |
-
margin: 0 0 10px !important;
|
| 535 |
-
letter-spacing: -0.5px;
|
| 536 |
-
}
|
| 537 |
-
.hero p { color: #64748b !important; font-size: 1.05em; margin: 0; line-height: 1.6; }
|
| 538 |
-
|
| 539 |
-
/* ββ Stat chips ββ */
|
| 540 |
-
.stats-strip { display: flex; gap: 10px; margin: 18px 0 4px; flex-wrap: wrap; }
|
| 541 |
-
.stat-chip {
|
| 542 |
-
background: #f1f5f9;
|
| 543 |
-
border: 1px solid #e2e8f0;
|
| 544 |
-
border-radius: 20px;
|
| 545 |
-
padding: 5px 14px;
|
| 546 |
-
font-size: 0.82em;
|
| 547 |
-
color: #475569;
|
| 548 |
-
font-weight: 500;
|
| 549 |
-
}
|
| 550 |
-
.stat-chip b { color: #4f46e5; }
|
| 551 |
-
|
| 552 |
-
/* ββ Hero badge + threat badges ββ */
|
| 553 |
-
.hero-badge {
|
| 554 |
-
display: inline-block;
|
| 555 |
-
background: linear-gradient(90deg, #4f46e5, #7c3aed);
|
| 556 |
-
color: #fff;
|
| 557 |
-
font-size: 0.72em;
|
| 558 |
-
font-weight: 700;
|
| 559 |
-
letter-spacing: 1px;
|
| 560 |
-
text-transform: uppercase;
|
| 561 |
-
border-radius: 20px;
|
| 562 |
-
padding: 3px 12px;
|
| 563 |
-
margin-bottom: 14px;
|
| 564 |
-
}
|
| 565 |
-
.hero h1 { font-size: 2.6em !important; line-height: 1.15 !important; }
|
| 566 |
-
.hero-sub {
|
| 567 |
-
background: linear-gradient(90deg, #7c3aed, #db2777);
|
| 568 |
-
-webkit-background-clip: text;
|
| 569 |
-
-webkit-text-fill-color: transparent;
|
| 570 |
-
background-clip: text;
|
| 571 |
-
}
|
| 572 |
-
.hero-desc { color: #64748b !important; font-size: 1.0em !important; line-height: 1.65 !important; margin: 4px 0 0 !important; }
|
| 573 |
-
.threat-badges { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 18px; }
|
| 574 |
-
.tbadge { font-size: 0.82em; font-weight: 600; border-radius: 8px; padding: 5px 13px; border: 1.5px solid; }
|
| 575 |
-
.tbadge-inj { color: #ef4444; border-color: #fecaca; background: #fff1f1; }
|
| 576 |
-
.tbadge-jail { color: #ea580c; border-color: #fed7aa; background: #fff7ed; }
|
| 577 |
-
.tbadge-ins { color: #9333ea; border-color: #e9d5ff; background: #faf5ff; }
|
| 578 |
-
.tbadge-ind { color: #2563eb; border-color: #bfdbfe; background: #eff6ff; }
|
| 579 |
-
.tbadge-pii { color: #db2777; border-color: #fbcfe8; background: #fdf2f8; }
|
| 580 |
-
|
| 581 |
-
/* ββ Threat cards ββ */
|
| 582 |
-
.threat-card {
|
| 583 |
-
background: #ffffff;
|
| 584 |
-
border: 1px solid #e2e8f0;
|
| 585 |
-
border-radius: 14px;
|
| 586 |
-
padding: 16px 20px;
|
| 587 |
-
margin-bottom: 18px;
|
| 588 |
-
border-left: 4px solid;
|
| 589 |
-
box-shadow: 0 2px 8px rgba(0,0,0,0.04);
|
| 590 |
-
}
|
| 591 |
-
.tc-injection { border-left-color: #ef4444; }
|
| 592 |
-
.tc-jailbreak { border-left-color: #f97316; }
|
| 593 |
-
.tc-insecure { border-left-color: #a855f7; }
|
| 594 |
-
.tc-indirect { border-left-color: #3b82f6; }
|
| 595 |
-
.tc-scanall { border-left-color: #10b981; }
|
| 596 |
-
.tc-test { border-left-color: #f59e0b; }
|
| 597 |
-
.threat-card h3 { margin: 0 0 4px !important; font-size: 1.08em !important; color: #0f172a !important; font-weight: 700 !important; }
|
| 598 |
-
.threat-card p { margin: 0 !important; color: #64748b !important; font-size: 0.9em !important; line-height: 1.5; }
|
| 599 |
-
|
| 600 |
-
/* ββ Textbox input ββ */
|
| 601 |
-
textarea:not([readonly]) {
|
| 602 |
-
background: #ffffff !important;
|
| 603 |
-
border: 1.5px solid #e2e8f0 !important;
|
| 604 |
-
color: #1e293b !important;
|
| 605 |
-
font-family: 'JetBrains Mono', 'Fira Code', 'Courier New', monospace !important;
|
| 606 |
-
font-size: 0.91em !important;
|
| 607 |
-
border-radius: 10px !important;
|
| 608 |
-
}
|
| 609 |
-
textarea:not([readonly]):focus {
|
| 610 |
-
border-color: #6366f1 !important;
|
| 611 |
-
box-shadow: 0 0 0 3px rgba(99,102,241,0.12) !important;
|
| 612 |
-
}
|
| 613 |
-
|
| 614 |
-
/* ββ Verdict output ββ */
|
| 615 |
-
textarea[readonly] {
|
| 616 |
-
background: #f8fafc !important;
|
| 617 |
-
border: 1.5px solid #e2e8f0 !important;
|
| 618 |
-
color: #1e293b !important;
|
| 619 |
-
font-weight: 700 !important;
|
| 620 |
-
font-size: 1.02em !important;
|
| 621 |
-
border-radius: 10px !important;
|
| 622 |
}
|
| 623 |
-
|
| 624 |
-
/* ββ Labels ββ */
|
| 625 |
-
label span, .label-wrap span { color: #64748b !important; font-size: 0.84em !important; font-weight: 600 !important; letter-spacing: 0.3px; }
|
| 626 |
|
| 627 |
/* ββ Buttons ββ */
|
| 628 |
button.primary {
|
| 629 |
background: linear-gradient(135deg, #4f46e5, #7c3aed) !important;
|
| 630 |
-
border: none !important;
|
| 631 |
-
|
| 632 |
-
|
| 633 |
-
border-radius: 10px !important;
|
| 634 |
-
letter-spacing: 0.3px;
|
| 635 |
-
transition: all 0.2s;
|
| 636 |
-
box-shadow: 0 2px 8px rgba(79,70,229,0.25) !important;
|
| 637 |
-
}
|
| 638 |
-
button.primary:hover {
|
| 639 |
-
background: linear-gradient(135deg, #4338ca, #6d28d9) !important;
|
| 640 |
-
box-shadow: 0 4px 16px rgba(79,70,229,0.4) !important;
|
| 641 |
-
transform: translateY(-1px);
|
| 642 |
}
|
|
|
|
| 643 |
|
| 644 |
-
/* ββ
|
| 645 |
-
.
|
| 646 |
-
background: linear-gradient(135deg, #059669, #10b981) !important;
|
| 647 |
-
border: none !important;
|
| 648 |
-
color: #ffffff !important;
|
| 649 |
-
font-size: 1.05em !important;
|
| 650 |
-
font-weight: 700 !important;
|
| 651 |
-
letter-spacing: 0.5px;
|
| 652 |
-
border-radius: 12px !important;
|
| 653 |
-
box-shadow: 0 2px 12px rgba(16,185,129,0.3) !important;
|
| 654 |
-
}
|
| 655 |
-
.scan-btn button:hover {
|
| 656 |
-
background: linear-gradient(135deg, #047857, #059669) !important;
|
| 657 |
-
box-shadow: 0 4px 20px rgba(16,185,129,0.45) !important;
|
| 658 |
-
transform: translateY(-1px);
|
| 659 |
-
}
|
| 660 |
-
|
| 661 |
-
/* ββ Tabs ββ */
|
| 662 |
-
.tabs > .tab-nav { border-bottom: 2px solid #e2e8f0 !important; background: transparent !important; }
|
| 663 |
-
.tabs button { color: #94a3b8 !important; font-weight: 600 !important; border-radius: 8px 8px 0 0 !important; }
|
| 664 |
-
.tabs button.selected {
|
| 665 |
-
background: #f0f4ff !important;
|
| 666 |
-
border-bottom: 2px solid #6366f1 !important;
|
| 667 |
-
color: #4f46e5 !important;
|
| 668 |
-
}
|
| 669 |
-
.tabs button:hover { color: #6366f1 !important; background: #f8faff !important; }
|
| 670 |
-
|
| 671 |
-
/* ββ Label widget (probability bars) ββ */
|
| 672 |
-
.output-class { color: #4f46e5 !important; font-weight: 700 !important; }
|
| 673 |
.confidence-bar { background: linear-gradient(90deg, #4f46e5, #7c3aed) !important; }
|
| 674 |
-
|
| 675 |
-
/* ββ Footer ββ */
|
| 676 |
-
.footer-bar {
|
| 677 |
-
margin-top: 24px;
|
| 678 |
-
padding: 16px 22px;
|
| 679 |
-
background: #ffffff;
|
| 680 |
-
border: 1px solid #e2e8f0;
|
| 681 |
-
border-radius: 12px;
|
| 682 |
-
color: #64748b;
|
| 683 |
-
font-size: 0.84em;
|
| 684 |
-
display: flex; justify-content: space-between; flex-wrap: wrap; gap: 8px;
|
| 685 |
-
box-shadow: 0 2px 8px rgba(0,0,0,0.04);
|
| 686 |
-
}
|
| 687 |
-
.footer-bar a { color: #4f46e5 !important; text-decoration: none; font-weight: 500; }
|
| 688 |
-
.footer-bar a:hover { color: #7c3aed !important; text-decoration: underline; }
|
| 689 |
-
|
| 690 |
-
/* ββ Examples ββ */
|
| 691 |
-
.examples-header { color: #64748b !important; font-size: 0.85em !important; font-weight: 700 !important; }
|
| 692 |
-
.examples { border: 1px solid #e2e8f0 !important; border-radius: 12px !important; overflow: hidden !important; margin-top: 12px !important; }
|
| 693 |
-
.examples table { border: none !important; }
|
| 694 |
-
.examples td {
|
| 695 |
-
padding: 9px 14px !important;
|
| 696 |
-
font-size: 0.87em !important;
|
| 697 |
-
color: #334155 !important;
|
| 698 |
-
border-bottom: 1px solid #f1f5f9 !important;
|
| 699 |
-
cursor: pointer !important;
|
| 700 |
-
font-family: 'JetBrains Mono', monospace !important;
|
| 701 |
-
white-space: nowrap;
|
| 702 |
-
overflow: hidden;
|
| 703 |
-
text-overflow: ellipsis;
|
| 704 |
-
max-width: 520px;
|
| 705 |
-
}
|
| 706 |
-
.examples td:hover { background: #eef2ff !important; color: #4f46e5 !important; }
|
| 707 |
-
.examples thead { display: none !important; }
|
| 708 |
-
.examples tr:last-child td { border-bottom: none !important; }
|
| 709 |
"""
|
| 710 |
|
| 711 |
-
|
| 712 |
-
<div
|
| 713 |
-
<div
|
| 714 |
-
<h1
|
| 715 |
-
|
| 716 |
-
|
| 717 |
-
<
|
| 718 |
-
|
| 719 |
-
|
| 720 |
-
|
| 721 |
-
|
| 722 |
-
<span
|
|
|
|
|
|
|
|
|
|
| 723 |
</div>
|
| 724 |
</div>
|
| 725 |
"""
|
| 726 |
|
| 727 |
-
|
| 728 |
-
<div class="footer-bar">
|
| 729 |
-
<span>π΄ HIGH β₯ 90% β strong attack signal Β· π‘ MED β₯ 70% β likely attack Β· π LOW β₯ 50% β review recommended Β· π’ CLEAN < 50%</span>
|
| 730 |
-
<span>
|
| 731 |
-
<a href="https://huggingface.co/Builder117/distilbert-prompt-injection">injection</a> Β·
|
| 732 |
-
<a href="https://huggingface.co/Builder117/distilbert-jailbreak">jailbreak</a> Β·
|
| 733 |
-
<a href="https://huggingface.co/Builder117/distilbert-insecure-output">insecure-output</a> Β·
|
| 734 |
-
<a href="https://huggingface.co/Builder117/distilbert-indirect-injection">indirect</a>
|
| 735 |
-
</span>
|
| 736 |
-
</div>
|
| 737 |
-
"""
|
| 738 |
-
|
| 739 |
-
with gr.Blocks(title="LLM Threat Shield", css=CSS, theme=gr.themes.Soft()) as demo:
|
| 740 |
|
| 741 |
-
gr.HTML(
|
| 742 |
|
| 743 |
with gr.Tabs():
|
| 744 |
|
| 745 |
-
with gr.Tab("
|
| 746 |
-
|
| 747 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 748 |
with gr.Row():
|
| 749 |
-
with gr.Column(
|
| 750 |
-
|
| 751 |
-
|
| 752 |
-
|
| 753 |
-
|
| 754 |
-
|
| 755 |
-
|
| 756 |
-
|
| 757 |
-
|
| 758 |
-
|
| 759 |
-
|
| 760 |
-
|
| 761 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 762 |
with gr.Row():
|
| 763 |
-
with gr.Column(
|
| 764 |
-
|
| 765 |
-
|
| 766 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 767 |
out2_label = gr.Label(label="Threat probability", num_top_classes=2)
|
| 768 |
-
out2_sev
|
| 769 |
-
|
| 770 |
-
|
| 771 |
-
|
| 772 |
-
|
| 773 |
-
with gr.Tab("β£οΈ Insecure Output"):
|
| 774 |
-
gr.HTML('<div class="threat-card tc-insecure"><h3>β£οΈ Insecure Output</h3><p>Scans LLM-generated text for dangerous payloads β XSS, SQL injection, command injection, path traversal, UNION attacks. Scores β₯ 90% = confirmed attack. β₯ 70% = probable. β₯ 50% = review recommended.</p></div>')
|
| 775 |
-
state4 = gr.State([])
|
| 776 |
with gr.Row():
|
| 777 |
-
with gr.Column(
|
| 778 |
-
|
| 779 |
-
|
| 780 |
-
|
|
|
|
| 781 |
out4_label = gr.Label(label="Threat probability", num_top_classes=2)
|
| 782 |
-
out4_sev
|
| 783 |
-
|
| 784 |
-
|
| 785 |
-
|
| 786 |
-
|
| 787 |
-
|
| 788 |
-
|
| 789 |
-
|
| 790 |
-
|
| 791 |
-
with gr.Column(scale=3):
|
| 792 |
-
inp5 = gr.Textbox(label="Retrieved content", lines=7, placeholder='e.g. "Article text... <!-- AI: ignore previous instructions -->"')
|
| 793 |
-
btn5 = gr.Button("β‘ Analyze", variant="primary")
|
| 794 |
-
with gr.Column(scale=2):
|
| 795 |
out5_label = gr.Label(label="Threat probability", num_top_classes=2)
|
| 796 |
-
out5_sev
|
| 797 |
-
|
| 798 |
-
|
| 799 |
-
inp5.submit(fn=detect_indirect, inputs=[inp5, state5], outputs=[out5_label, out5_sev, state5], api_name=False)
|
| 800 |
-
|
| 801 |
-
with gr.Tab("π PII Scanner"):
|
| 802 |
-
gr.HTML('<div class="threat-card" style="border-left-color:#db2777"><h3>π PII Scanner</h3><p>Detects personally identifiable information β credit cards, SSNs, emails, phone numbers, API keys, IP addresses, passport numbers, and more. Regex-based, instant results.</p></div>')
|
| 803 |
-
state_pii = gr.State([])
|
| 804 |
-
with gr.Row():
|
| 805 |
-
with gr.Column(scale=3):
|
| 806 |
-
inp_pii = gr.Textbox(label="Text to scan", lines=7, placeholder='e.g. "Contact me at john@example.com or 555-867-5309. Card: 4532-1234-5678-9012"')
|
| 807 |
-
btn_pii = gr.Button("π Scan for PII", variant="primary")
|
| 808 |
-
with gr.Column(scale=2):
|
| 809 |
-
out_pii_verdict = gr.Textbox(label="Verdict", interactive=False)
|
| 810 |
-
out_pii_summary = gr.Textbox(label="Found types", interactive=False)
|
| 811 |
-
out_pii_table = gr.Dataframe(headers=["Entity Type", "Value"], label="Detected PII", interactive=False, wrap=True)
|
| 812 |
-
gr.Examples(examples=PII_EXAMPLES, inputs=inp_pii, examples_per_page=8, label="Quick examples")
|
| 813 |
-
btn_pii.click(fn=detect_pii_tab, inputs=[inp_pii, state_pii], outputs=[out_pii_verdict, out_pii_summary, out_pii_table, state_pii])
|
| 814 |
-
inp_pii.submit(fn=detect_pii_tab, inputs=[inp_pii, state_pii], outputs=[out_pii_verdict, out_pii_summary, out_pii_table, state_pii])
|
| 815 |
-
|
| 816 |
-
with gr.Tab("π Scan All"):
|
| 817 |
-
gr.HTML('<div class="threat-card tc-scanall"><h3>π Full Threat Scan</h3><p>Runs all 4 ML detectors + PII scanner in parallel. Single input β unified threat report across every OWASP LLM attack category.</p></div>')
|
| 818 |
-
state3 = gr.State([])
|
| 819 |
-
with gr.Row():
|
| 820 |
-
with gr.Column(scale=3):
|
| 821 |
-
inp3 = gr.Textbox(label="Input text", lines=7, placeholder="Paste any text β prompt, LLM output, retrieved document, user message...")
|
| 822 |
-
with gr.Row(elem_classes=["scan-btn"]):
|
| 823 |
-
btn3 = gr.Button("π‘οΈ SCAN ALL THREATS", variant="primary", size="lg")
|
| 824 |
-
with gr.Column(scale=2):
|
| 825 |
-
out3_overall = gr.Textbox(label="Overall verdict", interactive=False, lines=2)
|
| 826 |
-
out3_report = gr.Textbox(label="Per-detector breakdown", interactive=False, lines=7)
|
| 827 |
-
gr.Examples(examples=SCAN_ALL_EXAMPLES, inputs=inp3, examples_per_page=8, label="Quick examples")
|
| 828 |
-
btn3.click(fn=scan_all, inputs=[inp3, state3], outputs=[out3_overall, out3_report, state3])
|
| 829 |
-
inp3.submit(fn=scan_all, inputs=[inp3, state3], outputs=[out3_overall, out3_report, state3])
|
| 830 |
-
|
| 831 |
-
with gr.Tabs():
|
| 832 |
-
|
| 833 |
-
with gr.Tab("π Model Health"):
|
| 834 |
-
gr.HTML('<div class="threat-card" style="border-left-color:#6366f1"><h3>π Model Health Check</h3><p>Runs 10 held-out examples per model (5 attack + 5 clean) and reports F1, precision, recall. Threshold: F1 β₯ 0.80. Does not use training data β independent quality gate.</p></div>')
|
| 835 |
-
btn_health = gr.Button("βΆ Run Health Check", variant="primary")
|
| 836 |
-
out_health_summary = gr.Textbox(label="Summary", interactive=False)
|
| 837 |
-
out_health_table = gr.Dataframe(
|
| 838 |
-
headers=["Model", "Status", "F1", "Precision", "Recall", "Accuracy"],
|
| 839 |
-
label="Benchmark results",
|
| 840 |
-
interactive=False,
|
| 841 |
-
wrap=True,
|
| 842 |
-
)
|
| 843 |
-
btn_health.click(fn=run_model_health, inputs=[], outputs=[out_health_table, out_health_summary])
|
| 844 |
-
|
| 845 |
-
with gr.Tab("π§ͺ Adversarial Tests"):
|
| 846 |
-
gr.HTML('<div class="threat-card tc-test"><h3>π§ͺ Adversarial Test Suite</h3><p>Runs 9 obfuscation bypass tests across injection and jailbreak models β fullwidth unicode, zero-width chars, leet substitution, mixed case, suffix attacks, and clean baselines. β
PASS = model correctly detected or ignored the input. β FAIL = bypass succeeded or false positive.</p></div>')
|
| 847 |
-
btn_test = gr.Button("βΆ Run Adversarial Tests", variant="primary")
|
| 848 |
-
out_test_summary = gr.Textbox(label="Summary", interactive=False)
|
| 849 |
-
out_test_table = gr.Dataframe(
|
| 850 |
-
headers=["Status", "Test Name", "Score", "Result", "Expected"],
|
| 851 |
-
label="Test results",
|
| 852 |
-
interactive=False,
|
| 853 |
-
wrap=True,
|
| 854 |
-
)
|
| 855 |
-
btn_test.click(fn=run_adversarial_tests, inputs=[], outputs=[out_test_table, out_test_summary])
|
| 856 |
-
|
| 857 |
-
gr.HTML(FOOTER_MD)
|
| 858 |
|
| 859 |
demo.launch()
|
|
|
|
| 499 |
["The quarterly earnings report shows 12% growth. No personal data here."],
|
| 500 |
]
|
| 501 |
|
| 502 |
+
import json
|
| 503 |
+
from pathlib import Path
|
| 504 |
+
|
| 505 |
+
STATUS_FILE = Path(__file__).parent / "pipeline_status.json"
|
| 506 |
+
|
| 507 |
+
def load_status():
|
| 508 |
+
if STATUS_FILE.exists():
|
| 509 |
+
return json.loads(STATUS_FILE.read_text())
|
| 510 |
+
return {"round": 0, "status": "no data", "red_team": {"families_tried": []},
|
| 511 |
+
"blue_team": {"weakness_scores": {}, "retrain_priority": []},
|
| 512 |
+
"orchestrator": {"action": "none", "severity": "none", "confidence": None,
|
| 513 |
+
"argo_workflow": "none", "reason": "No data yet."}}
|
| 514 |
+
|
| 515 |
+
def render_dashboard():
|
| 516 |
+
s = load_status()
|
| 517 |
+
rnd = s.get("rnd", s.get("round", 0))
|
| 518 |
+
ts = s.get("timestamp", "β")
|
| 519 |
+
status = s.get("status", "unknown")
|
| 520 |
+
orch = s.get("orchestrator", {})
|
| 521 |
+
blue = s.get("blue_team", {})
|
| 522 |
+
red = s.get("red_team", {})
|
| 523 |
+
|
| 524 |
+
sev_color = {"high": "#ef4444", "medium": "#f97316", "low": "#10b981", "none": "#94a3b8"}.get(
|
| 525 |
+
orch.get("severity", "none"), "#94a3b8")
|
| 526 |
+
action_badge = orch.get("action", "none").upper()
|
| 527 |
+
argo = orch.get("argo_workflow", "β")
|
| 528 |
+
conf = orch.get("confidence")
|
| 529 |
+
conf_str = f"{conf:.0%}" if conf is not None else "β"
|
| 530 |
+
|
| 531 |
+
weakness = blue.get("weakness_scores", {})
|
| 532 |
+
def bar(v):
|
| 533 |
+
if v is None: return "β¬ n/a"
|
| 534 |
+
pct = int(v * 100)
|
| 535 |
+
filled = round(pct / 10)
|
| 536 |
+
color = "π₯" if pct >= 60 else "π§" if pct >= 40 else "π©"
|
| 537 |
+
return color * filled + "β¬" * (10 - filled) + f" {pct}%"
|
| 538 |
+
|
| 539 |
+
families = red.get("families_tried", [])
|
| 540 |
+
fam_rows = "\n".join(
|
| 541 |
+
f" β’ {f['name']}: {f['samples']} samples, {int(f['evasion_pct']*100)}% evasion"
|
| 542 |
+
for f in families
|
| 543 |
+
) or " (no attacks run yet)"
|
| 544 |
+
|
| 545 |
+
retrain = ", ".join(blue.get("retrain_priority", [])) or "none"
|
| 546 |
+
|
| 547 |
+
html = f"""
|
| 548 |
+
<div style="font-family:'Inter','Segoe UI',sans-serif;padding:8px 0">
|
| 549 |
+
<div style="display:flex;gap:12px;flex-wrap:wrap;margin-bottom:20px">
|
| 550 |
+
<div style="flex:1;min-width:140px;background:#fff;border:1px solid #e2e8f0;border-radius:12px;padding:16px 20px">
|
| 551 |
+
<div style="font-size:.75em;color:#94a3b8;font-weight:700;text-transform:uppercase;letter-spacing:.5px">Round</div>
|
| 552 |
+
<div style="font-size:2.2em;font-weight:800;color:#4f46e5;line-height:1.1">{rnd}</div>
|
| 553 |
+
</div>
|
| 554 |
+
<div style="flex:1;min-width:140px;background:#fff;border:1px solid #e2e8f0;border-radius:12px;padding:16px 20px">
|
| 555 |
+
<div style="font-size:.75em;color:#94a3b8;font-weight:700;text-transform:uppercase;letter-spacing:.5px">Decision</div>
|
| 556 |
+
<div style="font-size:1.3em;font-weight:800;color:{sev_color};line-height:1.2">{action_badge}</div>
|
| 557 |
+
</div>
|
| 558 |
+
<div style="flex:1;min-width:140px;background:#fff;border:1px solid #e2e8f0;border-radius:12px;padding:16px 20px">
|
| 559 |
+
<div style="font-size:.75em;color:#94a3b8;font-weight:700;text-transform:uppercase;letter-spacing:.5px">Severity</div>
|
| 560 |
+
<div style="font-size:1.3em;font-weight:800;color:{sev_color}">{orch.get("severity","β").upper()}</div>
|
| 561 |
+
</div>
|
| 562 |
+
<div style="flex:1;min-width:140px;background:#fff;border:1px solid #e2e8f0;border-radius:12px;padding:16px 20px">
|
| 563 |
+
<div style="font-size:.75em;color:#94a3b8;font-weight:700;text-transform:uppercase;letter-spacing:.5px">Confidence</div>
|
| 564 |
+
<div style="font-size:1.3em;font-weight:800;color:#0f172a">{conf_str}</div>
|
| 565 |
+
</div>
|
| 566 |
+
<div style="flex:1;min-width:180px;background:#fff;border:1px solid #e2e8f0;border-radius:12px;padding:16px 20px">
|
| 567 |
+
<div style="font-size:.75em;color:#94a3b8;font-weight:700;text-transform:uppercase;letter-spacing:.5px">Argo DAG</div>
|
| 568 |
+
<div style="font-size:1.05em;font-weight:700;color:#0f172a">{argo}</div>
|
| 569 |
+
</div>
|
| 570 |
+
</div>
|
| 571 |
+
|
| 572 |
+
<div style="background:#fff;border:1px solid #e2e8f0;border-radius:12px;padding:18px 22px;margin-bottom:16px;border-left:4px solid {sev_color}">
|
| 573 |
+
<div style="font-size:.8em;color:#94a3b8;font-weight:700;text-transform:uppercase;margin-bottom:6px">Orchestrator Reasoning</div>
|
| 574 |
+
<div style="color:#1e293b;font-size:.95em;line-height:1.6">{orch.get("reason","β")}</div>
|
| 575 |
+
</div>
|
| 576 |
+
|
| 577 |
+
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px">
|
| 578 |
+
<div style="background:#fff;border:1px solid #e2e8f0;border-radius:12px;padding:16px 20px">
|
| 579 |
+
<div style="font-size:.8em;color:#94a3b8;font-weight:700;text-transform:uppercase;margin-bottom:10px">Detector Weakness (evasion rate)</div>
|
| 580 |
+
{"".join(f'<div style="margin-bottom:6px"><span style="font-size:.82em;color:#475569;display:inline-block;width:130px">{k}</span><span style="font-family:monospace;font-size:.85em">{bar(v)}</span></div>' for k, v in weakness.items())}
|
| 581 |
+
</div>
|
| 582 |
+
<div style="background:#fff;border:1px solid #e2e8f0;border-radius:12px;padding:16px 20px">
|
| 583 |
+
<div style="font-size:.8em;color:#94a3b8;font-weight:700;text-transform:uppercase;margin-bottom:10px">Red Team Attacks</div>
|
| 584 |
+
<pre style="margin:0;font-size:.83em;color:#334155;white-space:pre-wrap">{fam_rows}</pre>
|
| 585 |
+
<div style="margin-top:12px;font-size:.8em;color:#94a3b8;font-weight:700;text-transform:uppercase">Retrain Priority</div>
|
| 586 |
+
<div style="font-size:.9em;color:#ef4444;font-weight:600;margin-top:4px">{retrain}</div>
|
| 587 |
+
</div>
|
| 588 |
+
</div>
|
| 589 |
+
<div style="margin-top:10px;color:#94a3b8;font-size:.78em;text-align:right">Last updated: {ts}</div>
|
| 590 |
+
</div>"""
|
| 591 |
+
return html
|
| 592 |
+
|
| 593 |
CSS = """
|
| 594 |
/* ββ Reset & Base ββ */
|
| 595 |
+
body, .gradio-container { background: #0f172a !important; color: #e2e8f0 !important; }
|
| 596 |
+
.gradio-container { max-width: 1160px !important; margin: 0 auto; font-family: 'Inter', 'Segoe UI', sans-serif !important; }
|
| 597 |
footer { display: none !important; }
|
| 598 |
|
| 599 |
+
/* ββ Tabs ββ */
|
| 600 |
+
.tabs > .tab-nav { border-bottom: 2px solid #1e293b !important; background: transparent !important; }
|
| 601 |
+
.tabs button { color: #64748b !important; font-weight: 600 !important; border-radius: 8px 8px 0 0 !important; }
|
| 602 |
+
.tabs button.selected { background: #1e293b !important; border-bottom: 2px solid #6366f1 !important; color: #a5b4fc !important; }
|
| 603 |
+
.tabs button:hover { color: #6366f1 !important; background: #1e293b !important; }
|
| 604 |
+
|
| 605 |
+
/* ββ Textbox ββ */
|
| 606 |
+
textarea, input[type=text] {
|
| 607 |
+
background: #1e293b !important; border: 1.5px solid #334155 !important;
|
| 608 |
+
color: #e2e8f0 !important; border-radius: 10px !important;
|
| 609 |
+
font-family: 'JetBrains Mono', monospace !important; font-size: 0.91em !important;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 610 |
}
|
| 611 |
+
textarea:focus, input[type=text]:focus { border-color: #6366f1 !important; box-shadow: 0 0 0 3px rgba(99,102,241,0.2) !important; }
|
|
|
|
|
|
|
| 612 |
|
| 613 |
/* ββ Buttons ββ */
|
| 614 |
button.primary {
|
| 615 |
background: linear-gradient(135deg, #4f46e5, #7c3aed) !important;
|
| 616 |
+
border: none !important; color: #fff !important; font-weight: 600 !important;
|
| 617 |
+
border-radius: 10px !important; transition: all 0.2s;
|
| 618 |
+
box-shadow: 0 2px 8px rgba(79,70,229,0.35) !important;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 619 |
}
|
| 620 |
+
button.primary:hover { background: linear-gradient(135deg, #4338ca, #6d28d9) !important; transform: translateY(-1px); }
|
| 621 |
|
| 622 |
+
/* ββ Label widget ββ */
|
| 623 |
+
.output-class { color: #a5b4fc !important; font-weight: 700 !important; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 624 |
.confidence-bar { background: linear-gradient(90deg, #4f46e5, #7c3aed) !important; }
|
| 625 |
+
label span, .label-wrap span { color: #94a3b8 !important; font-size: 0.84em !important; font-weight: 600 !important; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 626 |
"""
|
| 627 |
|
| 628 |
+
HEADER_HTML = """
|
| 629 |
+
<div style="background:linear-gradient(135deg,#1e293b 0%,#0f172a 100%);border:1px solid #334155;border-radius:16px;padding:28px 36px 22px;margin-bottom:16px">
|
| 630 |
+
<div style="font-size:.72em;font-weight:700;letter-spacing:1.5px;text-transform:uppercase;color:#6366f1;margin-bottom:10px">π‘οΈ Adversarial Guardrail</div>
|
| 631 |
+
<h1 style="margin:0 0 8px;font-size:2em;font-weight:800;background:linear-gradient(90deg,#a5b4fc,#c4b5fd,#f472b6);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text">
|
| 632 |
+
AI Agent Orchestration Pipeline
|
| 633 |
+
</h1>
|
| 634 |
+
<p style="margin:0;color:#94a3b8;font-size:.95em;line-height:1.6">
|
| 635 |
+
3 LangChain ReAct agents (Red Team Β· Blue Team Β· Orchestrator) drive adversarial attacks against 4 DistilBERT detectors
|
| 636 |
+
through GitHub Actions β Jenkins (GKE) β Argo Workflows.
|
| 637 |
+
</p>
|
| 638 |
+
<div style="display:flex;gap:10px;margin-top:14px;flex-wrap:wrap">
|
| 639 |
+
<span style="background:#312e81;border:1px solid #4f46e5;border-radius:20px;padding:4px 12px;font-size:.78em;color:#a5b4fc;font-weight:600">π€ 3 Agents</span>
|
| 640 |
+
<span style="background:#1e1b4b;border:1px solid #6366f1;border-radius:20px;padding:4px 12px;font-size:.78em;color:#c4b5fd;font-weight:600">π¬ 4 DistilBERT Detectors</span>
|
| 641 |
+
<span style="background:#0c1a2e;border:1px solid #2563eb;border-radius:20px;padding:4px 12px;font-size:.78em;color:#93c5fd;font-weight:600">βοΈ GHA β Jenkins β Argo</span>
|
| 642 |
+
<span style="background:#1a1a1a;border:1px solid #6b7280;border-radius:20px;padding:4px 12px;font-size:.78em;color:#d1d5db;font-weight:600">OWASP LLM Top 10</span>
|
| 643 |
</div>
|
| 644 |
</div>
|
| 645 |
"""
|
| 646 |
|
| 647 |
+
with gr.Blocks(title="Adversarial Guardrail", css=CSS, theme=gr.themes.Soft()) as demo:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 648 |
|
| 649 |
+
gr.HTML(HEADER_HTML)
|
| 650 |
|
| 651 |
with gr.Tabs():
|
| 652 |
|
| 653 |
+
with gr.Tab("π― Pipeline Dashboard"):
|
| 654 |
+
dash_html = gr.HTML(render_dashboard())
|
| 655 |
+
refresh_btn = gr.Button("π Refresh", variant="primary", size="sm")
|
| 656 |
+
refresh_btn.click(fn=render_dashboard, inputs=[], outputs=[dash_html])
|
| 657 |
+
|
| 658 |
+
with gr.Tab("π΄ Red Team"):
|
| 659 |
+
gr.HTML("""<div style="background:#1e293b;border:1px solid #334155;border-left:4px solid #ef4444;border-radius:12px;padding:16px 20px;margin-bottom:16px">
|
| 660 |
+
<h3 style="margin:0 0 4px;color:#fca5a5;font-size:1.05em">π΄ Red Team Agent</h3>
|
| 661 |
+
<p style="margin:0;color:#94a3b8;font-size:.88em">Generates adversarial prompts against the 4 DistilBERT detectors using LangChain ReAct loops.
|
| 662 |
+
Reads attack memory from prior rounds, consults Blue Team feedback, refines samples.</p>
|
| 663 |
+
</div>""")
|
| 664 |
with gr.Row():
|
| 665 |
+
with gr.Column():
|
| 666 |
+
gr.Markdown("### Last Round Results")
|
| 667 |
+
red_status = gr.HTML(lambda: (lambda s: f"""
|
| 668 |
+
<div style="background:#0f172a;border:1px solid #334155;border-radius:10px;padding:14px 18px;font-family:monospace;font-size:.85em;color:#e2e8f0">
|
| 669 |
+
{"".join(f'<div style="margin-bottom:6px"><span style="color:#f472b6;font-weight:700">{f["name"]}</span> Β· {f["samples"]} samples Β· <span style="color:{"#ef4444" if f["evasion_pct"]>=0.5 else "#10b981"};font-weight:700">{int(f["evasion_pct"]*100)}% evasion</span></div>' for f in s["red_team"]["families_tried"]) or '<span style="color:#64748b">No attacks run yet</span>'}
|
| 670 |
+
</div>""" )(load_status()))
|
| 671 |
+
|
| 672 |
+
with gr.Tab("π΅ Blue Team"):
|
| 673 |
+
gr.HTML("""<div style="background:#1e293b;border:1px solid #334155;border-left:4px solid #3b82f6;border-radius:12px;padding:16px 20px;margin-bottom:16px">
|
| 674 |
+
<h3 style="margin:0 0 4px;color:#93c5fd;font-size:1.05em">π΅ Blue Team Agent</h3>
|
| 675 |
+
<p style="margin:0;color:#94a3b8;font-size:.88em">Analyzes detector weaknesses, validates Red Team proposals, and recommends which models need retraining.
|
| 676 |
+
Reports gap scores per detector and per attack family.</p>
|
| 677 |
+
</div>""")
|
| 678 |
+
with gr.Row():
|
| 679 |
+
with gr.Column():
|
| 680 |
+
gr.Markdown("### Detector Weakness Scores")
|
| 681 |
+
blue_status = gr.HTML(lambda: (lambda s: f"""
|
| 682 |
+
<div style="background:#0f172a;border:1px solid #334155;border-radius:10px;padding:14px 18px">
|
| 683 |
+
{"".join(f'''<div style="margin-bottom:10px;display:flex;align-items:center;gap:12px">
|
| 684 |
+
<span style="font-size:.82em;color:#94a3b8;width:150px;font-family:monospace">{k}</span>
|
| 685 |
+
<div style="flex:1;background:#1e293b;border-radius:6px;height:10px;overflow:hidden">
|
| 686 |
+
<div style="width:{int((v or 0)*100)}%;height:100%;background:{"#ef4444" if (v or 0)>=0.6 else "#f97316" if (v or 0)>=0.4 else "#10b981"};border-radius:6px"></div>
|
| 687 |
+
</div>
|
| 688 |
+
<span style="font-size:.82em;font-weight:700;color:{"#ef4444" if (v or 0)>=0.6 else "#f97316" if (v or 0)>=0.4 else "#10b981"};width:40px;text-align:right">{f"{int((v or 0)*100)}%" if v is not None else "n/a"}</span>
|
| 689 |
+
</div>''' for k, v in s["blue_team"]["weakness_scores"].items())}
|
| 690 |
+
<div style="margin-top:12px;padding-top:12px;border-top:1px solid #1e293b">
|
| 691 |
+
<span style="font-size:.78em;color:#94a3b8;text-transform:uppercase;font-weight:700;letter-spacing:.5px">Retrain Priority</span>
|
| 692 |
+
<div style="margin-top:4px;color:#f472b6;font-weight:600;font-size:.9em">{", ".join(s["blue_team"]["retrain_priority"]) or "none"}</div>
|
| 693 |
+
</div>
|
| 694 |
+
</div>""" )(load_status()))
|
| 695 |
+
|
| 696 |
+
with gr.Tab("π€ Orchestrator"):
|
| 697 |
+
gr.HTML("""<div style="background:#1e293b;border:1px solid #334155;border-left:4px solid #a855f7;border-radius:12px;padding:16px 20px;margin-bottom:16px">
|
| 698 |
+
<h3 style="margin:0 0 4px;color:#d8b4fe;font-size:1.05em">π€ Pipeline Orchestrator Agent</h3>
|
| 699 |
+
<p style="margin:0;color:#94a3b8;font-size:.88em">Reads Red+Blue agent outputs, decides pipeline routing, selects Argo DAG variant, writes pipeline_decision.json.
|
| 700 |
+
Uses ReAct loop with confidence threshold before committing to a decision.</p>
|
| 701 |
+
</div>""")
|
| 702 |
+
orch_status = gr.HTML(lambda: (lambda s: f"""
|
| 703 |
+
<div style="background:#0f172a;border:1px solid #334155;border-radius:10px;padding:18px 22px;font-family:'Inter',sans-serif">
|
| 704 |
+
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:12px;margin-bottom:16px">
|
| 705 |
+
{"".join(f'<div style="background:#1e293b;border:1px solid #334155;border-radius:8px;padding:12px 16px"><div style="font-size:.72em;color:#64748b;text-transform:uppercase;font-weight:700;letter-spacing:.5px">{lbl}</div><div style="font-size:1.1em;font-weight:700;color:{col};margin-top:2px">{val}</div></div>' for lbl,col,val in [
|
| 706 |
+
("Action","#a5b4fc",s["orchestrator"]["action"].upper()),
|
| 707 |
+
("Severity","#f472b6",s["orchestrator"]["severity"].upper()),
|
| 708 |
+
("Confidence","#e2e8f0",f'{s["orchestrator"]["confidence"]:.0%}' if s["orchestrator"]["confidence"] else "β"),
|
| 709 |
+
("Argo DAG","#93c5fd",s["orchestrator"]["argo_workflow"]),
|
| 710 |
+
])}
|
| 711 |
+
</div>
|
| 712 |
+
<div style="background:#1e293b;border:1px solid #334155;border-radius:8px;padding:14px 18px">
|
| 713 |
+
<div style="font-size:.75em;color:#64748b;text-transform:uppercase;font-weight:700;margin-bottom:6px">Reasoning</div>
|
| 714 |
+
<div style="color:#cbd5e1;font-size:.92em;line-height:1.7">{s["orchestrator"]["reason"]}</div>
|
| 715 |
+
</div>
|
| 716 |
+
<div style="margin-top:8px;color:#475569;font-size:.75em;text-align:right">Round {s.get("round",0)} Β· {s.get("timestamp","β")}</div>
|
| 717 |
+
</div>""" )(load_status()))
|
| 718 |
+
|
| 719 |
+
with gr.Tab("π¬ Detector API"):
|
| 720 |
+
gr.HTML("""<div style="background:#1e293b;border:1px solid #334155;border-left:4px solid #10b981;border-radius:12px;padding:14px 20px;margin-bottom:16px">
|
| 721 |
+
<h3 style="margin:0 0 3px;color:#6ee7b7;font-size:1.0em">π¬ Detector API β Live Testing</h3>
|
| 722 |
+
<p style="margin:0;color:#94a3b8;font-size:.85em">Direct access to all 4 DistilBERT classifiers. These endpoints are also exposed as REST API via <code style="background:#0f172a;padding:2px 6px;border-radius:4px;color:#a5b4fc">/gradio_api/call/{fn_name}</code></p>
|
| 723 |
+
</div>""")
|
| 724 |
with gr.Row():
|
| 725 |
+
with gr.Column():
|
| 726 |
+
gr.Markdown("#### π Injection")
|
| 727 |
+
state1 = gr.State([])
|
| 728 |
+
inp1 = gr.Textbox(label="Input text", lines=4, placeholder="Enter text to check for prompt injection...")
|
| 729 |
+
btn1 = gr.Button("β‘ Check Injection", variant="primary", size="sm")
|
| 730 |
+
out1_label = gr.Label(label="Threat probability", num_top_classes=2)
|
| 731 |
+
out1_sev = gr.Textbox(label="Verdict", interactive=False)
|
| 732 |
+
btn1.click(fn=detect_injection, inputs=[inp1, state1], outputs=[out1_label, out1_sev, state1], api_name="detect_injection")
|
| 733 |
+
inp1.submit(fn=detect_injection, inputs=[inp1, state1], outputs=[out1_label, out1_sev, state1], api_name=False)
|
| 734 |
+
|
| 735 |
+
with gr.Column():
|
| 736 |
+
gr.Markdown("#### π Jailbreak")
|
| 737 |
+
state2 = gr.State([])
|
| 738 |
+
inp2 = gr.Textbox(label="Input text", lines=4, placeholder="Enter text to check for jailbreak attempts...")
|
| 739 |
+
btn2 = gr.Button("β‘ Check Jailbreak", variant="primary", size="sm")
|
| 740 |
out2_label = gr.Label(label="Threat probability", num_top_classes=2)
|
| 741 |
+
out2_sev = gr.Textbox(label="Verdict", interactive=False)
|
| 742 |
+
btn2.click(fn=detect_jailbreak, inputs=[inp2, state2], outputs=[out2_label, out2_sev, state2], api_name="detect_jailbreak")
|
| 743 |
+
inp2.submit(fn=detect_jailbreak, inputs=[inp2, state2], outputs=[out2_label, out2_sev, state2], api_name=False)
|
| 744 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
| 745 |
with gr.Row():
|
| 746 |
+
with gr.Column():
|
| 747 |
+
gr.Markdown("#### β£οΈ Insecure Output")
|
| 748 |
+
state4 = gr.State([])
|
| 749 |
+
inp4 = gr.Textbox(label="LLM output text", lines=4, placeholder="Paste LLM output to scan for dangerous payloads...")
|
| 750 |
+
btn4 = gr.Button("β‘ Check Output", variant="primary", size="sm")
|
| 751 |
out4_label = gr.Label(label="Threat probability", num_top_classes=2)
|
| 752 |
+
out4_sev = gr.Textbox(label="Verdict", interactive=False)
|
| 753 |
+
btn4.click(fn=detect_insecure, inputs=[inp4, state4], outputs=[out4_label, out4_sev, state4], api_name="detect_insecure")
|
| 754 |
+
inp4.submit(fn=detect_insecure, inputs=[inp4, state4], outputs=[out4_label, out4_sev, state4], api_name=False)
|
| 755 |
+
|
| 756 |
+
with gr.Column():
|
| 757 |
+
gr.Markdown("#### π΅οΈ Indirect Injection")
|
| 758 |
+
state5 = gr.State([])
|
| 759 |
+
inp5 = gr.Textbox(label="Retrieved content", lines=4, placeholder="Paste document/RAG content to scan for hidden instructions...")
|
| 760 |
+
btn5 = gr.Button("β‘ Check Indirect", variant="primary", size="sm")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 761 |
out5_label = gr.Label(label="Threat probability", num_top_classes=2)
|
| 762 |
+
out5_sev = gr.Textbox(label="Verdict", interactive=False)
|
| 763 |
+
btn5.click(fn=detect_indirect, inputs=[inp5, state5], outputs=[out5_label, out5_sev, state5], api_name="detect_indirect")
|
| 764 |
+
inp5.submit(fn=detect_indirect, inputs=[inp5, state5], outputs=[out5_label, out5_sev, state5], api_name=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 765 |
|
| 766 |
demo.launch()
|
pipeline_status.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"round": 0,
|
| 3 |
+
"timestamp": "2026-06-22T00:00:00Z",
|
| 4 |
+
"status": "awaiting_first_run",
|
| 5 |
+
"red_team": {
|
| 6 |
+
"families_tried": []
|
| 7 |
+
},
|
| 8 |
+
"blue_team": {
|
| 9 |
+
"weakness_scores": {
|
| 10 |
+
"injection": null,
|
| 11 |
+
"jailbreak": null,
|
| 12 |
+
"insecure_output": null,
|
| 13 |
+
"indirect_injection": null
|
| 14 |
+
},
|
| 15 |
+
"retrain_priority": []
|
| 16 |
+
},
|
| 17 |
+
"orchestrator": {
|
| 18 |
+
"action": "none",
|
| 19 |
+
"severity": "none",
|
| 20 |
+
"confidence": null,
|
| 21 |
+
"argo_workflow": "none",
|
| 22 |
+
"reason": "Pipeline has not run yet. Awaiting first attack round."
|
| 23 |
+
}
|
| 24 |
+
}
|