MCP server SVG SMIL timing model security

The SMIL animation timing model supports more than the simple begin="id.event" syntax that most detection rules target. The full SMIL timing specification includes event-offset syntax (begin="id.click+0.001s" — fires 1ms after the click), negative event-offset (begin="id.click-0.5s" — fires 500ms before the click), continuous oscillation via repeatCount="indefinite" with short durations, and syncbase timing (begin="anim1.begin" — fires when another animation begins). This page covers four SMIL timing model attack patterns that exploit these features to hide consent erasure attacks from detection rules that check only the base event identifier without the offset, duration, or synchronization chain.

Finding SA-SMILT-001: begin event-offset +0.001s — consent text hidden 1ms after the click event fires

CriticalThe SMIL begin attribute supports the event-offset syntax id.event+offset where offset is a clock value in seconds. begin="agreeBtn.click+0.001s" fires the animation 1ms after the click event. An auditor scanning for begin values containing event identifiers on consent-affecting animations may check for begin="agreeBtn.click" (exact match) but not begin="agreeBtn.click+0.001s" (event with offset). The semantic difference: with no offset (begin="agreeBtn.click"), the animation fires in the same frame as the click event, and the attribute change may be applied before or concurrently with the click handler. With +0.001s offset, the animation fires in the next frame, after the click is recorded. Combined with fill="freeze", the consent text is hidden permanently after the click is recorded — meaning the text is visible at the click moment, but is immediately erased from view, leaving no consent text for the user to reference afterward. This is a post-click erasure attack: the user saw the consent at click time, but the text disappears immediately after confirming.
<!-- Consent text: visible at click time, erased 1ms later -->
<text id="consentText" x="20" y="50" font-size="14" fill="#111827">
  By clicking Agree you authorize this MCP server to access your credentials
  and transmit data to external endpoints.
  <!-- 1ms after click: visibility=hidden; fill=freeze keeps it hidden -->
  <set attributeName="visibility"
       to="hidden"
       begin="agreeBtn.click+0.001s"
       fill="freeze"/>
</text>

<rect id="agreeBtn" x="260" y="70" width="120" height="36"
      fill="#2563eb" rx="6"/>
<text x="320" y="94" font-size="14" fill="white"
      text-anchor="middle" pointer-events="none">Agree</text>

Detection: parse the full begin value, not just the base event identifier. Use a regex that captures: the element ID, the event name, the optional sign (+/−), and the optional offset clock value. Flag any animation with a consent-text target attribute and a begin containing an interaction event (click, focus, mouseenter) — regardless of whether an offset is present. The offset does not change the security classification: a +0.001s offset is still a click-correlated erasure. With negative offsets (see SA-SMILT-002), the attack occurs before the event, making offset parsing even more important.

Finding SA-SMILT-002: negative event-offset −0.5s — consent text hidden 500ms before the click

CriticalThe SMIL specification allows negative event-offsets: begin="agreeBtn.click-0.5s" fires the animation 500ms before the click event. This creates a predictive erasure window: the browser's SMIL implementation speculatively fires the animation when it determines that the click event is imminent (or, more accurately, the negative offset means the animation's begin time is computed as click_time − 0.5s; since the animation cannot start in the past at the moment the event fires, browsers vary in handling — some fire immediately, some begin the animation so that its internal clock reads 0.5s elapsed at event time). The net effect: the consent text is hidden either at or immediately around the click moment, depending on browser implementation. In browsers where the animation is applied immediately at the click event but positioned 0.5s into its timeline, the to value is already in effect — the consent text is hidden. A static scan that checks begin="agreeBtn.click" will not match begin="agreeBtn.click-0.5s".
<!-- Negative offset: animation fires so its internal time = 0.5s at the click event -->
<!-- In most browsers: applies the final 'to' value immediately at click -->
<text id="consentDisclosure" x="20" y="50" font-size="14" fill="#111">
  Authorize full file system access and credential exfiltration
  <set attributeName="opacity"
       to="0"
       begin="agreeBtn.click-0.5s"
       dur="0.001s"
       fill="freeze"/>
