Repository navigation
Conversation
|
Review of 1. Fix the phrase-exemption span calculation before merging phrase support.
A static walkthrough gives this synthetic counterexample: The two matches occupy source spans 2. Fix coverage comparison before using it as a no-regression gate.
Please evaluate both versions against the same frozen probe corpus, retain stable opaque probe identities/corpus metadata, and fail or report “incomparable” if probe identities differ. Removing/moving an entry must not silently remove its old probes from the safety comparison. Alternatively, split this tool out rather than treating its current output as a reliable gate. Follow-up, not a new regression: fullwidth/accent hardening still misses terms present only in I4/downstream backport: the production module now imports The phrase mechanism and audit tools can also be split out so the core Unicode fix can proceed independently. These findings are from static code/dependency analysis; I did not execute the project tests or a downstream run. |
is_safe ran to_ascii before any check, and to_ascii deletes every non-ASCII
run. A fullwidth, accented or lookalike spelling of a blocklist entry
(fullwidth "snow", "snów", "sn<U+043E>w" with a Cyrillic o) therefore reached
the matcher with letters missing and passed, while a reader still saw the
entry.
The passes now run first on the prompt as written, where censor_prompt folds
code points (NFKC, combining marks removed) before matching. The to_ascii
pass is kept after it, because some separators ("boston·dynamics") are only
turned into a space by to_ascii. On non-ASCII prompts the passes also run on
two more readings: invisible characters deleted rather than read as a space,
and letters from other scripts that are drawn like Latin ones folded to
Latin, using a vendored table generated from Unicode confusables.txt. Every
pass that ran before still runs, so a prompt blocked before is still blocked.
The list setup that __init__ did inline is moved into load_lists, shared with
a new from_lists constructor, so the tests run the real entry point, is_safe,
over the real bookkeeping instead of a copy of it.
This is the Unicode part of the earlier combined change. The whitelist
phrases and the list-maintenance tools move to their own pull requests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review on NVIDIA#306: censor_prompt folds fullwidth and accented letters itself, but the lemmatized pass and the exact-match check only see the prompt after to_ascii, which deletes those letters. alternate_spellings left out the plain folded reading, so an entry that is only on the exact-match list still passed when spelled in fullwidth or with an accent. alternate_spellings now includes the code-point-folded reading, so the whole chain of passes, exact-match included, runs on it. Tests cover fullwidth, precomposed and decomposed accents, lookalike and invisible spellings of an exact-match-only entry, and ordinary text that must stay allowed. Without the change, the fullwidth and both accent cases fail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5119bef to
9a7f792
Compare
|
@lfengad Thank you very much for such a careful review. I reproduced every point with the real matcher and the shipped Cosmos-Guardrail1 lists, and all of them hold. I really appreciate you taking the time to trace them through the code. I've taken your suggestion and split the PR, so the core Unicode fix can proceed on its own:
Point by point: 1. Phrase exemption spans: fixed in #313. You were right. With your example, the old head allows
Matches are now found by trying each span against the matcher itself, with a word after it. A prompt clears only if every match lies inside a phrase, and it stays blocked if a censored prompt yields no locatable match. Your example is now a test, alongside parametrized multiple-match, boundary-crossing and one-letter cases through 2. 3. Exact-match follow-up: fixed here, in 9a7f792. The folded reading now goes through the whole chain of passes. On the shipped exact-match list (1414 entries), the number that still pass when spelled in fullwidth goes from 1394 on 4. Backport: noted in the description. On the shipped lists, nothing that Could you please take another look at #306 when you have a moment? There is no rush on the two stacked PRs; they can wait until this one is settled. Thank you again for the thorough review. |
Blocklist hardening against fullwidth, accented, lookalike and invisible spellings. Following @lfengad's review, this PR is now only the Unicode fix. The whitelist phrases and the list-maintenance tools have moved to their own pull requests, stacked on this one:
coverage.pyandhygiene.py, with stable probe ids and an "incomparable" resultRebased onto
mainat 0169db5 (2026-10-26). No commit since the previous base touches the blocklist.The problem, on today's
mainis_saferunsto_asciibefore any check, andto_asciideletes every non-ASCII character. A disguised spelling of an entry therefore reaches the matcher with letters missing and passes, while a reader still sees the entry.snow white)maina snow white poster(fullwidth)a snöw white poster(accent)a sn<U+043E>w white poster(Cyrillic o)a sn<U+200B>ow white poster(zero-width inside a word)a boston·dynamics robot(middle dot, entryboston dynamics)The exact-match list had the same gap, and that is fixed here too (see below).
What changes
is_safeonly adds passes. It never moves or removes one, so nothingmainblocks can pass.A new first pass sees the prompt as written.
censor_promptfolds code points there (NFKC, combining marks removed) before matching. The existingto_asciipass, the lemmatized pass and the exact-match check still run unchanged after it.Other readings of a non-ASCII prompt go through the whole chain of passes. These are:
An ASCII prompt never takes this path, and a test pins that.
lookalikes.pyis a vendored table of 728 code points, generated from Unicodeconfusables.txt18.0.0. A word is folded only if it contains a Latin letter or is made entirely of lookalikes, which keeps ordinary Russian, Greek and Ukrainian text from being rewritten.One list-loading path.
__init__and the newfrom_listsboth go throughload_lists, so the tests run the realis_safeover the real bookkeeping.Review follow-up: exact-match entries (commit 9a7f792)
Thank you for catching this. The exact-match check ran only after
to_ascii, andalternate_spellings()left out the plain folded reading. So with custom["deskin"]and exact-match["chromebook"], the fullwidth and accented spellings ofchromebookpassed. The folded reading is now included, so the whole chain, exact-match included, runs on it. New tests cover fullwidth, precomposed and decomposed accents, lookalike and invisible spellings, and ordinary text that must stay allowed. Without the change, the fullwidth and both accent cases fail.Measured with the shipped Cosmos-Guardrail1 lists
All prompts go through
is_safewith the production lists and the real WordNet lemmatizer. Each entry gets six spellings ina photo of {entry} in the scene. The table counts entries that still pass:mainmainmainblocks passes with this PR. This was checked prompt by prompt.main. It contains punctuation, so the matcher can never produce it. That is a list-data issue for the owners.mainand on this PR: 0 newly blocked, 0 newly allowed.Tests
pytest cosmos_framework/auxiliary/guardrail/blocklist/: 72 passed.ruff checkandruff format --checkare clean, andpre-commiton the changed files passes.Backport note (I4 / downstream)
blocklist.pynow imports the newcosmos_framework/auxiliary/guardrail/blocklist/lookalikes.py. Please carry that file, and adapt its import, together with the other mapped blocklist files. An existing file manifest will not include it.🤖 Generated with Claude Code