Skip to content

Render: a backtick in a code-spanned field breaks out of the code span #35

Description

@jbeda

Found while spot-checking the adversarial fixture run during review of #34. Pre-existing on main, not a regression from that PR — filing separately so it doesn't creep into its scope, same as #29 was handled for #28.

What happens

internal/render/markdown wraps several user-controlled strings in single-backtick code spans. A backtick inside the value closes the span early, and whatever follows lands in the document as live Markdown/HTML rather than as literal text.

Affected fields, all confirmed:

  • entities.*.attributes[].name
  • entities.*.attributes[].type
  • entities.*.actions[] (the name)
  • entities.*.relationships[].role

Entity, enum and glossary keys are safe — the schema constrains them to ^[A-Z][A-Za-z0-9]+$, which excludes backticks. The fields above are unconstrained strings.

Repro

kind: DomainModel
version: v1
entities:
  Ticket:
    definition: A parking ticket.
    attributes:
      - name: "a ` <img src=q onerror=alert(1)> `"
        type: string
$ modelith lint bt.modelith.yaml
0 error(s), 0 warning(s)

$ modelith render bt.modelith.yaml --stdout
| `a ` <img src=q onerror=alert(1)> `` | string |  |

The code span closes at the second backtick and the <img> is emitted raw. Lint passes at every severity.

Why it's worth fixing

Correctness first. A model that legitimately documents shell or Markdown syntax — an attribute type containing a backtick — renders wrong. The renderer does not faithfully represent legal input, independent of any attacker.

Mild review asymmetry. Someone with commit access could already edit the generated .md directly, so this is not a privilege boundary. What it changes is where the payload appears in a diff: a weird-looking name in a YAML file, plus a regenerated golden that reviewers habitually skim, rather than obvious HTML in a reviewed .md.

It becomes a trust boundary when vendoring lands. ADR-0010's slice 2 vendors models from repositories this one does not control and renders them into local docs. At that point the author of the string needs commit access to their repo, not yours, and your CI renders it into your site. That is the deadline: this should be fixed before vendored third-party models ship.

Not part of this issue

  • Prose fields pass Markdown through. definition, description and similar emit raw HTML with no backtick needed. That appears intentional — they are prose in a Markdown document and an author may want emphasis — so it is not filed here. Worth confirming it is deliberate.
  • The same fixture re-demonstrates Render: sanitize lets Mermaid directives and angle brackets through from role text #29. With a role, the backtick is stripped by mermaid's sanitize but <b> passes into the diagram block untouched. Sibling problem, different renderer, already tracked.

Note for whoever picks this up

The branch for #34 added a codeSpan helper that fences past backtick runs, plus linkTarget percent-encoding, to fix the same class in import paths. Applying that helper at these call sites is likely most of the fix. Expect goldens to churn.

🤖 Filed by Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions