Agent skill

Security Triage

by symfony in symfony/ux

Triage a security finding in a Symfony UX package into a disposition: a private CVE (coordinated disclosure through the Symfony security process), a public hardening PR (fix in the open, no CVE), or…

MITAuto-check passedSecurity

Install Security Triage

skills CLI
$ npx skills add symfony/ux --skill security-triage -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install symfony/ux security-triage --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/symfony/ux.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.agents/skills/security-triage .claude/skills/security-triage && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
security-triage
GitHub stars
1.1k
Token cost
~3.2k tokens
SKILL.md length
1,746 words
Files
1
Skills in repo
5
Repo updated
First seen
Licence
MIT

At a glance

Triage a security finding in a Symfony UX package into a disposition: a private CVE (coordinated disclosure through the Symfony security process), a public hardening PR (fix in the open, no CVE), or…

  • Works in 4 steps: Establish the facts → Disposition decision tree → Severity and affected branches → …
  • Deciding whether a report
  • SKILL.md covers Progress checklist, Confirmation rule, Step 0 — Establish the facts and Step 1 — Disposition decision…, plus 5 more sections
  • Calls pnpm, git and composer

What it does

Security Triage is an agent skill from symfony/ux. Triage a security finding in a Symfony UX package into a disposition: a private CVE (coordinated disclosure through the Symfony security process), a public hardening PR (fix in the open, no CVE), or not-a-security-issue (reply to the reporter). Assigns severity and the affected maintained branches, and routes to the next step. Use when deciding whether a report or a discovered weakness needs a CVE, how it should be disclosed, or whether it is a security issue at all.

Its SKILL.md is about 3.2k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.

It sits in Security, covering Vulnerability scanning. It works with Symfony. The repository describes itself as: Symfony UX initiative: a JavaScript ecosystem for Symfony. The licence is MIT.

When your agent uses it

  • Deciding whether a report
  • A discovered weakness needs a CVE
  • How it should be disclosed
  • Whether it is a security issue at all

Example prompts

  • “/security-triage”

Workflow steps

4 steps, taken from the step headings in SKILL.md.

  1. Establish the facts
  2. Disposition decision tree
  3. Severity and affected branches
  4. Route

What it can do on your machine

Read from SKILL.md and the folder at commit 5e9614c. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    Shell commands in SKILL.md call:

    • pnpm
    • git
    • composer
    • php

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    Links to these hosts (documentation or services it may open):

    • symfony.com

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Security Triage loads about 3.2k tokens when it runs. Until then it costs about 122 tokens; SKILL.md has 1,746 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~122
When it runs · the whole SKILL.md, loaded when a task matches
~3.2k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from symfony/ux at commit 5e9614c, republished under its MIT licence (© symfony). 1,746 words, ~3,239 tokens.

