Skip to content

Verify woff - #560

Open
seuninfoo-svg wants to merge 2 commits into
Sub-Rosa-Issue:mainfrom
seuninfoo-svg:verify_WOFF
Open

seuninfoo-svg wants to merge 2 commits into
Sub-Rosa-Issue:mainfrom
seuninfoo-svg:verify_WOFF

Conversation

@seuninfoo-svg

Copy link
Copy Markdown

PR: Add WOFF/WOFF2 Magic Byte Verification for Font Assets

Summary

Implements a static repository check that validates committed .woff and .woff2 font files by verifying their magic bytes. This prevents malicious files disguised as fonts (e.g., JavaScript payloads with .woff extension) from being committed to the repository.

Motivation

Files named .woff or .woff2 can be used to conceal executable content (JavaScript, shell scripts, etc.). Real font files have recognizable binary signatures (magic bytes) that can be checked without executing or parsing the full file:

Format Extension Magic Bytes (hex) ASCII
WOFF 2.0 .woff2 77 4F 46 32 wOF2
WOFF 1.0 .woff 77 4F 46 46 wOFF

This checker reads only the first 4 bytes of each font file—never executing or fully parsing it—and rejects files with mismatched magic bytes.

Changes

New Files

File Purpose
scripts/check-font-magic.mjs Main checker script (reads 4 bytes only via fs.openSync/readSync)
scripts/check-font-magic.test.mjs Test suite (12 tests)

Modified Files

File Change
package.json Added font:check, font:test scripts
scripts/check-logging.mjs Added exception for new checker script (uses console for CLI output)

Acceptance Criteria

All criteria from issue #478 are satisfied:

# Criterion Implementation
1 .woff2 files must begin with wOF2 magic bytes ✅ MAGIC_WOFF2 = [0x77, 0x4F, 0x46, 0x32]
2 .woff files must begin with wOFF magic bytes ✅ MAGIC_WOFF = [0x77, 0x4F, 0x46, 0x46]
3 Tests cover one valid and one text-disguised-as-font fixture ✅ 12 tests: valid woff/woff2, fake woff/woff2, tiny files, subdirs
4 CI reports only the invalid path and exits non-zero ✅ Output format: path/file.woff2: invalid woff2 magic bytes... + process.exitCode=1
5 Add a root package script for local use ✅ pnpm font:check / pnpm font:test
6 Checker reads bytes only, never executes a file ✅ Uses fs.openSync + fs.readSync with 4-byte buffer

Usage

Manual Check

pnpm font:check
# ✅ All WOFF/WOFF2 assets have valid magic bytes.

# Or with custom path
node scripts/check-font-magic.mjs /path/to/check

CI Integration

# .github/workflows/ci.yml
- name: Verify font magic bytes
  run: pnpm font:check

Run Tests

pnpm font:test
# 12 tests passing

Test Coverage

pnpm font:test
# 12 tests passing:
#  - valid woff2 passes
#  - valid woff passes
#  - text file as woff2 fails
#  - text file as woff fails
#  - too small file fails
#  - valid files in subdirectory pass
#  - invalid file in subdirectory fails with relative path
#  - ignores node_modules directory
#  - ignores .git directory
#  - formatErrors success
#  - formatErrors failure
#  - repository has no committed font assets

Test Fixtures (created in temp dirs, not committed)

Fixture Content Expected
valid.woff2 77 4F 46 32 00 01 00 00 ✅ Pass
valid.woff 77 4F 46 46 00 01 00 00 ✅ Pass
fake.woff2 "this is not a font" ❌ Fail: invalid magic
fake.woff "also not a font" ❌ Fail: invalid magic
tiny.woff2 "wof" (3 bytes) ❌ Fail: too small
subdir/nested.woff2 Valid magic ✅ Pass
subdir/bad.woff2 "not a font" ❌ Fail: reports subdir/bad.woff2
node_modules/pkg/x.woff2 Any content ✅ Ignored
.git/objects/x.woff Any content ✅ Ignored

Example Output

Clean Repository

$ pnpm font:check
✅ All WOFF/WOFF2 assets have valid magic bytes.

Violations Found

$ node scripts/check-font-magic.mjs ./test-fonts
❌ Invalid font assets:
  test-fonts/bad.woff2: invalid woff2 magic bytes (expected wOF2, got 66 61 6B 65)
  test-fonts/fake.woff: invalid woff magic bytes (expected wOFF, got 61 6C 73 6F)
  test-fonts/tiny.woff2: file too small (3 bytes) to contain magic bytes

Exited with code 1

Design Decisions

  1. Reads exactly 4 bytes - Uses fs.openSync + fs.readSync with a 4-byte buffer; never reads the full file
  2. No execution/parsing - Pure byte comparison; safe for CI and air-gapped environments
  3. Ignores build/dependency directories - Skips node_modules, .git, dist, target, artifacts, tmp-bindings
  4. Relative paths in output - Reports paths relative to repo root for easy triage
  5. Graceful error handling - Unreadable files produce clear error messages without crashing
  6. Zero dependencies - Uses only Node.js built-in modules (fs, path, url)

Files Checked

The scanner recursively checks these directories:

  • packages/
  • services/
  • apps/
  • scripts/
  • .github/

And ignores:

  • node_modules/
  • dist/
  • target/
  • .git/
  • artifacts/
  • tmp-bindings/

Related

close #478

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.

security(ci): verify WOFF and WOFF2 magic bytes for committed font assets

1 participant