.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.
render-security-audit-md.py)query-chrome-releases.py) — see Chrome Releasesvalidate-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 3, 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".
⚠️ 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 3 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 10. 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 findingsThese rules apply to the JSON assembly (Step 8) 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.