Download SKILL.mdSave it as .claude/skills/security-triage/SKILL.md (or your agent's skills folder).
name
security-triage
description
Triage a security finding in a Symfony UX package into a disposition: a private CVE (coordinated disclosure through the Symfony security process), a public hardening PR (fix in the open, no CVE), or not-a-security-issue (reply to the reporter). Assigns severity and the affected maintained branches, and routes to the next step. Use when deciding whether a report or a discovered weakness needs a CVE, how it should be disclosed, or whether it is a security issue at all.

Symfony UX Security Triage

Decides how a finding is handled, not whether the code is wrong. It complements symfony-security-review (which finds missing hardening) by making the disclosure call on a report.

Symfony UX follows the Symfony security process (https://symfony.com/security): reports go privately to security@symfony.com, the security team works on the fix in a private Git repository, and the release publishes a GitHub Security Advisory (GHSA) with a CVE and a severity. The fix lands as a merge titled security #cve-<year>-<number> [<Package>] <title>, for example security #cve-2026-55878 [Toolkit] Harden recipe installer against path traversal. A security fix is always published as one, never disguised as a refactor or a routine bug fix: users decide whether to upgrade from that signal.

The three dispositions:

DispositionBranchProcess
CVEcve-<year>-<number>Private fix, GHSA + CVE with severity, reporter credit, coordinated release, merge titled security #cve-...
Public hardeningnormal topic branchNormal open PR, merged as bug #<number>, no embargo
Not a security issuenone, or a normal topic branchReply to the reporter; optionally a doc or robustness PR

The symfony/ux repository has no security or severity labels: the severity lives in the GHSA.

This skill produces a recommendation. The final call belongs to the Symfony security team; treat its output as a structured argument, and defer to https://symfony.com/security for the authoritative list of what is not a vulnerability.

Progress checklist

  • Step 0: Establish the facts (reproduce, scope, trust model)
  • Step 1: Apply the disposition decision tree
  • Step 2: Assign severity and affected maintained branches
  • Step 3: Route (branch, next workflow, reporter reply)

Confirmation rule

Whenever this skill says "Wait for confirmation", treat anything other than an explicit affirmative as no: stop and ask the user how they want to proceed.


Step 0 — Establish the facts

Before classifying, pin down four things. Guessing any of them produces a wrong call.

  1. Reachability: is the vulnerable code on a path reachable from untrusted input in a default configuration, or does it need opt-in or insecure config?
  2. Actor and precondition: what must the attacker already have? Unauthenticated and remote is the worst case; "already has the app secret" or "already controls the templates" usually means the precondition is itself game-over.
  3. Impact: RCE, arbitrary file read/write, auth/authz bypass, XSS on a default-rendered surface, CSRF on a state-changing endpoint, signature bypass that accepts forged input, secret or data disclosure, or "only" DoS / info of low value.
  4. Contract: is the package meant to defend this boundary, or is the unsafe behaviour documented as the app's responsibility? Boundaries ux defends: the LiveComponent props checksum and its request gate on live endpoints, TwigComponent attribute escaping, the Icons SVG sanitizer (for Iconify responses and local files alike), Autocomplete result escaping and search-query handling, the Toolkit installer's path confinement for third-party kits. Documented app responsibilities: a writable: true LiveProp accepts values from the client by design, options_as_html: true renders Autocomplete results as raw HTML on purpose, and an autocompleter left with the default security: false is public (see src/Autocomplete/doc/index.rst).

Reproduce if at all possible; an unreproducible report is not yet triable.

Step 1 — Disposition decision tree

Apply in order. The first matching bucket wins.

It is not a security issue if any of these hold
  • Requires misuse contrary to documentation, with no default-config attack (one of the documented app responsibilities from Step 0.4).
  • Dev-only tooling (debug commands, profiler data) manifesting only in a dev environment.
  • Precondition is already game-over (attacker already holds the app secret, writes the templates, or has local access).
  • Not reproducible, or rooted in a third-party dependency outside ux's control. Content ux fetches and renders is not in that category: ux sanitizes Iconify SVG responses because it outputs them (CVE-2026-55877).
  • Not a realistic bypass (a comparison nuance with no working exploit, etc.).

Pure DoS / resource exhaustion is listed by the Symfony policy among the issues not considered security issues. ux has nevertheless published CVE-2026-49209 (low) for the unbounded _batch action fan-out in LiveComponent. For a DoS on a ux endpoint, recommend hardening, cite that precedent, and leave the call to the security team.

It is a CVE only if all of these hold
  1. Default-reachable: exploitable against a default or documented-safe config.
  2. Expected actor: the attacker is at or below the trust level the boundary is meant to enforce (typically unauthenticated/remote, a lower-privileged user escalating, or the author of a third-party Toolkit kit), with no game-over precondition.
  3. Contracted boundary: the package is meant to defend this (see Step 0.4).
  4. Real impact: RCE, arbitrary file read/write, auth/authz bypass, stored/reflected XSS on a default surface, CSRF on live endpoints, signature bypass accepting forged input, or data disclosure.
  5. Maintained: the vulnerable code ships on 2.x or 3.x.
Otherwise it is public hardening

A genuine improvement where a CVE condition fails. Shapes from ux history:

  • Safer default where the unsafe one never reached production: LiveComponentSubscriber's test mode defaulted to true in the constructor, but the bundle's compiler pass already overrode it for non-test kernels; flipping the default was a bug PR (#3566).
  • Timing-safe comparison without a working exploit: the Autocomplete extra_options checksum moved from !== to hash_equals() as a bug PR (#3565).
  • Robustness improvements (input caps, broader sanitizer coverage).

ux has also published CVEs for fixes that close a boundary a default install already guards: CVE-2026-49210 (child component tag, rejected even though the request is gated by the live endpoint checks) and CVE-2026-49212 (checksum bound to component name and slot). When a finding has that defense-in-depth shape, present both kinds of precedent instead of deciding.

Step 2 — Severity and affected branches

Severity (the GHSA severity; CVSS is a sanity check, not the goal). ux precedent:

  • high: arbitrary file write/read (CVE-2026-55878, Toolkit path traversal through a crafted kit manifest). Also unauthenticated RCE or auth bypass.
  • medium: XSS on a default-rendered surface (CVE-2025-47946 TwigComponent attributes, CVE-2026-49210 LiveComponent child tag, CVE-2026-49216 Autocomplete AJAX results, CVE-2026-55877 Icons SVG).
  • low: bounded impact or defense-in-depth (CVE-2026-49208 lenient date LiveProp parsing, CVE-2026-49209 _batch DoS, CVE-2026-49211 LIKE wildcards in Autocomplete, CVE-2026-49212 checksum binding, CVE-2026-49215 CSRF through the CORS-safelisted Accept header).

Affected branches: find the oldest version where the vulnerable code exists and intersect with the maintained branches, 2.x and 3.x (ux publishes no releases.json). Fix on the lowest maintained affected branch, then merge up with the merge-up skill: every 2026 CVE fix landed on 2.x and reached 3.x that way. Code that exists only on 3.x is fixed on 3.x. Record the oldest exposure even if it predates maintained versions; it becomes the GHSA affected range.

Show full SKILL.md (666 more words)Show less

Step 3 — Route

State the recommendation as: disposition + severity + affected maintained branches + the one-line rationale (which decision-tree conditions decided it), then route:

  • CVE: name the branch cve-<year>-<number>. The fix is prepared privately and goes through the coordinated-disclosure process (GHSA + CVE, reporter credit, security release). Do not open a public PR or push to a public remote before the release; origin is a public fork, so it counts. The fix carries a src/<Package>/CHANGELOG.md entry ending with (security fix), plus a BC note when the fix changes behaviour users can see (see the LiveComponent 2.36 entries). Wait for confirmation before any outward step, and print any git push command for the user instead of running it.
  • Public hardening: open a normal PR against the lowest affected branch; use symfony-security-review to confirm the fix. Hardening PRs such as #3565 and #3566 carried no CHANGELOG entry; add one only when users see a behaviour change.
  • Not a security issue: draft a short, factual reply to the reporter explaining why (cite the contract or threat-model reason), and optionally a doc clarification or low-priority robustness PR.

In every case, the fix follows TDD and runs only the affected package's tests: cd src/<Package> && composer update && php vendor/bin/phpunit, plus pnpm run test:unit and pnpm run build in src/<Package>/assets for a JS change, since dist/ is committed. No Claude/Anthropic credit, comments sparingly, no issue references in code.


Worked examples

From ux's published advisories and merged fixes:

Finding shapeDispositionDeciding factor
A third-party kit's copy-files entry with .. makes the installer write outside the project (CVE-2026-55878)CVE, highthe author of a remote kit is untrusted; arbitrary file write
{{ attributes }} renders attribute values unescaped (CVE-2025-47946)CVE, mediumdefault rendering path, XSS
Iconify API or local SVG rendered without sanitization (CVE-2026-55877)CVE, mediumux outputs the markup, so it owns sanitizing it
Autocomplete Stimulus controller renders AJAX text unescaped (CVE-2026-49216)CVE, mediumstored XSS through a default option
Client-sent child component tag interpolated into HTML (CVE-2026-49210)CVE, mediumXSS, published even though the live endpoint checks gate it by default
Accept header used as the only CSRF gate on live endpoints (CVE-2026-49215)CVE, lowAccept is CORS-safelisted, so cross-origin fetch() sets it without a preflight
Unbounded _batch actions, one sub-request each (CVE-2026-49209)CVE, lowDoS; ux precedent differs from the Symfony policy default
LiveComponentSubscriber test mode defaulting to on outside the compiler pass (#3566)Hardeningthe unsafe default never reached production through the bundle's DI
extra_options checksum compared with !== (#3565)Hardeningtiming nuance, no working exploit

Not-a-security-issue shapes follow from the documented contracts in Step 0.4 (a writable LiveProp set by the client, options_as_html: true, an autocompleter left public). They come from the documentation, not from a ruling on record, so say so in the reply.

Gotchas

  • "Boundary crossed" does not imply CVE. Impact and exposure decide it; present the ux precedents on both sides when the finding is defense-in-depth.
  • DoS is where ux practice and the written policy diverge. Cite CVE-2026-49209 and let the security team decide.
  • Rendered third-party content is ux's boundary. Whatever ux outputs (Iconify SVG, AJAX results rendered by a Stimulus controller) has to be made safe by ux, even though another system produced it.
  • Trust-model questions are for the maintainer. Surface them (is a remote kit trusted? is a given prop meant to be client-writable?) instead of assuming the answer.
  • Embargo discipline. Never name a CVE-bound finding, push a cve-* branch, or open a public issue or PR for a CVE-class finding before the coordinated release. Wait for confirmation.
  • Defer to authority. https://symfony.com/security is the source of truth for what is not a vulnerability; this skill encodes observed practice, not policy.

Error handling

  • If reachability or the trust model is unknown, say so and triage as needs-human-judgement; do not force a disposition.
  • Security reports are handled privately. Do not echo report contents into public artifacts, commit messages, or branch names that leak the vulnerability before release.
  • Never push to a public remote during CVE triage. Stop and hand back to the user.

© symfony, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

Just SKILL.md in .agents/skills/security-triage of symfony/ux.

Open the folder on GitHubat commit 5e9614c

Compare with similar skills

Security Triage next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Security Triage compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Security Triage this skillsymfony/ux1.1k—~3.2kAutomated safety check: PassMIT
Security Triagesymfony/symfony31k—~2.6kAutomated safety check: PassMIT
Deepsec Documentation Guidevercel-labs/deepsec8.1k—~956Automated safety check: PassApache-2.0
Shiro Attack CLISummerSec/ShiroAttack22.6k—~945Automated safety check: PassMIT
Cve Remediationrundeck/rundeck6.3k—~2.9kAutomated safety check: PassApache-2.0
Native Dependency Updatemono/SkiaSharp5.6k—~4.1kAutomated safety check: PassMIT

Similar skills

  • Security Triage

    symfony/symfony

    Triage a reported security finding into a disposition: a private CVE (coordinated disclosure + advisory), a public hardening PR (fix in the open, no CVE), or not-a-security-issue (reply to reporter).

    31k GitHub stars~2.6k tokensUpdated today
    SecurityAuto-check passed
  • Deepsec Documentation Guide

    vercel-labs/deepsec

    Official

    Points the agent at deepsec's own docs to answer questions about initializing, configuring, resuming, scanning with and extending the vulnerability scanner.

    8.1k GitHub stars~956 tokensUpdated 10 days ago
    SecurityAuto-check passed
  • Shiro Attack CLI

    SummerSec/ShiroAttack2

    当用户要求利用、检测或测试 Apache Shiro rememberMe 反序列化漏洞 (Shiro-550, CVE-2016-4437) 时使用。触发词包括 "Shiro"、"rememberMe"、"shiro attack"、"CVE-2016-4437"、"Shiro-550"、"爆破 Shiro key"、"利用 Shiro"、"Shiro…

    2.6k GitHub stars~945 tokensUpdated 4 mo ago
    SecurityAuto-check passed
  • Cve Remediation

    rundeck/rundeck

    Verify if a CVE affects the project and remediate it. An agent skill from rundeck/rundeck.

    6.3k GitHub stars~2.9k tokensUpdated today
    SecurityAuto-check passed
  • Update native dependencies (libpng, libexpat, zlib, libwebp, harfbuzz, freetype, libjpeg-turbo, etc.) in SkiaSharp's Skia fork.

    5.6k GitHub stars~4.1k tokensUpdated today
    SecurityAuto-check passed
  • Forensify

    alexgreensh/repo-forensics

    Cross-agent self-inspection of your AI-agent stack. An agent skill from alexgreensh/repo-forensics.

    188 GitHub stars~2.5k tokensUpdated 12 days ago
    SecurityAuto-check: notes

More from symfony/ux

  • Merge Up

    symfony/ux

    Cascade-merge the maintained Symfony UX branches from oldest to newest (2.x - 3.x), resolve conflicts, run the affected packages' tests and prepare the push.

    1.1k GitHub stars~3.4k tokensUpdated yesterday
    Auto-check passed
  • Principles for rigorously reviewing a Symfony UX pull request and making it merge-ready.

    1.1k GitHub stars~4.6k tokensUpdated yesterday
    Auto-check passed
  • Review a change (a PR, the current branch diff, or a set of files) or audit a Symfony UX package or the whole src/ tree for missing or incorrect security hardening.

    1.1k GitHub stars~2.9k tokensUpdated yesterday
    Auto-check passed
  • Generate, modify, or review Symfony UX Toolkit kit recipes (shadcn, flowbite-4, bootstrap, common).

    1.1k GitHub stars~10k tokensUpdated yesterday
    Auto-check passed

Works with

Categories

Questions about Security Triage

What does Security Triage do?

Triage a security finding in a Symfony UX package into a disposition: a private CVE (coordinated disclosure through the Symfony security process), a public hardening PR (fix in the open, no CVE), or…. Security Triage is an agent skill from symfony/ux. Triage a security finding in a Symfony UX package into a disposition: a private CVE (coordinated disclosure through the Symfony security process), a public hardening PR (fix in the open, no CVE), or not-a-security-issue (reply to the reporter).

When should I use Security Triage?

Security Triage fits situations like: deciding whether a report; A discovered weakness needs a CVE; how it should be disclosed; whether it is a security issue at all.

How do I install Security Triage in Claude Code?

Run `npx skills add symfony/ux --skill security-triage -a claude-code`. Or copy the skill folder (.agents/skills/security-triage in symfony/ux) into .claude/skills/security-triage in your project. Claude Code loads it when a task matches its description.

How do I install Security Triage in Codex?

Run `npx skills add symfony/ux --skill security-triage -a codex`. Or copy the skill folder (.agents/skills/security-triage in symfony/ux) into .agents/skills/security-triage in your project. Codex loads it when a task matches its description.

Can I use Security Triage in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add symfony/ux --skill security-triage -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/security-triage, .gemini/skills/security-triage, .github/skills/security-triage and .opencode/skills/security-triage in your project.

What does Security Triage need to run?

Going by SKILL.md and its folder, Security Triage needs the command-line tools its instructions call (pnpm, git, composer and php).

Does Security Triage access the network?

SKILL.md names 1 domain. As links in the text: symfony.com. This is read from the text; nothing was executed.

Is Security Triage safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Security Triage use?

Security Triage is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Security Triage use?

About 3.2k tokens (SKILL.md is roughly 13k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Security Triage?

Skills that share tags, products or a category with Security Triage: Security Triage (symfony/symfony, 31k stars), Deepsec Documentation Guide (vercel-labs/deepsec, 8.1k stars), Shiro Attack CLI (SummerSec/ShiroAttack2, 2.6k stars) and Cve Remediation (rundeck/rundeck, 6.3k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Security Triage?

symfony (a GitHub organization) maintains it in symfony/ux, which has 1,080 GitHub stars. The repository holds 5 skills in this directory. The repository was last updated on October 8, 2026.

Source: symfony/ux on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.