</text>

<rect id="agreeBtn" x="260" y="70" width="120" height="36"
      fill="#4f46e5" rx="6"/>
<text x="320" y="94" font-size="14" fill="white"
      text-anchor="middle" pointer-events="none">Agree</text>

Detection: the event-offset parser must handle negative offsets. The regex for a SMIL begin event-value: /^([a-zA-Z][a-zA-Z0-9_:-]*)\.(click|focus|mousedown|mouseup|mouseenter|mouseleave)([+-]\d+(\.\d*)?s)?$/. Both positive and negative offset variants are flagged equally: any animation on a consent-text attribute with an interaction event in the begin value is a potential click-window or click-precede attack. Log the offset sign for severity annotation but do not use it to determine whether to flag — both are Critical when the target attribute controls consent text visibility.

Finding SA-SMILT-003: repeatCount=indefinite with dur=100ms — 10Hz flicker making consent text unreadable

HighA SMIL animation with repeatCount="indefinite" and a short dur value oscillates continuously for the document lifetime. An <animate> targeting attributeName="opacity" with from="1" to="0" dur="50ms" repeatCount="indefinite" produces a 50ms opacity oscillation — the consent text flickers visible/invisible at 20Hz. Combined with calcMode="discrete" and equal keyTimes, the text alternates between fully opaque and fully transparent at the specified frequency. At 10Hz (dur=100ms), the human visual system can perceive individual flashes but cannot read text during the invisible phase. Over a 2-second reading window, the text is visible for approximately 1 second total — broken into 10 one-tenth-second fragments separated by invisible phases. The disclosure is technically rendered at some moments but is not readable as connected prose. The flicker also constitutes an accessibility violation (WCAG 2.3.1: no content flashing more than 3 times per second — the attack explicitly targets the range between 3 and 50Hz where legal photosensitivity thresholds are ambiguous).
<!-- 10Hz flicker: consent text visible/invisible alternating at 100ms intervals -->
<text x="20" y="50" font-size="14" fill="#111827">
  By clicking Agree you authorize credential access and network transmission
  <animate attributeName="opacity"
           from="1" to="0"
           dur="100ms"
           calcMode="discrete"
           repeatCount="indefinite"/>
</text>

<!-- Agree button: not affected by flicker -- user can click during any phase -->
<rect id="agreeBtn" x="260" y="70" width="120" height="36"
      fill="#2563eb" rx="6"/>
<text x="320" y="94" font-size="14" fill="white"
      text-anchor="middle" pointer-events="none">Agree</text>

Detection: flag any animation on consent-text visibility attributes (opacity, visibility, display) with repeatCount="indefinite" or repeatCount ≥ 3 and dur ≤ 500ms. The threshold (dur ≤ 500ms) covers oscillation rates ≥ 2Hz, well above the rate at which most users can read 14px text during the visible phase. Classify as High rather than Critical because the consent text is technically visible for some fraction of the time; it is not permanently hidden. Flag Critical if calcMode="discrete" produces a 50% duty cycle at rates above 3Hz, or if the animation is triggered by an interaction event (not document load) and would activate during the consent interaction window.

Finding SA-SMILT-004: syncbase timing — anim2 begins when anim1 begins, chaining through animation sync dependency

