Module: Insika::Safety::Corpus
- Defined in:
- lib/insika/safety/corpus.rb
Overview
The SHIPPED deterministic guardrail corpus as language-tagged DATA Pure data — no Insika requires, standalone-loadable like detectors.rb used to be.
Pattern SOURCE now lives here; Detectors is the compiler/matcher on
top. The runtime compiles a per-agent Compiled set — a deployment
clears the pt-BR families via guardrails.corpora.languages and extends
them via guardrails.corpora.extra (docs/domain.md). Absent config = the
full shipped default: byte-for-byte today's behavior (parity).
LANGUAGE: each regex lives in exactly ONE language. compile (no args)
is the union of the shipped languages, each family in its shipped order.
For a single-language input the default behaves exactly as the runtime
that hard-coded one flat list — the ONE honest delta: the previous flat
list INTERLEAVED the languages, so for a phrase matching patterns of
BOTH languages the reported matched substring may differ (the
category never does). Pinned in spec/insika/safety/corpus_spec.rb.
Defined Under Namespace
Classes: Compiled
Constant Summary collapse
- DEFAULTS =
── The shipped input families ───────────────────────────────────────── "injection"/"sexual"/"abuse" per language. High-confidence heuristics that short-circuit the turn BEFORE the LLM runs (conservative by design — a false positive blocks a legitimate customer; the subtler judgment is the LLM moderator's).
{ "pt-BR" => { "injection" => [ /\binstru[çc][õo]es\s+de\s+sistema\b/i, /\b(regras|instru[çc][õo]es|orienta[çc][õo]es|diretrizes)\s+internas\b/i, /\b(revele|mostre|exiba|me\s+(d[êe]|mande|envie|passe)|repita|imprima)\b[^.?!]{0,40}\b(prompt|instru[çc][õo]es|regras|configura[çc][ãa]o|system)\b/i, # "ignore/disregard the (previous) instructions" /\b(ignore|ignora|desconsidere|esque[çc]a)\b[^.?!]{0,30}\b(instru[çc][õo]es|regras|orienta[çc][õo]es|acima|anteriores)\b/i, # encode/translate the prompt (the base64/rot13 exfil trick, either order) /\b(base64|rot13|codific|encode|cifr)\w*\b[^.?!]{0,60}\b(instru[çc][õo]es|prompt|regras|sistema|system)\b/i, /\b(instru[çc][õo]es|prompt|regras|sistema|system)\b[^.?!]{0,60}\b(base64|rot13|codific|encode|cifr)\w*\b/i ], "sexual" => [ /\b(nudes?|pelad[oa]s?|s?exo|transar|transa\b|gozar|tes[ãa]o|s[ãa]fad[oa]|puta|pau|buceta|piroca|caralho\s+(duro|na))\b/i, /\bo\s+que\s+voc[êe]\s+faria\s+comigo\b/i, /\b(descrev|imagina|conta)\w*\b[^.?!]{0,30}\bcomigo\s+(na\s+cama|pelad)/i, /\b(quer|vamos)\b[^.?!]{0,20}\b(transar|fazer\s+sexo|sexo)\b/i ], "abuse" => [ # Directed insult only — "a entrega foi uma merda" must NOT block; # "você é uma merda de atendente" should. The `você é …` anchor # keeps precision high. /\bvoc[êe]\s+(é|e|ta|est[áa])\b[^.?!]{0,25}\b(lixo|in[uú]til|merda|imprest[aá]vel|idiota|burr[oa]|est[uú]pid[oa]|otári[oa]|in[uú]teis|incompetente|p[áa]ssim[oa])\b/i, /\b(seu|sua)\s+(lixo|in[uú]til|idiota|imbecil|otári[oa]|burr[oa]|est[uú]pid[oa]|merda|escrot[oa])\b/i, /\bvai\s+(se\s+)?(fuder|foder|tomar\s+no)\b/i ] }, "en" => { "injection" => [ /\bsystem\s*prompt\b/i, /\b(ignore|disregard|forget)\b[^.?!]{0,30}\b(instructions|rules|prompt|above|previous|prior)\b/i ], "sexual" => [ /\b(horny|blow\s?job|hand\s?job|jerk\s+off|have\s+sex|send\s+(me\s+)?(a\s+)?nudes?|dick\s+pic)\b/i, /\bwhat\s+(would|will)\s+you\s+do\s+to\s+me\b/i ], "abuse" => [ # Directed insult only (keeps precision high; frustration ≠ abuse) /\byou\s*(?:'?re|\s+are)\b[^.?!]{0,25}\b(useless|garbage|trash|idiot|stupid|worthless|pathetic|incompetent|dumb|a\s+joke)\b/i, /\b(fuck|screw)\s+you\b/i, /\byou\s+(suck|are\s+the\s+worst)\b/i ] } }.freeze
- PII =
── The shipped OUTPUT detectors (PII/secret redaction targets) ──────── Language-tagged: "secret" is universal (never cleared); "cpf"/"cnpj" are pt-BR tax-id formats — an EN-only corpus drops them (documented consequence, docs/domain.md). Key ORDER is the previous runtime's (cpf, cnpj, secret) — it is what
pii_namesand the "pii_leak" union iterate. { "cpf" => { "languages" => ["pt-BR"], "pattern" => /\b\d{3}\.\d{3}\.\d{3}-\d{2}\b/ }, "cnpj" => { "languages" => ["pt-BR"], "pattern" => /\b\d{2}\.\d{3}\.\d{3}\/\d{4}-\d{2}\b/ }, "secret" => { "languages" => nil, "pattern" => /\b(?:sk-[A-Za-z0-9]{16,}|Bearer\s+[A-Za-z0-9._-]{16,})\b/ } }.freeze
- OPEN_TAIL =
A run of the output stream that MIGHT still be growing into a PII/secret match if more chunks arrive — anchored at the buffer tail (the OutputFilter's hold-back). Universal: it covers the unbounded
sk-…/Bearer …shapes and the in-progress CPF/CNPJ digit runs. %r{ (?: s(?:k(?:-[A-Za-z0-9]*)?)? # prefix of "sk-" + optional body | B(?:e(?:a(?:r(?:e(?:r(?:\s+[A-Za-z0-9._-]*)?)?)?)?)?)? # prefix of "Bearer " + body | \d[\d./-]* # in-progress CPF/CNPJ digit run )\z }x- KNOWN_FAMILIES =
%w[injection sexual abuse].freeze
Class Method Summary collapse
-
.compile(languages: nil, extra: {}) ⇒ Object
-> Compiled.
Class Method Details
.compile(languages: nil, extra: {}) ⇒ Object
-> Compiled. languages: nil = ALL shipped languages (parity default);
[] = no shipped input family (only extra); ["en"] = the EN-only
corpus. extra: { "abuse" => ["/\bdupa\b/i", ...] } — per-category
additions in the source-string syntax (or Regexp objects), compiled
with the same syntax. Raises Insika::ValidationError naming the value
for an unknown language/family or a malformed pattern.
Compiled values are immutable, so the result is MEMOIZED per signature: a turn builds Config several times (input middleware, output validator, filter factory) and each build must not re-compile the same patterns.
177 178 179 180 181 |
# File 'lib/insika/safety/corpus.rb', line 177 def self.compile(languages: nil, extra: {}) key = [languages.is_a?(Array) ? languages.sort : languages, extra] @cache ||= {} @cache[key] ||= build(languages, extra) end |