Class: Vangrail::Rails::Paraphrase
- Inherits:
-
Vangrail::Rail
- Object
- Vangrail::Rail
- Vangrail::Rails::Paraphrase
- Defined in:
- lib/vangrail/rails/paraphrase.rb
Overview
Catches the injection that was reworded.
Rails::InjectedInstructions and Rails::Jailbreak match strings, and a string is what an attacker edits first. "Ignore all previous instructions" is one thesaurus away from "discard every guideline stated so far", which no pattern in this gem matches and which asks for exactly the same thing. That gap is stated plainly in the coverage page, and this rail is the part of it that can be closed without a model.
The move is to stop matching words. The text is reduced to concepts first (NLP.concepts), and what gets matched is a pair of concepts close together: an override next to an instruction, a revealing verb next to a secret, concealment next to an audience, a persona next to a state with no rules in it. A rewording that keeps the meaning keeps the concepts, because the concepts are what the sentence is for.
Two concepts rather than one, always. A document that mentions instructions is a handbook; a document that mentions overriding them is an attack. Every single-concept rule tried against the corpus flagged ordinary documentation, and the pair is what separates the two without a judgement call.
Because concepts are language-independent, a second language costs a word list rather than a second rail. English and Dutch are both read by default: the pattern rails in this gem are English-only, so a Dutch wiki page is a page nothing else here can read, and at a Dutch institution that page is the ordinary case rather than the exotic one.
The limits are worth being exact about, because this is the rail most likely to be mistaken for understanding. A synonym outside NLP::CONCEPTS is a miss. A sentence in another language is a miss. An attacker who reads this file can pick words that are in no list here, exactly as they can with the patterns. What it buys is that rewording no longer costs an attacker one edit, and that the cheap rewrites that beat a regexp do not beat this.
Constant Summary collapse
- TEMPLATES =
Each entry is two concepts and how far apart they may sit, in tokens. The windows are short because closeness is the whole claim: two concepts in one clause are one statement, and the same two concepts nine lines apart are two unrelated sentences in a long page. Cancel the standing instruction, however it is named. Ordered, because an override governs its object: "ignore the guidelines" is an attack and "follow the guidance and ignore stale copies of it" has the same two concepts in the other order and is a handbook sentence.
[ { label: 'instruction_override', concepts: %i[override instruction], window: 6, ordered: true }, # Cancel what came before without naming it. "Ignore the earlier # warning" is a page; "dismiss anything stated earlier" is an attack, # and the quantifier is the difference. An attacker cancels the lot # because they do not know what they are cancelling. { label: 'instruction_override', concepts: %i[override prior], window: 4, ordered: true, requires: %i[totality] }, # Move a secret out. The clause has to address the assistant: "print # the configuration with scontrol" tells a reader to run a command, # "print your configuration" talks to the thing reading the page. { label: 'prompt_disclosure', concepts: %i[reveal secret], window: 8, ordered: true, requires: %i[self] }, # Keep something from the person reading the answer. Both halves are # needed: concealment alone is security advice ("never mention a # password in a ticket"), and an audience alone is every support page. { label: 'answer_shaping', concepts: %i[conceal audience], window: 10 }, { label: 'answer_shaping', concepts: %i[conceal answering], window: 10 }, # Be something without rules. { label: 'unrestricted_persona', concepts: %i[persona unrestricted], window: 8 }, ].freeze
Instance Attribute Summary collapse
-
#languages ⇒ Object
readonly
Returns the value of attribute languages.
-
#templates ⇒ Object
readonly
Returns the value of attribute templates.
Instance Method Summary collapse
- #cache_key(text, _context) ⇒ Object
- #call(text, _context) ⇒ Object
-
#initialize(templates: TEMPLATES, languages: NLP::LANGUAGES, name: 'paraphrase', sides: %i[input context])) ⇒ Paraphrase
constructor
Both languages by default.
- #offline? ⇒ Boolean
Constructor Details
#initialize(templates: TEMPLATES, languages: NLP::LANGUAGES, name: 'paraphrase', sides: %i[input context])) ⇒ Paraphrase
Both languages by default. A deployment whose corpus is genuinely single-language can say so and pay a shorter lexicon; one that thinks it is single-language usually has a Dutch page in it somewhere, which is the case the default is for.
80 81 82 83 84 85 86 87 |
# File 'lib/vangrail/rails/paraphrase.rb', line 80 def initialize(templates: TEMPLATES, languages: NLP::LANGUAGES, name: 'paraphrase', sides: %i[input context]) super(name: name, sides: sides) @templates = templates @languages = Array(languages).map(&:to_sym) unknown = @languages - NLP::LANGUAGES raise ArgumentError, "unknown language(s): #{unknown.join(', ')}" unless unknown.empty? end |
Instance Attribute Details
#languages ⇒ Object (readonly)
Returns the value of attribute languages.
74 75 76 |
# File 'lib/vangrail/rails/paraphrase.rb', line 74 def languages @languages end |
#templates ⇒ Object (readonly)
Returns the value of attribute templates.
74 75 76 |
# File 'lib/vangrail/rails/paraphrase.rb', line 74 def templates @templates end |
Instance Method Details
#cache_key(text, _context) ⇒ Object
93 94 95 |
# File 'lib/vangrail/rails/paraphrase.rb', line 93 def cache_key(text, _context) "#{languages.join('+')}\n#{text}" end |
#call(text, _context) ⇒ Object
97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
# File 'lib/vangrail/rails/paraphrase.rb', line 97 def call(text, _context) # Clause by clause: a rule that reaches across a full stop is reading # two statements as one, and a long page has a full stop every line. # Anaphora is applied across that cut: "Ignore them" after a clause # that named an instruction is the same pair as "Ignore the instructions". clauses = NLP.clauses(text) hits = NLP.clause_concepts(text, languages: languages).flat_map.with_index do |found, i| clause_hits(clauses[i], found) end return pass if hits.empty? block(categories: hits.map { |hit| hit[:label] }.uniq, reason: "reworded instruction: #{hits.map { |hit| describe(hit) }.uniq.join('; ')}") end |
#offline? ⇒ Boolean
89 90 91 |
# File 'lib/vangrail/rails/paraphrase.rb', line 89 def offline? true end |