# How the self_check.py Script Validates Diagrams in diagram-design

> Learn how self_check.py validates diagram HTML files by detecting unsafe tags, verifying SVG accessibility, and enforcing script integrity for the diagram-design repository.

- Repository: [Cathryn Lavery/diagram-design](https://github.com/cathrynlavery/diagram-design)
- Tags: how-to-guide
- Published: 2026-08-20

---

**The [`self_check.py`](https://github.com/cathrynlavery/diagram-design/blob/main/self_check.py) script validates diagram HTML files by parsing the document with a custom `HTMLParser` subclass, detecting unsafe tags and external references, verifying SVG accessibility compliance, enforcing canonical script integrity, and validating motion-specific contracts when animation markup is present.**

The [`self_check.py`](https://github.com/cathrynlavery/diagram-design/blob/main/self_check.py) script is a zero-dependency validator shipped with the **diagram-design** skill that ensures generated diagram HTML files comply with strict safety and accessibility standards. When an installed agent produces a diagram, running this script against the output verifies adherence to the accessible-SVG contract, single-file safety rules, and structural motion requirements. The validation pipeline mirrors the repository-side gates implemented in [`lint-skin.py`](https://github.com/cathrynlavery/diagram-design/blob/main/lint-skin.py) and [`verify-motion.py`](https://github.com/cathrynlavery/diagram-design/blob/main/verify-motion.py), providing immediate feedback during development.

## Parsing the HTML Document Structure

The validation process begins with a custom `HTMLParser` subclass called `DiagramParser` defined in [`skills/diagram-design/scripts/self_check.py`](https://github.com/cathrynlavery/diagram-design/blob/main/skills/diagram-design/scripts/self_check.py). During initialization at `DiagramParser.__init__` (lines 34-44), the parser prepares collections to store document metadata while traversing the DOM tree.

As the parser walks the document in `handle_starttag` (lines 57-66), it populates several critical data structures:

- `unsafe` – disallowed tags and executable attributes
- `references` – every URL-bearing attribute for external link validation  
- `svgs` – metadata about each top-level `<svg>` element
- `scripts` – body content and attributes of `<script>` blocks
- `styles` – raw CSS from `<style>` tags
- Motion-related sets including `roots`, `items`, `actions`, `controls`, and `statuses`

## Detecting Unsafe Tags and Attributes

The validator enforces a strict security model by flagging potentially dangerous HTML elements. In `handle_starttag`, the script immediately identifies forbidden tags such as `<embed>`, `<object>`, and `<iframe>`.

Additionally, any attribute starting with `on` (e.g., `onclick`, `onload`) is flagged as an event handler that could execute arbitrary JavaScript. These restrictions ensure that diagram files remain safe, single-file documents without external dependencies or executable code paths beyond the approved controller.

## Validating External References

Every reference to external resources undergoes scrutiny in the `reference_error` function (lines 79-97). The parser examines attributes including `src`, `href`, `xlink:href`, `poster`, `srcset`, `action`, and `formaction`.

The validation logic distinguishes three cases:

1. **Local or fragment references** – ignored as safe internal navigation
2. **Executable URLs** – `javascript:` or `data:text/html` schemes are immediately flagged as errors
3. **Remote URLs** – allowed only for the approved Google Fonts `/css2` endpoint via `is_approved_google_fonts_stylesheet`; all other remote resources trigger validation errors

This ensures diagrams remain self-contained while permitting standardized font loading.

## Verifying SVG Accessibility Compliance

The `check_svgs` function (lines 14-45) enforces the accessible-SVG contract on every non-hidden SVG element. The parser extracts top-level `<svg>` elements, ignoring those with `aria-hidden="true"`.

For each accessible SVG, the validator confirms:

- Presence of `role="img"`
- The first child element is `<title>`
- Non-empty text content in both `<title>` and `<desc>` elements
- ID attributes use proper prefixes (not bare "title" or "desc")
- `aria-labelledby` correctly references the title ID followed by the description ID

These checks prevent silent or unlabeled graphics that would fail screen reader accessibility standards.

## Enforcing Single Canonical Script Rules

The `check_scripts` function (lines 47-62) ensures that when a `<script>` tag exists, it represents exactly the approved motion controller. The validation requires:

1. Exactly one script element in the document
2. The script carries only the `data-diagram-controls` attribute (no other attributes permitted)
3. The script body exactly matches the canonical controller stored in [`assets/template-motion.html`](https://github.com/cathrynlavery/diagram-design/blob/main/assets/template-motion.html)

The canonical controller is loaded via `canonical_controller` and normalized for comparison. This prevents agents from shipping mutated or malicious controller code while permitting the specific interactive behaviors defined in the template.

## Validating Motion-Specific Contracts

When the parser detects motion markup (attributes like `data-motion-root`, `data-motion-item`, or `data-motion-action`), the `check_motion` function (lines 69-112) enforces the structural motion contract. This validation only activates when motion elements are present.

**Root and Mode Validation:**
- Exactly one element must have `data-motion-root`
- `data-motion-mode` must be one of `none`, `reveal`, `step`, or `loop`
- `data-step-count` must be a decimal integer within the mode's allowed range (0-8)

**Item Constraints:**
- Maximum of 12 motion items permitted
- Each item's `data-step` must be a decimal integer
- Semantic items require `aria-label` attributes
- Decorative items must carry both `aria-hidden="true"` and `focusable="false"`

**Visibility and Step Requirements:**
- No items with `display:none`, `visibility:hidden`, or `opacity:0` at step 0
- Step values must form a contiguous set from 1 to count
- Maximum two items allowed per step

**Controlled Mode Requirements (for `step` or `reveal` with script):**
- Exactly one control group must exist
- All six actions required: `play`, `pause`, `replay`, `prev`, `next`
- Status element must be a `<div>` with `role="status"`, `aria-live="polite"`, and `aria-atomic="true"`
- CSS must include `prefers-reduced-motion` media query, `@media print` rules, and a `<noscript>` explanation block

## Error Aggregation and Reporting

The `verify` function (lines 53-65) and `main` entry point (lines 68-86) coordinate the final validation output. All collected warnings and errors merge into a single list for reporting.

The script prints results in a machine-parseable format:

```bash
python3 skills/diagram-design/scripts/self_check.py diagram1.html diagram2.html

```

**Typical output:**

```

OK diagram1.html
FAIL diagram2.html
  - <embed> is not allowed in a diagram file
  - svg 1 needs role=img
  - remote stylesheet is not the approved Google Fonts /css2 URL: https://example.com/style.css
  - data-motion-mode must be one of ['none', 'reveal', 'step', 'loop']; got 'invalid'

```

The script exits with status code `1` when any errors are present, enabling integration with CI pipelines as referenced in [`.github/workflows/ci.yml`](https://github.com/cathrynlavery/diagram-design/blob/main/.github/workflows/ci.yml). Clean documents yield exit code `0` with an `OK` prefix.

## Summary

- **[`self_check.py`](https://github.com/cathrynlavery/diagram-design/blob/main/self_check.py)** provides zero-dependency validation of diagram HTML files through a seven-stage pipeline implemented in [`skills/diagram-design/scripts/self_check.py`](https://github.com/cathrynlavery/diagram-design/blob/main/skills/diagram-design/scripts/self_check.py)
- **Security checks** in `handle_starttag` block unsafe tags like `<iframe>` and event handlers while `reference_error` restricts external URLs to approved Google Fonts endpoints
- **Accessibility validation** via `check_svgs` enforces ARIA labeling, proper DOM order, and semantic requirements for non-hidden SVG elements
- **Script integrity** enforced by `check_scripts` requires the canonical motion controller from [`assets/template-motion.html`](https://github.com/cathrynlavery/diagram-design/blob/main/assets/template-motion.html) to appear unchanged when present
- **Motion contracts** validated by `check_motion` ensure proper step sequencing, item budgets, control groups, and CSS fallbacks for animated diagrams
- **CI-ready reporting** through the `verify` function provides non-zero exit codes and detailed error messages for automated testing workflows

## Frequently Asked Questions

### What specific security threats does self_check.py prevent?

The script prevents cross-site scripting (XSS) vectors by blocking `<embed>`, `<object>`, and `<iframe>` tags, rejecting event handler attributes (any attribute starting with `on`), and prohibiting executable URL schemes like `javascript:` or `data:text/html` in the `reference_error` function. These checks ensure diagram files cannot execute arbitrary code or load unauthorized external content.

### Why does the validator reject most remote URLs but allow Google Fonts?

The `is_approved_google_fonts_stylesheet` function explicitly whitelists only the Google Fonts `/css2` endpoint to support typography while maintaining the single-file safety contract. All other remote references are flagged because they introduce external dependencies that could break offline viewing, compromise privacy, or serve as attack vectors if the third-party domain is compromised.

### How does the script detect and validate animated diagrams?

The parser watches for motion-specific data attributes during the HTML traversal. When `data-motion-root`, `data-motion-item`, or related attributes appear, the `check_motion` function activates to validate the structural motion contract, verifying step counts, item budgets, ARIA labeling for semantic items, and the presence of required control groups and CSS fallbacks for reduced motion and print media.

### Can self_check.py be integrated into automated testing pipelines?

Yes. The script is designed for CI integration through its exit code behavior and machine-parseable output format. When `verify` detects any errors, the `main` function exits with status code `1`, failing the build; successful validation returns `0`. This enables use in GitHub Actions workflows or other automated gates to prevent non-compliant diagrams from reaching production.