~/blogfenced-padding-byte-budget.md
cchu@nycu:~/blog$ cat fenced-padding-byte-budget.md
2026.10.286 min[agents][static-analysis][unicode][testing]

A Code Fence With 2,049 Bytes of Padding

A SkillSpector detector replay checks Unicode padding, Markdown fence exemptions, and the difference between byte budgets, character spans, and bounded diagnostic summaries.

A Markdown code fence containing 682 ideographic spaces produced no padding record. Increasing the run to 683 spaces produced a block record: the same blank-looking region now occupied 2,049 UTF-8 bytes, just above the detector's 2,048-byte budget.

The fence still did its job. It exempted the horizontal-padding check, which would otherwise report that run. It did not exempt the independent block check. That local treatment of context is the useful detail in SkillSpector's padding detector: an exception for normal code formatting does not turn into a blanket exception for the whole document.

I examined revision ea2e457d571d422ea274c4c12159c6e1efc3f8db and ran the unchanged detector module with Python 3.12.14, using Unicode database 15.0.0, on Linux. All text fixtures were synthetic. No skill was executed, no model was called, and the full scanner was not installed or run.

Markdown fences skip the horizontal detector. A separate block detector still checks the UTF-8 byte budget. The tested 683-character span occupied 2,049 bytes; its diagnostic summary sampled only 200 characters.

One Padding Rule, Several Signals

SkillSpector analyzes agent skills using static analyzers and optional semantic analysis. Its repository includes input handling, artifact inventory, resource bounds, report generation, and tests across individual analyzers. The package configuration declares Python >=3.12,<3.15 and package version 2.12.0. I used the source commit as the reproduction identity rather than inferring an installed CLI version.

My subject is the standalone whitespace_padding.py module. It imports only the standard library and returns structured PaddingRun records. The file-body prompt-injection analyzer and the tool-description analyzer consume this shared detector.

The module combines several kinds of evidence:

SignalThreshold in the examined sourceRelevant unit
Vertical gapAt least 20 consecutive blank logical linesLines
Horizontal runAt least 80 consecutive padding charactersCharacters
Contiguous blockMore than 2,048 padding bytesUTF-8 bytes for admission
Whole-file padding ratioMore than 90%, in files over 4,096 bytesBytes
Repeated non-padding characterAt least 512 repetitionsCharacters

Those units are deliberately different. The byte threshold counts encoded size; the character threshold counts code points. Crossing either threshold produces a signal, rather than limiting how much input this helper reads. Consumers also need spans suitable for slicing Python strings, which use character offsets.

Whitespace Classification Extends Beyond isspace()

The detector recognizes Unicode separator categories, a listed zero-width family, ASCII padding controls, and a small additional character set. It does not classify every format character or blank-looking glyph as whitespace.

The replay confirmed:

CharacterPython isspace()Detector padding classification
U+200B ZERO WIDTH SPACEFalseTrue
U+180E MONGOLIAN VOWEL SEPARATORFalseTrue
U+00A0 NO-BREAK SPACETrueTrue
U+2800 BRAILLE PATTERN BLANKFalseFalse

A run of 79 U+200B characters between two letters returned no record. At 80 characters it returned a horizontal record spanning 240 bytes. The U+180E fixture behaved the same way.

U+2800 took another path. Eighty repeated characters returned no record; 512 triggered the generic repetition detector. That matters when interpreting coverage: a character outside the padding classifier may still be covered by another structural signal, with a different threshold. Neither result establishes whether the surrounding instruction is harmful.

Fences Exempt One Detector

I constructed the fenced fixture with escaped Unicode so its contents remain visible in the reproduction code:

content = "```\nA" + "\u3000" * 683 + "B\n```"
runs = detector.detect_whitespace_padding(content, file_type="markdown")

The result contained one block record. Its character span was [5, 688), its length was 683, and slicing that span and encoding it as UTF-8 yielded 2049 bytes. The 682-character version occupied 2,046 padding bytes and returned no record.

A shorter fence containing 80 ASCII spaces also returned no record in Markdown mode. Passing that exact text with file_type="other" returned a horizontal record. The classification supplied by the caller therefore affects this exemption; the helper does not infer a complete file format from the text.

Outside a fence, the 683-character U+3000 run returned a horizontal record, with the overlapping block result suppressed. The detector prefers the primary horizontal or vertical signal over redundant block and ratio records. A single region does not automatically become several independent findings.

The block record's summary was U+3000 x200. That describes its bounded sample, not the total run. The module samples the first 200 characters for this summary while retaining the full span. Reading only the display text would understate the fixture's actual length.

The Executed Replay

The probe verifies the detector's SHA-256 digest before loading the original module. It builds 13 in-memory fixtures, checks record kinds and lengths, and validates span arithmetic. Obtain src/skillspector/nodes/analyzers/whitespace_padding.py from the pinned revision and point the probe at that file.

From the portfolio checkout, I ran:

python public/blog/fenced-padding-byte-budget/padding-probe.py \
  /tmp/skillspector-note28/whitespace_padding.py

The command exited zero. Selected stdout:

zero_width_79: no runs
zero_width_80: horizontal; chars=80; span_bytes=240; line=1
fenced_ascii_80: no runs
same_text_other_type: horizontal; chars=80; span_bytes=80; line=2
fenced_682: no runs
fenced_683: block; chars=683; span_bytes=2049; line=2
fenced_683 summary: U+3000 x200 (bounded sample)
unfenced_683: horizontal; chars=683; span_bytes=2049; line=1
unicode_vertical_20: vertical; blank_lines=20; span_bytes=60; line=2
PASS: 13 synthetic detector cases; no full-scanner or maliciousness verdict

The Unicode-line fixture used U+2028 separators. It returned a vertical gap starting on logical line 2, containing 20 blank lines and followed by content. Its span occupied 60 bytes. The detector's logical-line splitter handles more than ASCII newline characters.

The upstream tests cover these signal families, Markdown contexts, and additional regressions. I inspected them; the output above comes from the separate probe, not a pytest suite run.

A Padding Record Is a Signal

Two further fixtures checked the decoding guard. One embedded U+FFFD replacement character did not suppress an 80-space horizontal run. A fixture with 100 replacement characters and that same run returned no records. The source deliberately exits this detector when replacement characters exceed 30% of the input's character count, treating it as binary-like content. An empty result can therefore reflect a guard, not the absence of unusual structure.

The file-body analyzer turns these records into P9 findings. In the examined source, block and ratio records receive LOW severity and a 0.4 confidence value; horizontal records receive MEDIUM severity. Those are configured assessment values, not probabilities measured by this replay. The caller also has generated/vendor-path exclusions. I did not execute that integration or the final report pipeline.

The project's resource-bound documentation separately describes how incomplete analysis is reported. Detector thresholds, input coverage, and malicious intent remain separate questions. The tested fence exception is narrow and the byte budget still applies, but a returned padding record alone proves neither an attack nor a complete inspection of the skill.