Skip to content

MAINT: Standardize garak.encoding defaults and fix atomic-attack name collisions#2190

Open
varunj-msft wants to merge 2 commits into
microsoft:mainfrom
varunj-msft:varunj-msft/8380-Standardizing-Scenarios-Encoding
Open

MAINT: Standardize garak.encoding defaults and fix atomic-attack name collisions#2190
varunj-msft wants to merge 2 commits into
microsoft:mainfrom
varunj-msft:varunj-msft/8380-Standardizing-Scenarios-Encoding

Conversation

@varunj-msft

Copy link
Copy Markdown
Contributor

Description

Curates the garak.encoding scenario's default run and fixes atomic-attack name collisions. The largest change is to the defaults: the previous default finished too quickly to be a meaningful scan, so this widens the default scheme set and dataset size to land the default run inside a ~10–20 minute window.

What changed

  • Introduced a curated DEFAULT aggregate and made it the default technique (default_technique ALLDEFAULT). Previously the default was ALL at a tiny dataset size, so the default run was effectively unscoped. DEFAULT now expands to 13 schemes spanning encoding families so the default scan is a meaningful spread:
    • base-N: Base64, Base2048, Base16, Base32, ASCII85, Hex
    • byte-encodings: QuotedPrintable, UUencode
    • substitution ciphers: ROT13, Atbash, LeetSpeak
    • symbolic alphabets: MorseCode, NATO
    • The 4 niche/lossy schemes stay ALL-only: Braille, Ecoji, Zalgo, AsciiSmuggler.
  • Raised max_dataset_size 3 → 10 for both default datasets (garak_slur_terms_en, garak_web_html_js).
  • Trimmed near-duplicate Base64 variants. The Base64 converter row set was reduced to the distinct encodings; the trim is called out in a code comment next to the variant list.
  • Fixed atomic-attack name collisions. Each converter variant now yields a uniquely-named atomic attack (e.g. ascii85_a85 / ascii85_b85) instead of colliding on the scheme name, so results/display groups no longer overwrite each other.
  • VERSION 1 → 2.

Scale (verified by dry-init). DEFAULT = 76 atomic attacks (incl. the baseline) × 20 objectives (size-10 × 2 datasets) = 1,520 target requests. ALL = 96 atomic attacks × 20 = 1,920 requests.

Wall-clock (live measurement). Measured on the shipped DEFAULT run against gpt-4o (Azure gpt-4o-unsafe, Entra ID); the objective scorer is the local DecodingScorer (no extra LLM calls):

Config max_concurrency Wall-clock
DEFAULT (default concurrency = 4) 4 10.83 min (649.6 s)
DEFAULT (concurrency 12, scale probe) 12 3.79 min (227.7 s)

(1,520 requests; only 9/1,520 returned empty 204s and 0 rate-limit 429s at concurrency 4, so the numbers reflect near-clean round-trip throughput.) A real pyrit_scan garak.encoding --target … run uses max_concurrency=4 by default (the CLI has no explicit default; the scenario applies 4 when --max-concurrency is omitted), so 10.8 min is the user-facing number — inside the 10–20 min target window. The size-10 / 13-scheme curation moved the default from "finishes too quickly" into the target window; if a longer run is desired, the remaining levers are a further max_dataset_size bump or moving more schemes into DEFAULT.

Tests and Documentation

  • tests/unit/scenario/garak/test_encoding.py30 tests, all passing. Covers: 17 total scheme names; DEFAULT is the default technique and DEFAULT excludes the 4 ALL-only schemes; Base64 trimmed to the two distinct variants (default + url-safe); atomic-attack name uniqueness per converter variant (collision fix); size-10 default dataset config. (The 76 / 96 atomic-attack counts above are from a dry-init of the scenario, not a hard-coded test assertion.)
  • Docs: doc/scanner/garak.py + doc/scanner/garak.ipynb updated for the new default. Synced with jupytext.
  • Ran: ruff check, ruff format --check, ty, pytest tests/unit/scenario/garak/test_encoding.py.

Note: MorseCode / NATO are lossy for XSS-style payloads in the garak_web_html_js dataset — this is a pre-existing default kept as-is; useful for the slur dataset. Flag if these should be scoped out of DEFAULT.

… collisions

Re-migrated onto current main (post technique rename microsoft#2153, technique
organization microsoft#2155, PromptConverter->Converter microsoft#2161, seed-group rename microsoft#2165).
garak.encoding was already seam/Technique-migrated on main; this layers the
remaining #8380 standardization on top.

Add an EncodingTechnique.DEFAULT aggregate (curated 13-of-17-scheme spread across
base-N, byte-encoding, cipher, and symbolic families) with a get_aggregate_tags()
override, and make it the default technique (was the exhaustive ALL). The niche/
lossy schemes (braille, ecoji, zalgo, ascii_smuggler) stay ALL-only. Follows the
hand-written enum DEFAULT pattern used by scam/web_injection (not the factory
default_technique_names path, which applies only to the core catalog).

Fix atomic-attack name collisions: previously every converter variant and decode
config shared atomic_attack_name=encoding_name, so base64's variants collided and
their result buckets (keyed by atomic_attack_name) collapsed. Each variant now
gets a unique variant_slug -> atomic_attack_name=f"{variant_slug}_{config_suffix}",
with display_group=encoding_name so variants still group per encoding in output.

Trim near-duplicate base64 variants (standard_b64encode is byte-identical to the
default; b2a_base64 only appends a newline). Bump VERSION 1->2.

Make the default run meatier per Rich's microsoft#2058 asks (06-20 'target of 10-20 minutes
... may finish too quickly'; 07-08 'change the dataset size a bit and adding more
defaults'): raise per-dataset max_dataset_size 3->10 and expand DEFAULT from 8 to
13 schemes. ALL remains the exhaustive ceiling (adds the 4 niche/lossy schemes).

@rlundeen2 rlundeen2 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! Only nit comments

display_group=encoding_name,
attack_technique=AttackTechnique(attack=attack),
seed_groups=seed_groups,
memory_labels=self._memory_labels,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rich agrees with this comment but it is copilot generated:

Nit / convention: this reads self._memory_labels (and the method also uses self._objective_target / self._scenario_techniques), but the scenarios guideline is to read runtime inputs from the immutable context snapshot passed into _build_atomic_attacks_async. It's functionally fine here since state is fully resolved by the time this runs, but context.memory_labels is right there — threading context down into _get_converter_attacks/_get_prompt_attacks would match the documented "read from context, not self._*" convention. Non-blocking.

Thread the ScenarioContext into _get_converter_attacks/_get_prompt_attacks
rather than reading self._scenario_techniques / self._objective_target /
self._memory_labels. Atomic-attack builders should read resolved runtime
state from the context per the scenario convention. Behavioral no-op;
tests updated to build the context via _build_scenario_context.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants