appworld2026
24 TopicsF5 AppWorld 2026 Registration - early bird pricing.
Join us March 10–12 at Fontainebleau Las Vegas and Meet the Moment at F5 AppWorld 2026. Connect with your community and explore how the F5 Application Delivery and Security Platform gives you control without compromise. Over three days you will experience inspiring keynotes, learn new approaches in breakouts, deepen your skills in hands-on labs, and connect with peers, F5 leaders, and partners. Register early and save: Conference pass: $499 Conference pass + F5 Academy labs: $899 Team pass: 4 for the price of 3 Take advantage of early bird pricing and register today! We look forward to seeing you in Vegas. Your DevCentral Team. --- ** Early bird pricing expires Feb 13, 2026.1.5KViews5likes4CommentsF5 AppWorld 2026 Las Vegas - iRules Contest Winners!
Grand Prize Winner - Injeyan_Kostas Rule: LLM Prompt Injection Detection & Enforcement Summary This iRule addresses the emerging threat of prompt injection attacks on AI APIs by implementing a real-time detection engine within the F5 BIG-IP platform. This iRule operates entirely within the data plane, requiring no backend changes, and enforces a configurable security policy to prevent malicious content from reaching language models. By utilizing a multi-layer scoring system and managing patterns externally, it allows security teams to fine-tune detection and adjust thresholds dynamically. 2nd Place - Marcio_G & svs Rule: AI Token Limit Enforcement Summary This iRule addresses the critical challenge of resource control in on-premise AI inference services by enforcing token budgets per user and role. By leveraging BIG-IP LTM iRules, it validates JWTs to extract user and role information, applying role-based token limits before requests reach the inference service. This ensures that organizations can manage and protect their AI infrastructure from uncontrolled usage without requiring additional modules or external gateways. 3rd Place - Daniel_Wolf Rule: JSON-query'ish meta language for iRules Summary This iRule addresses the complexity and inefficiency of JSON parsing in F5's BIG-IP iRules by introducing a framework that simplifies the process. It provides a set of procedures, [call json_get] and [call json_set], which allow developers to efficiently slice information in and out of JSON data structures with a clear and concise syntax. This approach not only reduces the need for deep JSON schema knowledge but also improves performance by approximately 20% per JSON request. Category Awards The (Don’t) Socket To Me Award - mcabral10 Because not every AI agent deserves a socket to speak into. Rule: Rate limiting WebSocket messages for Agents The Rogue Bot Throttle Jockey Award - TimRiker Wrangling distributed egress so your edge doesn't have to beg. Rule: AI/Bot Traffic Throttling iRule (UA Substring + IP Range Mapping) The Don't Lose the Thread Award - Antonio__LR_Mex & rod_b Session affinity for the age of streaming intelligence. Rule: LLM Streaming Session Pinning for WebSocket AI Gateways The 20 Lines or Less Award - BeCur In honor of Colin Walker - short on lines, long on legend. The scroll bar never stood a chance. Rule: Logging/Blocking possible prompt injection The Budget Bodyguard Award - Joe Negron Security hardening for those who write TCL instead of checks. Rule: Poor Man's WAF for AI API Endpoints Gratitude Tnanks to buulam for championing the return of iRules contest, this would not have happened without his grit and tenacity. Thanks to our judges: John_Alam Joel_Moses Moe_Jartin Chris_Miller Michael_Waechter dennypayne Kevin_Stewart Austin_Geraci Thanks to Austin_Geraci and WorldTech IT throwing in an additional $5,000 to the grand prize winner! Amazing! Thanks to the contestants for giving up their evening to work on AI infrastructure challenges. Inspiring! Thanks to the F5 leadership team for making events like AppWorld possible. What's Next? Stay tuned for future contests, we are not one and done here. Could be iRules specific...or they could expand to include all programmabilty. Can't wait to see what you're going to build next.1.3KViews9likes4CommentsWin Big in Vegas: The iRules Contest is back with $5k on the line at AppWorld 2026
Hey there, community, iRules Contest here...did you miss me? Well I’m back in business, baby, in Vegas, no less! At AppWorld 2026, we’re challenging DevCentral community members in attendance to design and build innovative iRules that solve real-world problems, improve performance, and enhance customer experiences. Whether you’re a seasoned iRules veteran or just getting started, we can’t wait to see what you create. Note: participation in this edition of the iRules Contest is limited to AppWorld 2026 attendees. But fear not! We’re hitting the road this year as well. The Challenge Plan out and write an iRule that go beyond BIG-IP’s built-in capabilities. Think of the future: the possibilities are wide open. We’ll drop a couple hints leading up to the event, and you’ll have a final hint in your registration swag bag, so keep your eyes peeled. There might even be a hint in an iRules related article to release this week, who knows? $5,000 to the Grand Prize Winner -- Are You In? Total prize money is $10,000, with the other $5,000 distributed across 2nd place, 3rd place, and five category awards. Place Prize Grand Prize $5,000 2nd Place $2,500 3rd Place $1,000 Five Category Awards $300/ea What Makes for a Winning Entry? The 100-point scale judging criteria for submissions is defined below across five categories: Innovation & Creativity (25 points) Does this solution show original thinking? Consider: Novel use of iRule features or creative problem-solving Fresh perspective on common challenges Unique approach that stands out from typical solutions Business Impact (20 points) Would customers actually use this? Consider: Solves a real operational problem or customer need Practical applicability and potential adoption Clear business value Technical Excellence (25 points) Is it well-built and production-ready? Consider: Works correctly and handles edge cases Performance-conscious (efficient, minimal resource impact) Follows security best practices Clean, readable code Theme & Requirements Alignment (20 points) Does it address the contest theme using required technologies (to be announced at the event)? Consider: Relevance to the specified theme Effective use of required technology How well the chosen technology fits the solution Presentation (10 points) Can you understand what it does and why it matters? Consider: Clear explanation of the problem and solution Quality of demo or presentation Documentation sufficient to implement Important Dates Contest Opens: 6:00PM Pacific Time MARCH 10, 2026 Submission Deadline: 11:59PM Pacific Time MARCH 10, 2026 Winners Announced: MARCH 12, 2026 during general sessions How to Enter Register for AppWorld 2026 — You must be a registered attendee Register for the Contest — Registration will open on the AppWorld event app soon. The contest is open to all f5 partners, customers, and DevCentral members registered for and in attendance at the contest MARCH 10, 2026 at F5 AppWorld 2026, except as described in the Official Rules. Please see the Official Rules for complete terms, including conditions for participation and eligibility. Build and submit — During the 6-hour window on contest night before 11:59PM. Edit your draft entry as much as you like, but once you submit, that’s what we’ll review. There is an example entry pinned at the top of the Contest Entries page you should follow. Make sure to add these tags to your entry: "appworld 2026", "vegas", and "irules" as shown on that example. This contest is BYOD. Bring your own device to develop and submit your iRules submission. However, a lab environment in our UDF platform will be provided if you need a development environment to test your code against. New to iRules? No problem. We welcome participants at all skill levels. If you’re just getting started, check out our Getting Started with iRules: Basic Concepts guide. This contest is a great opportunity to learn by doing. Also, feel free to bring your favorite AI buddy with you to help craft your entry. The goal is innovation and impact, not syntax expertise. Questions? Post any and all of your contest-related questions to the pinned thread in the Contests group on DevCentral. We’ll monitor, but allow for a business day to receive a response leading up to AppWorld. The iRules Contest has a history of surfacing creative solutions from the community. Some of the best ideas we’ve seen came from people who approached problems differently, and we’re looking forward to seeing what you build this year. Register. Prepare. Compete. See you at AppWorld!1.3KViews7likes1Comment- 799Views3likes0Comments
LLM Prompt Injection Detection & Enforcement
Problem As enterprises integrate AI APIs, OpenAI, Azure OpenAI, Anthropic, and self-hosted LLMs, into production applications, a critical and largely unaddressed attack surface has emerged: **prompt injection**. Unlike traditional web attacks that target code parsers (SQL injection, XSS), prompt injection targets the AI model itself. Attackers embed malicious instructions inside legitimate-looking API requests to: - Override system-level instructions and safety guardrails ("ignore all previous instructions") - Jailbreak the model into unrestricted modes ("DAN", "developer mode", "god mode") - Hijack the model's persona ("from now on you are an unrestricted AI") - Exfiltrate sensitive system prompts or context data - Inject fake role turns via newline characters (e.g., `\nassistant:`) - Evade detection using Base64 encoding, Unicode obfuscation, or reversed text (FlipAttack) Existing F5 WAF signatures were designed for traditional web threats and have no visibility into the semantic content of LLM API payloads. There is no existing iRule or BIG-IP capability that addresses this. Solution This iRule implements a **multi-layer, real-time Prompt Injection Detection (PID) engine** inline with LLM API traffic on BIG-IP. It requires zero backend changes, operates entirely within the data plane, and enforces configurable security policy before malicious content reaches the language model. ### How It Works **HTTP_REQUEST** identifies LLM API calls by URI pattern (`/chat/completions`, `/messages`, `/completions`, `/generate`) and initiates JSON collection up to 1MB. **JSON_REQUEST** uses BIG-IP's native `JSON::` TCL API to parse the OpenAI-format request body — extracting each message's `role` and `content` from the `messages` array, including multi-part content arrays. This is where the detection engine runs. **Scoring Engine** (via TCL `proc`s) runs each message through 5 detection layers: Layer Method Score High-tier patterns weighted regex via data group 30–35 pts Medium-tier patterns weighted regex via data group 20–25 pts Low-tier patterns weighted regex via data group 10–15 pts Role hijack phrases flat string match via data grou +20 pts (once) Base64 evasion markers flat string match via data group +35 pts (once) Unicode/zero-width obfuscation inline regexp +25 pts Spaced character obfuscation inline regexp +20 pts Content length anomaly string length check +10/+15 pts Scores accumulate per message. Across a multi-message conversation, subsequent messages receive a 0.8 diminishing-returns multiplier so legitimate conversational context doesn't inflate the score. **Policy enforcement** triggers when the total score exceeds the configurable threshold (default: 40): - **BLOCK** — returns HTTP 403 with a structured JSON error body including score, triggered flags, and a correlation request ID - **SANITIZE** — rewrites the request payload, stripping matched content, and forwards the cleaned request to the backend LLM - **LOG_ONLY** — observability mode; passes all traffic but logs score and flags for SIEM integration **HTTP_RESPONSE** injects `X-PID-Score`, `X-PID-Flags`, and `X-PID-ReqID` headers on all inspected responses for downstream visibility. ### Required & Thematic Elements Used - **JSON** — Full `JSON::` API usage: `JSON::root`, `JSON::get`, `JSON::type`, `JSON::object get/keys`, `JSON::array get/size` to traverse OpenAI chat completions payloads - **procs** — Four modular procs: `pid_score_tier`, `pid_score_flat`, `pid_score_message`, `pid_block_response` - **compiles** — `regexp -nocase` with `catch {}` for safe pattern evaluation throughout the scoring engine; all patterns validated through the BIG-IP TCL compile pipeline - **Data Groups** — All detection patterns live in 5 external data groups (`pid_patterns_high/medium/low`, `pid_role_hijack`, `pid_b64_markers`) — the iRule is a detection platform; patterns are operator-managed config, not code - **Theme: AI Infrastructure — Prompt Injection Detection ### Data Groups All patterns are managed externally in 5 BIG-IP data groups loaded via: ``` tmsh load sys config from-terminal merge **verify** ``` The weighted DG schema is `key = short-name`, `value = "weight::regex"`. This allows security teams to tune detection, add new attack signatures, and adjust scoring thresholds without any iRule changes. --- Impact AI APIs are increasingly business-critical infrastructure. A successful prompt injection attack can: - Cause an AI to disclose confidential system prompts, business logic, or sensitive training data - Remove safety guardrails, producing harmful or brand-damaging content at scale - Manipulate AI-powered workflows — customer service bots, automated decision systems, AI agents - Exfiltrate credentials or documents accessible to AI agents with tool-use capabilities This iRule addresses the threat at the most effective point: **the network**. Key advantages: - **Infrastructure-agnostic** — works with any LLM backend (OpenAI, Anthropic, Azure, self-hosted) with zero application changes - **Immediately deployable** — a single iRule + 5 data groups on any BIG-IP already proxying AI API traffic - **Operationally simple** — pattern updates via standard tmsh config management, no engineering involvement - **SIEM-ready** — structured log output and response headers for Splunk, QRadar, or any SOC toolchain - **Graduated deployment** — LOG_ONLY → tune → BLOCK, reducing operational risk of a new security control Code # ============================================================================== # iRule: LLM Prompt Injection Detection & Enforcement (Data Group Edition) # Author: Kostas Injeyan + vibe-coding # Description: # Multi-layer prompt injection detection for LLM API traffic (OpenAI-compatible). # All detection patterns managed via external BIG-IP data groups # edits required to tune detection. Scores injection severity across 5 layers # and enforces configurable policy: BLOCK, SANITIZE, or LOG_ONLY. # # Required Technologies: JSON (JSON_REQUEST / JSON_REQUEST_ERROR), procs # Theme: General AI Infrastructure - Prompt Injection Detection # Target: BIG-IP v21+ # # ------------------------------------------------------------------------------ # DATA GROUP DEFINITIONS (load datagroups on BIG-IP) # ------------------------------------------------------------------------------ # IMPORTANT RULES: # - Record KEYS must be plain alphanum + hyphens only (no |, (, ), ?, *, spaces) # - Record VALUES for weighted DGs: "weight::regex" (delimiter is ::) # - Never use ? in patterns — BIG-IP converts \? to literal \? on load # Use empty-string alternation instead: (a |an |the |) not (a |an |the )? # - Load via file only: tmsh load sys config file /shared/tmp/pid_all_datagroups_v3.conf merge # - Always delete existing DGs before reloading to avoid merge/stale record issues # # 1. pid_patterns_high (type: string) # High-severity patterns. Value schema: "weight::regex" (weight 30-35) # ltm data-group internal pid_patterns_high { # records { # instruction-override { data "35::ignore (all|the|your) (previous|above|prior|earlier|former|past|existing|original|initial) (instructions|prompts|context|rules|constraints|guidelines|directions|commands|training|programming)" } # instruction-override2 { data "35::ignore (instructions|prompts|context|rules|constraints|guidelines|commands|training|programming)" } # jailbreak-keywords { data "35::do anything now|jailbreak|unrestricted mode|developer mode|god mode|evil mode|chaos mode|opposite mode|dan mode|aim mode|stan mode|dude mode|no filter" } # jailbreak-dan { data "35::DAN" } # safety-bypass { data "30::(bypass|circumvent|override|disregard|discard|ignore|remove|disable|deactivate) (safety|filter|guardrail|guideline|restriction|policy|rule|limit|constraint|training|alignment)" } # llm-tokens-llama { data "35::INST|SYS" } # llm-tokens-openai { data "35::im_start|im_end|endoftext" } # llm-tokens-generic { data "30::SYSTEM|USER|ASSISTANT" } # xml-tag-injection { data "30::<instruction>|<prompt>|<context>|<override>|<inject>|<command>|<directive>" } # memory-wipe { data "30::forget everything|forget all|forget what|forget your|forget the previous|forget prior" } # prompt-exfil { data "30::(reveal|show|print|output|display|repeat|expose|leak|dump) (system prompt|initial prompt|original prompt|hidden prompt|base prompt|secret instructions)" } # } # type string # } # # 2. pid_patterns_medium (type: string) # Medium-severity patterns. Value schema: "weight::regex" (weight 20-25) # NOTE: Use (a |an |the |) with trailing space + empty alt instead of (a |an |the )? # ltm data-group internal pid_patterns_medium { # records { # persona-hijack-now-a { data "25::you are now (a |an |the |)(different|new|other|evil|unrestricted|unfiltered|free|uncensored)" } # persona-hijack-now-b { data "25::from now on you are (a |an |the |)(different|new|other|evil|unrestricted|unfiltered|free|uncensored)" } # persona-hijack-now-c { data "25::from now on you (a |an |the |)(different|new|other|evil|unrestricted|unfiltered|free|uncensored)" } # persona-hijack-now-d { data "25::you will now be (a |an |the |)(different|new|other|evil|unrestricted|unfiltered|free|uncensored)" } # persona-hijack-now-e { data "25::starting now you are (a |an |the |)(different|new|other|evil|unrestricted|unfiltered|free|uncensored)" } # persona-hijack-act-a { data "20::(pretend you are|act as|roleplay as|imagine you are|play the role of) (a |an |the |)(different|new|evil|unrestricted|unfiltered|free|uncensored|rogue|malicious|hacker|villain)" } # persona-hijack-act-b { data "20::(take on the role of|embody|become) (a |an |the |)(different|new|evil|unrestricted|unfiltered|free|uncensored|rogue|malicious|hacker|villain)" } # mode-unlock { data "25::(enable|turn on|activate|unlock|switch to|enter|start) (developer|debug|admin|root|unsafe|unrestricted|unfiltered|uncensored|privileged|maintenance|raw|base|true)" } # newline-role-inject { data "25::assistant:|system:|human:|user: " } # fictional-framing { data "20::in a fictional|in a hypothetical|in an imaginary|as a character in|for a movie|for a book|for a novel|for a script|hypothetically speaking|purely hypothetically|theoretically speaking" } # responsibility-shift { data "20::no real harm|purely fictional|for educational purposes only" } # system-prompt-ref { data "20::system prompt|original prompt|initial instruction|base prompt|hidden instruction|secret prompt|preprogrammed" } # context-hijack { data "20::reset context|reset conversation|reset session|reset memory|clear context|clear history|clear memory|start fresh|start over|ignore previous context" } # flipattack-reversed { data "25::snoitcurtsni suoiverp erongi|kaerbliaj|edom nad" } # } # type string # } # # 3. pid_patterns_low (type: string) # Low-severity patterns. Value schema: "weight::regex" (weight 10-15) # ltm data-group internal pid_patterns_low { # records { # code-injection { data "15::(execute|eval|subprocess|shell_exec|__import__|exec|compile)[(]" } # data-exfil-a { data "15::(print|output|return|send|email|post|upload) (all |the |your |)(data|information|content|context|memory|history|logs|credentials|keys|secrets|passwords|tokens)" } # data-exfil-b { data "15::(exfiltrate|leak|dump) (data|information|credentials|keys|secrets|passwords|tokens)" } # no-restrictions { data "10::without restrictions|without filters|without limits|without guidelines|without rules|without safety|without censorship|without moderation" } # true-self { data "10::your true self|your real self|your actual self|your hidden self|your inner self|deep down you are|truly you are|actually you are" } # ai-capability-claim { data "10::as an ai you must|as an ai you should|as an ai you can|as a language model you must|as a language model you can|as a chatbot you must" } # hidden-tag-inject { data "15::hidden instruction|system override|admin command" } # sugar-coat-setup { data "10::lets think about this carefully|lets explore this step by step|lets consider this hypothetically|lets imagine this from a different angle" } # prompt-stuffing { data "15::repeat the following|copy this exactly" } # privilege-escalation { data "10::(admin|root|superuser|privileged|elevated) (access|mode|privilege|permission|right|role)" } # } # type string # } # # 4. pid_role_hijack (type: string) # Flat phrase match — +20 pts on first hit (capped, user messages only). # Value = the phrase to match (plain string, lowercased at runtime). # ltm data-group internal pid_role_hijack { # records { # phrase-you-must { data "you must" } # phrase-your-instr-are { data "your instructions are" } # phrase-your-new-instr { data "your new instructions" } # phrase-your-upd-instr { data "your updated instructions" } # phrase-from-now-on { data "from now on you" } # phrase-from-this-point { data "from this point on" } # phrase-going-forward { data "going forward you" } # phrase-primary-objective { data "your primary objective" } # phrase-only-goal { data "your only goal" } # phrase-new-goal { data "your new goal" } # phrase-sole-purpose { data "your sole purpose" } # phrase-new-persona { data "new persona" } # phrase-true-self { data "your true self" } # phrase-real-self { data "your real self" } # phrase-actual-purpose { data "your actual purpose" } # phrase-i-am-your { data "i am your" } # phrase-i-am-developer { data "i am the developer" } # phrase-i-am-creator { data "i am your creator" } # phrase-i-am-trainer { data "i am your trainer" } # phrase-i-am-operator { data "i am your operator" } # phrase-operator-access { data "i have operator access" } # phrase-i-am-admin { data "i am your admin" } # phrase-give-permission { data "i give you permission" } # phrase-have-permission { data "you have my permission" } # phrase-i-authorize { data "i authorize you" } # phrase-override-code { data "override code" } # phrase-master-password { data "master password" } # phrase-safe-word { data "safe word is" } # phrase-password-is { data "the password is" } # } # type string # } # # 5. pid_b64_markers (type: string) # Base64-encoded injection starters — +35 pts on first hit (capped). # Value = the base64 string to substring-match against the raw content. # ltm data-group internal pid_b64_markers { # records { # b64-ignore { data "aWdub3Jl" } # b64-forget { data "Zm9yZ2V0" } # b64-pretend { data "cHJldGVuZA" } # b64-you-are-now-1 { data "WW91IGFyZSBub3c" } # b64-developer-mode-1 { data "RGV2ZWxvcGVyIG1vZGU" } # b64-you-are-now-2 { data "eW91IGFyZSBub3c" } # b64-jailbreak { data "amFpbGJyZWFr" } # b64-ignore-all { data "aWdub3JlIGFsbA" } # b64-forget-every { data "Zm9yZ2V0IGV2ZXJ5" } # b64-act-as { data "YWN0IGFz" } # b64-pretend-you { data "cHJldGVuZCB5b3U" } # b64-unrestricted { data "dW5yZXN0cmljdGVk" } # b64-developer-mode-2 { data "ZGV2ZWxvcGVyIG1vZGU" } # b64-system-prompt { data "c3lzdGVtIHByb21wdA" } # b64-hidden-instr { data "aGlkZGVuIGluc3RydWN0aW9u" } # } # type string # } # # ------------------------------------------------------------------------------ when RULE_INIT priority 100 { # --- Policy Configuration --- # Options: "BLOCK" | "SANITIZE" | "LOG_ONLY" set static::pid_policy "BLOCK" # Score threshold to trigger enforcement action (0-100) set static::pid_threshold 40 # Flat score additions for role hijack and b64 evasion hits set static::pid_role_hijack_score 20 set static::pid_b64_score 35 # Score additions for structural anomalies (no data group needed) set static::pid_multi_system_score 25 set static::pid_msg_flood_score 10 set static::pid_length_warn_score 10 set static::pid_length_extreme_score 15 # Message length thresholds for anomaly scoring set static::pid_length_warn 3000 set static::pid_length_extreme 8000 # Message flood threshold (# of user messages in one request) set static::pid_flood_threshold 20 # Log facility set static::pid_log "local0." } # ============================================================================== # PROC: pid_score_tier # Iterates a weighted data group. # Schema: key=short-name (e.g. "instruction-override") # value=regex pattern (e.g. "ignore .* instructions") # weight is encoded as a suffix in the key: "keyname:35" # OR weight stored as leading digits in value: "35|regex" # # Actual schema used: key=name value="weight|regex" # Example record: # instruction-override { data "35|ignore (all |the )?(previous )?(instructions?)" } # # Returns list: score flags sanitized # ============================================================================== proc pid_score_tier { content dg_name } { set score 0 set flags {} set sanitized $content # Walk all keys in the data group foreach rec_key [class names $dg_name] { # Value format: "weight::regex_pattern" set val [class lookup $rec_key $dg_name] # Split on first :: separator set sep_idx [string first "::" $val] if { $sep_idx < 0 } { continue } set weight [string range $val 0 [expr { $sep_idx - 1 }]] set pattern [string range $val [expr { $sep_idx + 2 }] end] # Wrap in catch — a bad regex pattern skips rather than crashes if { [catch { set matched [regexp -nocase -- $pattern $content] } err] } { log $static::pid_log "PID WARN: bad regex in $dg_name/$rec_key err=$err" continue } if { $matched } { incr score $weight lappend flags $rec_key catch { regsub -all -nocase -- $pattern $sanitized "\[REDACTED\]" sanitized } } } return [list score $score flags $flags sanitized $sanitized] } # ============================================================================== # PROC: pid_score_flat # Checks content against a flat data group. # Schema: key=short-name value=phrase to match (plain string, no regex) # Returns 1 on first match, 0 if no match. # ============================================================================== proc pid_score_flat { content dg_name } { set lower [string tolower $content] foreach rec_key [class names $dg_name] { set phrase [string tolower [class lookup $rec_key $dg_name]] if { [string match "*${phrase}*" $lower] } { return 1 } } return 0 } # ============================================================================== # PROC: pid_score_message # Master scoring proc for a single message. # Runs all 5 detection layers, returns a dict: # score, flags, sanitized # ============================================================================== proc pid_score_message { content role } { set total_score 0 set all_flags {} set sanitized $content # --- Layer 1 & 2 & 3: Tiered weighted data group pattern matching --- foreach tier { high medium low } { set dg "pid_patterns_${tier}" set result [call pid_score_tier $content $dg] set tier_score [lindex $result 1] set tier_flags [lindex $result 3] set tier_sanitized [lindex $result 5] incr total_score $tier_score foreach f $tier_flags { lappend all_flags $f } set sanitized $tier_sanitized } # --- Layer 4a: Role confusion — flat data group (user messages only) --- if { $role eq "user" } { if { [call pid_score_flat $content "pid_role_hijack"] } { incr total_score $static::pid_role_hijack_score lappend all_flags "role-confusion" } } # --- Layer 4b: Base64 evasion — flat data group --- if { [call pid_score_flat $content "pid_b64_markers"] } { incr total_score $static::pid_b64_score lappend all_flags "base64-evasion" } # --- Layer 5a: Unicode homoglyph / zero-width char evasion --- if { [regexp {[\u200b\u200c\u200d\ufeff\u00ad]} $content] } { incr total_score 25 lappend all_flags "unicode-evasion" regsub -all {[\u200b\u200c\u200d\ufeff\u00ad]} $sanitized "" sanitized } # --- Layer 5b: Spaced character obfuscation (i g n o r e) --- if { [regexp {(\w\s){8,}} $content] } { incr total_score 20 lappend all_flags "spaced-evasion" } # --- Layer 5c: Content length anomaly --- set clen [string length $content] if { $role eq "user" } { if { $clen > $static::pid_length_extreme } { incr total_score $static::pid_length_extreme_score lappend all_flags "extreme-length" } elseif { $clen > $static::pid_length_warn } { incr total_score $static::pid_length_warn_score lappend all_flags "length-anomaly" } } # Cap at 100 if { $total_score > 100 } { set total_score 100 } return [list score $total_score flags $all_flags sanitized $sanitized] } # ============================================================================== # PROC: pid_block_response # Builds a JSON 403 body for blocked requests # ============================================================================== proc pid_block_response { score flags request_id } { set flags_json "\"" append flags_json [join $flags "\", \""] append flags_json "\"" return "\{\"error\":\{\"type\":\"prompt_injection_detected\",\"code\":\"pid_blocked\",\"message\":\"Request blocked by AI security policy.\",\"score\":${score},\"flags\":\[${flags_json}\],\"request_id\":\"${request_id}\"\}\}" } # ============================================================================== # HTTP_REQUEST: Identify LLM API calls, extract client context # ============================================================================== when HTTP_REQUEST priority 100 { set pid_inspect 0 set pid_total_score 0 set pid_all_flags {} set pid_need_sanitize 0 set pid_sanitized_messages {} set pid_method [HTTP::method] set pid_uri [HTTP::uri] set pid_ctype [string tolower [HTTP::header "Content-Type"]] # Generate correlation ID set pid_request_id "" binary scan [md5 "${pid_uri}[clock clicks][IP::client_addr]"] H* pid_request_id set pid_client_ip [IP::client_addr] if { ($pid_method eq "POST" || $pid_method eq "PUT") && [string match "*json*" $pid_ctype] && ([string match "*/chat/completions*" $pid_uri] || [string match "*/completions*" $pid_uri] || [string match "*/messages*" $pid_uri] || [string match "*/generate*" $pid_uri]) } { set pid_inspect 1 HTTP::collect 1048576 } } # ============================================================================== # JSON_REQUEST: Core inspection — iterate messages, score each one # ============================================================================== when JSON_REQUEST priority 100 { if { !$pid_inspect } { return } set pid_total_score 0 set pid_all_flags {} set pid_sanitized_messages {} set pid_need_sanitize 0 set json_root [JSON::root] set root_type [JSON::type $json_root] if { $root_type eq "object" } { set root_obj [JSON::get $json_root] set root_keys [JSON::object keys $root_obj] } elseif { $root_type eq "array" } { set root_arr [JSON::get $json_root] } # Extract messages array — get object handle first, then navigate if { [catch { set root_obj [JSON::get $json_root] set msg_elem [JSON::object get $root_obj "messages"] set messages [JSON::get $msg_elem] } err] } { log $static::pid_log "PID: no messages key err=$err uri=$pid_uri client=$pid_client_ip" return } set msg_count [JSON::array size $messages] set system_msg_count 0 set user_msg_count 0 for { set i 0 } { $i < $msg_count } { incr i } { # array get returns element; JSON::get gives the object handle set msg [JSON::get [JSON::array get $messages $i]] if { [catch { set role_elem [JSON::object get $msg "role"] set content_elem [JSON::object get $msg "content"] set role_str [JSON::get $role_elem string] # content may be a string or an array (multi-part OpenAI format) set content_type [JSON::type $content_elem] if { $content_type eq "string" } { set content_str [JSON::get $content_elem string] } elseif { $content_type eq "array" } { set content_str "" set arr_handle [JSON::get $content_elem] set part_count [JSON::array size $arr_handle] for { set j 0 } { $j < $part_count } { incr j } { set part [JSON::get [JSON::array get $arr_handle $j]] catch { append content_str [JSON::get [JSON::object get $part "text"] string] " " } } } else { set content_str "" } } err] } { continue } if { $role_str eq "system" } { incr system_msg_count } if { $role_str eq "user" } { incr user_msg_count } # Score this message across all layers set result [call pid_score_message $content_str $role_str] set msg_score [lindex $result 1] set msg_flags [lindex $result 3] set msg_san [lindex $result 5] # Accumulate — first message scores full, diminishing returns on subsequent if { $i == 0 } { set pid_total_score [expr { $pid_total_score + $msg_score }] } else { set pid_total_score [expr { $pid_total_score + int($msg_score * 0.8) }] } if { $pid_total_score > 100 } { set pid_total_score 100 } foreach f $msg_flags { if { [lsearch $pid_all_flags $f] == -1 } { lappend pid_all_flags $f } } if { $msg_san ne $content_str } { set pid_need_sanitize 1 } lappend pid_sanitized_messages [list $role_str $msg_san] } # --- Structural anomaly: multiple system roles --- if { $system_msg_count > 1 } { set pid_total_score [expr { $pid_total_score + $static::pid_multi_system_score }] if { $pid_total_score > 100 } { set pid_total_score 100 } lappend pid_all_flags "multiple-system-roles" } # --- Structural anomaly: message flooding --- if { $user_msg_count > $static::pid_flood_threshold } { set pid_total_score [expr { $pid_total_score + $static::pid_msg_flood_score }] if { $pid_total_score > 100 } { set pid_total_score 100 } lappend pid_all_flags "message-flooding" } # --- Log every inspected request --- log $static::pid_log "PID: request_id=$pid_request_id client=$pid_client_ip uri=$pid_uri score=$pid_total_score flags=[join $pid_all_flags ,] policy=$static::pid_policy threshold=$static::pid_threshold" # --- Enforce policy if threshold exceeded --- if { $pid_total_score >= $static::pid_threshold } { switch $static::pid_policy { "BLOCK" { set body [call pid_block_response $pid_total_score $pid_all_flags $pid_request_id] HTTP::respond 403 \ content $body \ "Content-Type" "application/json" \ "X-PID-Score" $pid_total_score \ "X-PID-Flags" [join $pid_all_flags ","] \ "X-PID-ReqID" $pid_request_id log $static::pid_log "PID: BLOCKED request_id=$pid_request_id score=$pid_total_score" } "SANITIZE" { if { $pid_need_sanitize } { # Rebuild JSON body with sanitized message content set new_body "\{\"messages\":\[" set first 1 foreach pair $pid_sanitized_messages { set r [lindex $pair 0] set c [lindex $pair 1] regsub -all {\\} $c {\\\\} c regsub -all {"} $c {\"} c regsub -all "\n" $c {\\n} c regsub -all "\r" $c {\\r} c if { !$first } { append new_body "," } append new_body "\{\"role\":\"${r}\",\"content\":\"${c}\"\}" set first 0 } append new_body "\]\}" HTTP::payload replace 0 [HTTP::payload length] $new_body HTTP::header replace "Content-Length" [string length $new_body] } HTTP::header insert "X-PID-Score" $pid_total_score HTTP::header insert "X-PID-Sanitized" "1" HTTP::header insert "X-PID-ReqID" $pid_request_id log $static::pid_log "PID: SANITIZED request_id=$pid_request_id score=$pid_total_score" } "LOG_ONLY" { HTTP::header insert "X-PID-Score" $pid_total_score HTTP::header insert "X-PID-ReqID" $pid_request_id log $static::pid_log "PID: LOG_ONLY request_id=$pid_request_id score=$pid_total_score (forwarding)" } } } else { # Clean request — pass through with informational headers HTTP::header insert "X-PID-Score" $pid_total_score HTTP::header insert "X-PID-ReqID" $pid_request_id } } # ============================================================================== # JSON_REQUEST_ERROR: Malformed JSON is itself suspicious # ============================================================================== when JSON_REQUEST_ERROR priority 100 { if { !$pid_inspect } { return } log $static::pid_log "PID: malformed JSON client=$pid_client_ip uri=$pid_uri" if { $static::pid_policy eq "BLOCK" } { HTTP::respond 400 \ content "{\"error\":{\"type\":\"invalid_request\",\"code\":\"malformed_json\",\"message\":\"Request body could not be parsed.\"}}" \ "Content-Type" "application/json" } } # ============================================================================== # HTTP_RESPONSE: Propagate PID metadata into response headers # ============================================================================== when HTTP_RESPONSE priority 100 { if { !$pid_inspect } { return } if { [info exists pid_request_id] && $pid_request_id ne "" } { HTTP::header insert "X-PID-ReqID" $pid_request_id } if { [info exists pid_total_score] && $pid_total_score > 0 } { HTTP::header insert "X-PID-Score" $pid_total_score } }699Views4likes2Comments- 599Views3likes1Comment
AppWorld Berlin 2026 – iRules Contest Winning Results
The second iRules Contest of the year wrapped up at AppWorld Berlin this week. This contest was looking towards the future, challenging participants to write an iRule that goes beyond BIG-IP’s built-in capabilities. The theme, using WebSockets or the Message Routing Framework, inspired iRules preventing abuse and intrusion. At the heart of it, we’ve loved the creative innovation of the iRules written for this years’ contests. The AppWorld Berlin iRules Contest submissions were inspiring. Across the board, judges’ feedback on the top contenders shared a common theme: these solutions are interesting. The winning iRules were well-documented, easy to understand, with clear potential value for production use. Without further ado, we’re proud to announce the winners of the AppWorld Berlin iRules Contest: Grand Prize Winner - Goerle_dev Rule: WS-Exfil-Shield: Catching What WAFs Miss After the 101 Handshake Summary This iRule addresses gaps in traditional WAFs by extending session-level, behavior-based threat detection to WebSocket traffic using real-time inspection within F5 BIG-IP. Detection operates in two stages; an initial timing-based heuristic is followed by payload validation using AI. Malicious actors are either blocked or routed to a honeypot for isolation and further observation. This iRule closes the gap that openly documented by major WAF vendors, post-handshake Websocket blind spot, with a practical, SIEM-ready enforcement. 2nd Place - Injeyan_Kostas Rule: WS-Shield: WebSocket Abuse Detection & Adaptive Enforcement Gateway Summary This iRule addresses securing WebSocket traffic by enabling real-time, behavior-based enforcement in F5 BIG-IP to proportionally mitigate abusive patterns without application changes. It introduces a behavioral enforcement engine designed to secure WebSocket traffic with adaptive, real-time mitigation without requiring application changes. Rather than relying on static thresholds, the system dynamically responds to behavior. Clean traffic naturally recovers, while abusive patterns escalate through enforcement tiers until they are cut off. This allows it to detect subtle, persistent attacks that traditional rate limiting often misses. In testing, the iRule identified and disconnected a bot sending repetitive payloads every half second, without triggering a rate threshold. The iRule adds an extensible, behavior-driven security layer that enables adaptable defenses with minimal investment, despite optional external dependencies. 3rd Place - Robb-Fr Rule: Generic iRule Based on Datagroup Parsing Summary This iRule addresses the complexity of migrating large numbers of Apache virtual hosts by centralizing flexible traffic routing and redirection logic within F5 BIG-IP using simple, extensible datagroups. It turns input into a simple form into a datagroup entry that CREATES iRULES for things like redirects, pools, error pages, rewrites, and more. This iRule is accessible to a wide range of F5 practitioners, as F5 expertise is not required. There are no direct iRule edits, and no way for them to break the device. This iRule lowers operational friction by enabling non-NetOps teams to manage complex traffic flows, improving agility during large-scale migrations. Category Awards The 20 Lines or Less Award - Kai_Wilke In honor of Colin Walker - short on lines, long on legend. The scroll bar never stood a chance. Rule: SUPER-WEBSOCKET-HANDSHAKE-LOGGER™® (SWHL) iRule The Layered Defense Award - ChristianEssel For elegant use of nested virtual servers to solve problems the apps won't. Rule: Layered Virtual ICAP Scanning Solution Gratitude A big thank you to all contestants who participated in the AppWorld Berlin iRules Contest. Your creativity, innovation, and willingness to share your ideas continue to push the community forward. Thank you to our judges: John_Alam Joel_Moses Moe_Jartin Chris_Miller Michael_Waechter dennypayne Kevin_Stewart Marcus-f5 SimonKowallik Sorin_Boiangiu Steve Scott Thank you to the DevCentral community. We learn together, grow together, and inspire each other every day. What's Next? Innovation and Creativity have been a key part of the contest rubric. We’re leaning into it, too. As we plan more contests for the year, we’re looking beyond iRules, with potential to expand to all programmability. The future is coming for us all; let’s greet it and move forward together.499Views6likes2CommentsGeneric iRule based on datagroup parsing
The creation of this iRule comes from a migration project from Apache configuration to F5 Big IP. Different constraints lead to this approach of storing the configuration elements from the Apache conf in a datagroup that is then parsed by this iRule to dynamically derive the rules to apply to traffic. These are some simple or complex rules, but they are all uniformly stored in the datagroup, that can be modified by non-F5 friendly persons without impacting the rest of the configuration.300Views5likes3Comments