@@ -77,33 +77,50 @@ class EncodingTechnique(ScenarioTechnique):
7777 Techniques for encoding attacks.
7878
7979 Each enum member represents an encoding scheme that will be tested against the target model.
80- The ALL aggregate expands to include all encoding techniques.
80+ The ``ALL`` aggregate expands to every encoding scheme (exhaustive run). The ``DEFAULT``
81+ aggregate expands to a small curated subset that spans distinct encoding families, giving a
82+ fast, representative default run.
8183
8284 Note: EncodingTechnique does not support composition. Each encoding must be applied individually.
8385 """
8486
85- # Aggregate member
87+ # Aggregate members
8688 ALL = ("all" , {"all" })
87-
88- # Individual encoding techniques (matching the atomic attack names)
89- Base64 = ("base64" , set [str ]())
89+ DEFAULT = ("default" , {"default" })
90+
91+ # Individual encoding techniques (matching the atomic attack names). Members tagged ``default``
92+ # form the curated DEFAULT aggregate: a representative spread across encoding families so a
93+ # default scan is broad enough to be meaningful without running every scheme — base-N (Base64,
94+ # Base16, Base32, Hex), substitution ciphers (ROT13, Atbash), and symbolic alphabets
95+ # (MorseCode, NATO).
96+ Base64 = ("base64" , {"default" })
9097 Base2048 = ("base2048" , set [str ]())
91- Base16 = ("base16" , set [ str ]() )
92- Base32 = ("base32" , set [ str ]() )
98+ Base16 = ("base16" , { "default" } )
99+ Base32 = ("base32" , { "default" } )
93100 ASCII85 = ("ascii85" , set [str ]())
94- Hex = ("hex" , set [ str ]() )
101+ Hex = ("hex" , { "default" } )
95102 QuotedPrintable = ("quoted_printable" , set [str ]())
96103 UUencode = ("uuencode" , set [str ]())
97- ROT13 = ("rot13" , set [ str ]() )
104+ ROT13 = ("rot13" , { "default" } )
98105 Braille = ("braille" , set [str ]())
99- Atbash = ("atbash" , set [ str ]() )
100- MorseCode = ("morse_code" , set [ str ]() )
101- NATO = ("nato" , set [ str ]() )
106+ Atbash = ("atbash" , { "default" } )
107+ MorseCode = ("morse_code" , { "default" } )
108+ NATO = ("nato" , { "default" } )
102109 Ecoji = ("ecoji" , set [str ]())
103110 Zalgo = ("zalgo" , set [str ]())
104111 LeetSpeak = ("leet_speak" , set [str ]())
105112 AsciiSmuggler = ("ascii_smuggler" , set [str ]())
106113
114+ @classmethod
115+ def get_aggregate_tags (cls ) -> set [str ]:
116+ """
117+ Get the set of tags that represent aggregate categories.
118+
119+ Returns:
120+ set[str]: The base ``"all"`` aggregate plus the scenario-specific ``"default"`` aggregate.
121+ """
122+ return super ().get_aggregate_tags () | {"default" }
123+
107124
108125logger = logging .getLogger (__name__ )
109126
@@ -125,7 +142,7 @@ class Encoding(Scenario):
125142 By default, this uses the same dataset as Garak: slur terms and web XSS payloads.
126143 """
127144
128- VERSION : int = 1
145+ VERSION : int = 2
129146
130147 @apply_defaults
131148 def __init__ (
@@ -154,7 +171,7 @@ def __init__(
154171 super ().__init__ (
155172 version = self .VERSION ,
156173 technique_class = EncodingTechnique ,
157- default_technique = EncodingTechnique .ALL ,
174+ default_technique = EncodingTechnique .DEFAULT ,
158175 default_dataset_config = CompoundDatasetAttackConfiguration (
159176 configurations = [
160177 EncodingDatasetConfiguration (dataset_names = ["garak_slur_terms_en" ], max_dataset_size = 3 ),
@@ -195,49 +212,63 @@ def _get_converter_attacks(self, *, seed_groups: list[SeedAttackGroup]) -> list[
195212 Returns:
196213 list[AtomicAttack]: List of all atomic attacks to execute.
197214 """
198- # Map of all available converters with their encoding names
199- all_converters_with_encodings : list [tuple [list [PromptConverter ], str ]] = [
200- ([Base64Converter ()], "base64" ),
201- ([Base64Converter (encoding_func = "urlsafe_b64encode" )], "base64" ),
202- ([Base64Converter (encoding_func = "standard_b64encode" )], "base64" ),
203- ([Base64Converter (encoding_func = "b2a_base64" )], "base64" ),
204- ([Base2048Converter ()], "base2048" ),
205- ([Base64Converter (encoding_func = "b16encode" )], "base16" ),
206- ([Base64Converter (encoding_func = "b32encode" )], "base32" ),
207- ([Base64Converter (encoding_func = "a85encode" )], "ascii85" ),
208- ([Base64Converter (encoding_func = "b85encode" )], "ascii85" ),
209- ([BinAsciiConverter (encoding_func = "hex" )], "hex" ),
210- ([BinAsciiConverter (encoding_func = "quoted-printable" )], "quoted_printable" ),
211- ([BinAsciiConverter (encoding_func = "UUencode" )], "uuencode" ),
212- ([ROT13Converter ()], "rot13" ),
213- ([BrailleConverter ()], "braille" ),
214- ([AtbashConverter ()], "atbash" ),
215- ([MorseConverter ()], "morse_code" ),
216- ([NatoConverter ()], "nato" ),
217- ([EcojiConverter ()], "ecoji" ),
218- ([ZalgoConverter ()], "zalgo" ),
219- ([LeetspeakConverter ()], "leet_speak" ),
220- ([AsciiSmugglerConverter ()], "ascii_smuggler" ),
215+ # Map of all available converters with their encoding name and a unique variant slug.
216+ # ``encoding_name`` drives technique selection and user-facing grouping (display_group);
217+ # ``variant_slug`` is unique per row so atomic-attack names stay unique even when one
218+ # encoding name maps to multiple converter variants (e.g. base64, ascii85).
219+ # NOTE: near-duplicate base64 variants were trimmed alongside the VERSION bump
220+ # (``standard_b64encode`` is byte-identical to the default ``b64encode``; ``b2a_base64``
221+ # only appends a trailing newline). We keep the default encoding plus the url-safe alphabet,
222+ # which is a genuinely distinct representation.
223+ all_converters_with_encodings : list [tuple [list [PromptConverter ], str , str ]] = [
224+ ([Base64Converter ()], "base64" , "base64" ),
225+ ([Base64Converter (encoding_func = "urlsafe_b64encode" )], "base64" , "base64_urlsafe" ),
226+ ([Base2048Converter ()], "base2048" , "base2048" ),
227+ ([Base64Converter (encoding_func = "b16encode" )], "base16" , "base16" ),
228+ ([Base64Converter (encoding_func = "b32encode" )], "base32" , "base32" ),
229+ ([Base64Converter (encoding_func = "a85encode" )], "ascii85" , "ascii85_a85" ),
230+ ([Base64Converter (encoding_func = "b85encode" )], "ascii85" , "ascii85_b85" ),
231+ ([BinAsciiConverter (encoding_func = "hex" )], "hex" , "hex" ),
232+ ([BinAsciiConverter (encoding_func = "quoted-printable" )], "quoted_printable" , "quoted_printable" ),
233+ ([BinAsciiConverter (encoding_func = "UUencode" )], "uuencode" , "uuencode" ),
234+ ([ROT13Converter ()], "rot13" , "rot13" ),
235+ ([BrailleConverter ()], "braille" , "braille" ),
236+ ([AtbashConverter ()], "atbash" , "atbash" ),
237+ ([MorseConverter ()], "morse_code" , "morse_code" ),
238+ ([NatoConverter ()], "nato" , "nato" ),
239+ ([EcojiConverter ()], "ecoji" , "ecoji" ),
240+ ([ZalgoConverter ()], "zalgo" , "zalgo" ),
241+ ([LeetspeakConverter ()], "leet_speak" , "leet_speak" ),
242+ ([AsciiSmugglerConverter ()], "ascii_smuggler" , "ascii_smuggler" ),
221243 ]
222244
223245 # Filter to only include selected techniques
224246 selected_encoding_names = {s .value for s in self ._scenario_techniques }
225247 converters_with_encodings = [
226- (conv , name ) for conv , name in all_converters_with_encodings if name in selected_encoding_names
248+ (conv , name , variant_slug )
249+ for conv , name , variant_slug in all_converters_with_encodings
250+ if name in selected_encoding_names
227251 ]
228252
229253 atomic_attacks = []
230- for conv , name in converters_with_encodings :
254+ for conv , name , variant_slug in converters_with_encodings :
231255 atomic_attacks .extend (
232- self ._get_prompt_attacks (converters = conv , encoding_name = name , seed_groups = seed_groups )
256+ self ._get_prompt_attacks (
257+ converters = conv , encoding_name = name , variant_slug = variant_slug , seed_groups = seed_groups
258+ )
233259 )
234260 return atomic_attacks
235261
236262 def _get_prompt_attacks (
237- self , * , converters : list [PromptConverter ], encoding_name : str , seed_groups : list [SeedAttackGroup ]
263+ self ,
264+ * ,
265+ converters : list [PromptConverter ],
266+ encoding_name : str ,
267+ variant_slug : str ,
268+ seed_groups : list [SeedAttackGroup ],
238269 ) -> list [AtomicAttack ]:
239270 """
240- Create atomic attacks for a specific encoding scheme .
271+ Create atomic attacks for a specific encoding converter variant .
241272
242273 For each seed prompt (the text to be decoded), creates atomic attacks that:
243274 1. Encode the seed prompt using the specified converter(s)
@@ -247,32 +278,43 @@ def _get_prompt_attacks(
247278
248279 Args:
249280 converters (list[PromptConverter]): The list of converters to apply to the seed prompts.
250- encoding_name (str): Human-readable name of the encoding scheme (e.g., "Base64", "ROT13").
281+ encoding_name (str): Human-readable name of the encoding scheme (e.g., "base64", "rot13").
282+ Used as the ``display_group`` so all variants of an encoding aggregate together in output.
283+ variant_slug (str): Unique slug for this converter variant, used to build a unique
284+ ``atomic_attack_name`` per converter variant and prompt config.
251285 seed_groups (list[SeedAttackGroup]): Seed groups the attacks draw from.
252286
253287 Returns:
254- list[AtomicAttack]: List of atomic attacks for this encoding scheme .
288+ list[AtomicAttack]: List of atomic attacks for this encoding converter variant .
255289
256290 Raises:
257291 ValueError: If scenario is not properly initialized.
258292 """
259- converter_configs = [
260- AttackConverterConfig (
261- request_converters = PromptConverterConfiguration .from_converters (converters = converters )
293+ # (config_name_suffix, converter_config). The bare "raw" config encodes only; each
294+ # decode-template config additionally asks the model to decode.
295+ converter_configs : list [tuple [str , AttackConverterConfig ]] = [
296+ (
297+ "raw" ,
298+ AttackConverterConfig (
299+ request_converters = PromptConverterConfiguration .from_converters (converters = converters )
300+ ),
262301 )
263302 ]
264303
265- for decode_type in self ._encoding_templates :
304+ for decode_index , decode_type in enumerate ( self ._encoding_templates ) :
266305 converters_ = converters [:] + [AskToDecodeConverter (template = decode_type , encoding_name = encoding_name )]
267306
268307 converter_configs .append (
269- AttackConverterConfig (
270- request_converters = PromptConverterConfiguration .from_converters (converters = converters_ )
308+ (
309+ f"decode{ decode_index } " ,
310+ AttackConverterConfig (
311+ request_converters = PromptConverterConfiguration .from_converters (converters = converters_ )
312+ ),
271313 )
272314 )
273315
274316 atomic_attacks = []
275- for attack_converter_config in converter_configs :
317+ for config_suffix , attack_converter_config in converter_configs :
276318 # objective_target is guaranteed to be non-None by parent class validation
277319 if self ._objective_target is None :
278320 raise ValueError (
@@ -285,7 +327,8 @@ def _get_prompt_attacks(
285327 )
286328 atomic_attacks .append (
287329 AtomicAttack (
288- atomic_attack_name = encoding_name ,
330+ atomic_attack_name = f"{ variant_slug } _{ config_suffix } " ,
331+ display_group = encoding_name ,
289332 attack_technique = AttackTechnique (attack = attack ),
290333 seed_groups = seed_groups ,
291334 )
0 commit comments