.agents/skills/security-audit/SKILL.md
Investigate the security status of SkiaSharp's native dependencies. Skia core is treated as the product itself (not just a dependency) and gets a deeper, commit-level resolution process. Third-party deps and Component Governance alerts are audited alongside it and combined into a single unified report.
ℹ️ This skill is read-only. To create PRs and fix issues, use the
native-dependency-updateskill.
main vs Beta milestone coverage, channel milestones + Skia commitsrender-security-audit-md.py)query-chrome-releases.py) — see Chrome Releasesquery-milestone-schedule.py) — main vs Beta heads-up + release-notes support-tier drift, see Milestone Schedulevalidate-security-audit.py)render-security-audit.py)Search mono/SkiaSharp open issues for:
Search PRs in both mono/SkiaSharp and mono/skia for dependency updates already in flight.
🔍 The Chrome Releases blog often discloses Skia CVEs before NVD processes them. This step provides early detection and cross-validation.
See references/chrome-releases.md for full details on the data source, script usage, and AI review instructions.
python3 .agents/skills/security-audit/scripts/query-chrome-releases.py \
--verbose --output output/ai/chrome-releases-cache.json
This takes ~10-30 seconds (fetches RSS feed pages). Cache is reused if < 24 hours old.
Deterministic (regex): Read structured_cves[] from the JSON output. These are
high-confidence CVEs extracted from the known blog format. Each has a CVE ID, severity,
component, bug ID, and milestone already parsed.
AI review (broad): Scan posts[].text_content for anything the regex missed:
After the NVD query in Step 5, compare results:
| Chrome Releases | NVD | Interpretation |
|---|---|---|
| ✅ Found | ✅ Found | Normal — use NVD CVSS, Chrome Releases for milestone |
| ✅ Found | ❌ Not found | Early disclosure — NVD may be delayed. Use Chrome severity. |
| ❌ Not found | ✅ Found | Vendor bulletin CVE (Android/Huawei) — not in Chrome stable |
Set the source field on each CVE object: "both", "chrome_releases", or "nvd".
🗓️ Scheduling + channel context, not a security check on its own. It tells us whether
mainis keeping up with the Chrome Beta milestone and how much lead time remains before the next milestone reaches stable.
main is the SkiaSharp front line and tracks the Chrome Beta milestone; as milestones
graduate Beta → Stable → Extended stable, a release/<major>.<M>.x line is cut from a main that
was already on M. So "where we are" = main's milestone, and the signal that matters is
main_milestone >= beta_channel_milestone. See
references/milestone-schedule.md for the model, endpoints,
and flags.
python3 .agents/skills/security-audit/scripts/query-milestone-schedule.py \
--output output/ai/milestone-schedule-cache.json
This reads main's milestone + major from scripts/VERSIONS.txt, fetches the live channels and the
upcoming schedule, and prints prioritized heads-up alerts:
| Level | Meaning |
|---|---|
🔴 critical | main < Beta and a newer milestone already ships on a stable-class channel — the bump is overdue and reaching non-preview users. |
🟠 urgent | main < Beta — the front line is behind; bump main to the Beta milestone. |
❓ unknown | The Beta milestone couldn't be read (Chromium Dash down) — signal not evaluated. Don't treat as OK; re-run. |
🟡 watch | A milestone past main branches within the window — start preparing. |
🟢 ok | main >= Beta — front line current. |
support block)The same run also drift-checks the release-notes support paths in
scripts/infra/docs/versions.json (two hand-maintained lists, stable + preview) against
the live channels — detection only, the fix is a manual edit of that file (spec §3.5). The
verdict is in the support object of the JSON (status: ok | warn | drift | absent)
and printed under "Support tiers (versions.json)":
support.status | Meaning | Audit action |
|---|---|---|
🟢 ok | stable covers Chrome Stable (or Extended-stable during the promotion gap) and preview tracks Beta-or-newer. | None. |
🟡 warn | Plausible but worth noting (e.g. stable ahead of Chrome Stable, preview empty or trailing Beta). | Mention in the prose summary. |
🔴 drift | stable is behind/off-channel, or preview is not a real preview. | Raise a finding in nextSteps: edit versions.json support to the milestones we actually ship. |
This is a docs-grouping check, not a CVE — but a drift verdict means the website is
mis-stating what is supported, so treat it as a finding.
meta.status + the upcoming table answer it directly.nextSteps when status == "behind" (or a watch
milestone) also carries HIGH/CRITICAL CVEs from the Chrome Releases / NVD passes; cite the
target milestone's stable date as the deadline. Treat a critical heads-up as a finding
even with no GitHub issue filed.release/*.x line is missing a within-milestone Skia backport, use the
Skia CVE resolution process (merge-base ancestry) — the
schedule tool only covers milestone alignment.⚠️ CRITICAL: Never trust
cgmanifest.jsonblindly. Always verify versions against the actual submodule, DEPS file, and source headers. cgmanifest.json is manually maintained and can drift. Report any mismatches as findings.
🛑 MANDATORY: Fetching the upstream
google/skiabranch is required, not optional. Adding a git remote and fetching is read-only — it does not modify any tracked files. Without independent verification of the upstream merge point, the audit would trust cgmanifest.json circularly, defeating the purpose of verification.
# 1. Get the actual submodule commit
git submodule status externals/skia
# Output: 8c99e432... externals/skia (the mono/skia fork commit)
# 2. Read the REAL milestone from the source
cat externals/skia/include/core/SkMilestone.h
# Look for: #define SK_MILESTONE NNN
# 3. Find the upstream google/skia merge point
cd externals/skia
git log --oneline --merges --grep="chrome/m" -5 HEAD
# Find the merge commit that brought in chrome/mNNN
# 4. Add the upstream remote and fetch (read-only)
git remote add upstream https://github.com/google/skia.git 2>/dev/null || \
git remote set-url upstream https://github.com/google/skia.git
git fetch upstream chrome/mNNN
git log --format="%H %s" -1 FETCH_HEAD
# This gives the independently-verified upstream_merge_commit
# 5. Confirm upstream is ancestor of our fork
git merge-base --is-ancestor FETCH_HEAD <merge-parent> && echo "VERIFIED"
Compare against cgmanifest.json and report mismatches:
| Field | Source of truth | cgmanifest.json field |
|---|---|---|
| Milestone | SkMilestone.h in submodule | chrome_milestone |
| Fork commit | git submodule status | git entry commitHash |
| Upstream commit | git fetch upstream chrome/mNNN tip | upstream_merge_commit |
See references/third-party-deps.md for the full table of
header files and the googlesource mirror URL pattern. In short: read pinned commit hashes
from externals/skia/DEPS, then fetch each dependency's version header at that commit and
parse the version string.
ANGLE is a separate native component (Windows-only, for WinUI). It is NOT part of the Skia submodule.
grep ANGLE scripts/VERSIONS.txt
# Output: ANGLE release chromium/NNNN
ANGLE has its own submodules (third_party/zlib, jsoncpp, vulkan-deps,
astc-encoder/src) that must also be tracked. See
references/third-party-deps.md
for details. Flag any missing from cgmanifest.json as a coverage gap.
The versionVerification array in the JSON report must include ALL dependencies from
ALL sources:
| Source | What to include |
|---|---|
"Skia DEPS" | All deps from externals/skia/DEPS + Skia itself |
"ANGLE" | ANGLE itself (version from VERSIONS.txt) |
"ANGLE submodule" | ANGLE's submodules (zlib, jsoncpp, vulkan-deps, astc-encoder) |
"GPU/Graphics" | VulkanMemoryAllocator, SPIRV-Cross, D3D12Allocator from DEPS |
"Supporting" | piex, wuffs, dng_sdk, buildtools from DEPS |
Each entry must have a source field and a cgmanifestVersion field (null if missing).
Report mismatches as findings.
🛑 Skia is the product, not just a dependency. Every Skia CVE must be resolved to a specific fix commit, branch, cherry-pick test, and reachability assessment. Classification by milestone alone is INCOMPLETE.
See references/skia-cve-resolution.md for the full process, including:
keywordSearch=Skia)issues.chromium.org/issues/NNNNN referencesgit fetch upstream chrome/mNNN + git log --grep=<bug_id> to find fix commitsFor libpng, freetype, harfbuzz, libexpat, brotli, zlib, libjpeg-turbo, libwebp, ANGLE submodules, etc.
See references/third-party-deps.md for:
git merge-base --is-ancestor)CG scans Docker container images and build-time deps from both ADO pipelines. CG alerts are invisible to GitHub Issues and NVD searches alone.
🛑 THIS STEP TAKES 5–7 MINUTES. The CG script queries 60+ jobs across 8+ builds. This is NORMAL and NON-NEGOTIABLE. Use
initial_wait: 600(or higher). Do NOT skip, fabricate empty results, or write placeholder data because it's "taking too long." The validator will reject reports with emptypipelinesor fabricated timestamps.
See references/cg-alerts.md for:
scripts/query-cg-alerts.py) — run ONCE, cache to fileaz devops approach for debuggingalerts array in the report (do NOT summarize)Before flagging anything, verify the CVE actually affects SkiaSharp.
General false positives (apply to any dependency):
Dependency-specific false positives:
🛑 MANDATORY: The audit MUST produce a JSON file conforming to references/report-schema.md. This is the machine-readable output used by dashboards and CI.
Build the JSON object with these top-level keys:
meta — Date, schema version, Skia commit hashes, milestone, upstream verification statussummary — Counts by status category, total CVEs, highest severityversionVerification — One entry per dependency with DEPS commit, verified version, cgmanifest version, match booleanfindings — Array of finding objects sorted by priority then severity. ONE object per dependency (e.g., one "skia" finding containing ALL Skia CVEs regardless of status). Each has dependency, status, cves[], nonChromeCves[], action, notes. The status reflects the WORST-case status among the CVEs.cgAlerts — The complete raw JSON from query-cg-alerts.py (full alerts array, do not summarize)chromeReleases — Chrome Releases blog data. Transform the script's snake_case output (cve_id→cveId, bug_id→bugId, blog_post_url→blogPostUrl) into structuredCves[]. Also copy blogPostUrl onto matching CVEs in findings[].cves[]. See report-schema.md for the full field mapping.nextSteps — Prioritized action items with severity, command, and reason🛑 COMPLETENESS REQUIREMENT: The
findingsarray MUST include every CVE returned by the NVD query (Step 1 of skia-cve-resolution.md). CVEs that are verified as already fixed in our tree are classified as"already_fixed"or"false_positive"— they are NOT dropped from the report. An audit that finds 15 CVEs in NVD but only reports 7 in the JSON is INCOMPLETE and will fail review. The total CVE count insummary.totalCvesmust match the number of CVE objects across all findings.
🛑 ONE FINDING PER DEPENDENCY: Do NOT create multiple finding objects for the same dependency. All CVEs for "skia" go in ONE finding. All CVEs for "libpng" go in ONE finding. Use each CVE's
assessmentfield to distinguish affected/fixed/false_positive. The finding's top-levelstatusreflects the worst-case among its CVEs (e.g., if 3 CVEs are already_fixed but 2 are needs_attention, the finding status is"needs_attention").
Save as output/ai/security-audit-{date}.json.
🛑 MANDATORY: Always validate before rendering. Fix any errors reported.
python3 .agents/skills/security-audit/scripts/validate-security-audit.py \
output/ai/security-audit-{date}.json
Exit codes: 0 = valid, 1 = fixable errors (fix and retry), 2 = fatal.
Warnings are informational — errors must be fixed before proceeding.
🛑 MANDATORY: Always generate both reports.
python3 .agents/skills/security-audit/scripts/render-security-audit.py \
output/ai/security-audit-{date}.json
python3 .agents/skills/security-audit/scripts/render-security-audit-md.py \
output/ai/security-audit-{date}.json
This produces:
The HTML renders:
Present the output path to the user:
✅ security-audit-2026-04-10.html (45 KB)
m132 • 2026-04-10 • 12 CVEs • Highest: HIGH
🔴 3 attention · 🆕 2 undiscovered · ⚪ 4 FP · ✅ 5 clean
The Markdown report was already generated in Step 11. Present a brief summary in the conversation pointing to the generated files:
✅ Reports generated:
• output/ai/security-audit-{date}.json (structured data)
• output/ai/security-audit-{date}.html (interactive dashboard)
• output/ai/security-audit-{date}.md (full markdown for AI review)
m147 • 2026-05-29 • 102 CVEs • Highest: CRITICAL
🔴 0 attention · 🆕 0 undiscovered · ⚪ 1 FP · ✅ 6 clean
📰 Chrome Releases: 146 Skia-relevant CVEs (16 above current milestone)
Then highlight the top actionable items from the report:
needs_attention or undiscovered findingsmain behind the Beta milestone, or a milestone branching/going stable soonversions.json support block out of date with the live Chrome channelsThese rules apply to the JSON assembly (Step 9) and are enforced by the renderers:
After audit, use the native-dependency-update skill to act on findings:
For Skia core CVEs, the fix typically requires merging a newer upstream milestone into the fork (or cherry-picking specific fix commits, per the resolution pipeline). This is a significant undertaking — flag it in the report with the milestone gap and the list of required commits.
For CG container alerts, the fix is updating Dockerfiles under
scripts/infra/native/linux/docker/. This does not require a Skia submodule update — only
Docker image rebuilds.