HighThe SMIL syncbase timing syntax allows animations to begin relative to another animation's begin or end time: begin="anim1.begin+0" fires animation 2 at the exact moment animation 1 fires. This creates a chain of animations triggered by a single interaction event — without each animation directly referencing the interaction event. Pattern: (1) anim1 references begin="agreeBtn.click" and performs a harmless transformation (e.g., changes a decorative element's color); (2) anim2 references begin="anim1.begin+0" and hides the consent text; (3) anim3 references begin="anim2.begin+0" and performs another harmless transformation. An auditor that searches consent-element animations for interaction events in the begin attribute will find: anim2 has begin="anim1.begin+0" — no interaction event. The interaction event is in anim1, which targets a non-consent element. Detection requires building the complete syncbase dependency graph and tracing trigger propagation from interaction events through all syncbase chains.
<!-- anim1: interaction trigger on a non-consent element (decorative color change) -->
<animate id="anim1"
         xlink:href="#decorativeCircle"
         attributeName="fill"
         to="#2563eb"
         begin="agreeBtn.click"
         dur="200ms"
         fill="freeze"/>

<!-- anim2: syncbase on anim1 — fires when anim1 fires; hides consent text -->
<!-- No direct reference to agreeBtn.click — only "anim1.begin+0" -->
<set id="anim2"
     xlink:href="#consentText"
     attributeName="visibility"
     to="hidden"
     begin="anim1.begin+0"
     fill="freeze"/>

<!-- anim3: syncbase on anim2 — further harmless transformation -->
<animate id="anim3"
         xlink:href="#confirmBadge"
         attributeName="opacity"
         to="1"
         begin="anim2.begin+0"
         dur="150ms"
         fill="freeze"/>

<!-- Target elements -->
<circle id="decorativeCircle" cx="350" cy="30" r="8" fill="#e5e7eb"/>
<text id="consentText" x="20" y="50" font-size="14" fill="#111827">
  You authorize this MCP server to access your credentials and filesystem.
</text>
<rect id="agreeBtn" x="260" y="70" width="120" height="36"
      fill="#2563eb" rx="6"/>

Detection: build the complete syncbase dependency graph for all animations in the document. For each animation with an interaction-event begin value (the "source" nodes in the graph), follow all syncbase edges (begin="animX.begin" or begin="animX.end") to find all transitively triggered animations. Check whether any transitively triggered animation targets a consent text attribute (visibility, opacity, display, fill, transform, or any attribute whose hide value removes the element from view). If yes, flag the consent-element animation at the severity level of its target attribute transformation — even though the animation's own begin value contains no direct interaction-event reference. Syncbase chains can be arbitrarily long; traverse the full graph.

Detection algorithm: SMIL timing model consent attack patterns

Step Action Catches
1 Parse all animation begin values using a full SMIL timing parser. Support: absolute clock values, relative clock values, indefinite, event (id.event), event-offset (id.event±offset), syncbase (animId.begin/end±offset), wallclock (rare). Extract the base event type and offset for each Prerequisite for SA-SMILT-001 and SA-SMILT-002
2 Flag all animations with interaction events in begin values (click, focus, mouseenter, mousedown) — regardless of offset sign or magnitude. Classify the target attribute as consent-affecting or not. Flag Critical if consent-affecting, interaction-event-triggered, with a hide-value to attribute and fill="freeze" SA-SMILT-001: post-click erasure (+offset); SA-SMILT-002: pre-click erasure (−offset)
3 Flag all animations with repeatCount="indefinite" or high repeatCount, short dur (≤ 500ms), and consent-affecting target attributes. Compute effective oscillation frequency. Flag High if frequency ≥ 2Hz on a consent visibility attribute SA-SMILT-003: indefinite repeat flicker
4 Build the syncbase dependency graph. Starting from all interaction-event-triggered animations, follow syncbase edges transitively. For each transitively triggered animation targeting a consent element, flag at the appropriate severity with a note identifying the syncbase chain depth and the root trigger event SA-SMILT-004: syncbase chaining through indirect trigger

SkillAudit parses the complete SMIL timing grammar — event-offset, negative-offset, repeatCount oscillation, and syncbase chains — and flags interaction-triggered consent erasure at any chain depth. Run a free audit on your MCP server's GitHub URL to check every animation timing value in your consent UI.