Re-triage the 2026-03-15 security finding set against current main, keep the old snapshot as historical baseline, and add a current-head refresh with updated counts and finding status.\n\nLink the baseline and addendum to the new refresh report so maintainers have one current source of truth for what is still reproducible on HEAD.
17 KiB
17 KiB
Security Findings Triage (2026-03-15)
Maintainer note: later fixes changed the status of several findings after this baseline snapshot. Use security-findings-triage-2026-03-29-refresh.md as the current source of truth, and keep this file as the historical baseline snapshot.
- Baseline:
origin/main@226f10c2a62fc182b4e93458bddea2e60f9b0cb9 - Input CSV was treated as triage input only, not as ground truth.
- Status meanings:
still present and exploitable,still present but low practical risk,obsolete/not reproducible on current HEAD,duplicate of another finding.
Summary
- still present and exploitable: 6
- obsolete/not reproducible on current HEAD: 6
- still present but low practical risk: 14
- duplicate of another finding: 7
Remediation Buckets
codex/security-filesystem-trust-boundary: findings 1, 3, 7, 10, 16, 20, 21, 27, 31, 32, 33 plus duplicates 5, 6, 8, 17, 22, 23.codex/security-auth-integrity: findings 12 and 19.codex/security-shell-safety: findings 4 and 24.codex/security-robustness: findings 9, 14, 15, 18, 29, 30.codex/security-runtime-exploitable: no standalone bucket remained after default-branch verification; the actionable issues all fit the filesystem/auth/shell/robustness buckets above.
Detailed Findings
| # | Severity | Title | Current Paths | Status | Bucket | Why It Is / Is Not Valid On origin/main |
Minimal Safe Fix | Target PR |
|---|---|---|---|---|---|---|---|---|
| 1 | high | Unsanitized frontmatter name enables path traversal in sync script | tools/scripts/sync_microsoft_skills.py |
still present and exploitable | filesystem-trust-boundary | On origin/main, sync_microsoft_skills.py used the parsed frontmatter name directly under TARGET_DIR and cleanup_previous_sync reused flat_name from attribution without constraining it to skills/. | Sanitize flat names to a single safe path segment and refuse cleanup/copy targets that resolve outside the cloned repo or local skills/ root. | codex/security-filesystem-trust-boundary |
| 2 | medium | Stored XSS via rehype-raw rendering of skill markdown | apps/web-app/src/pages/SkillDetail.tsx |
obsolete/not reproducible on current HEAD | n/a | On origin/main, SkillDetail renders markdown with react-markdown + remark-gfm + rehype-highlight only; rehype-raw is no longer imported or enabled. | n/a | n/a |
| 3 | medium | Symlink-following copy leaks host files in setup_web | tools/scripts/setup_web.js |
still present and exploitable | filesystem-trust-boundary | On origin/main, setup_web.js used fs.statSync and recursive copy on skills/, so a symlink inside skills could resolve to an arbitrary host file or directory and be copied into public assets. | Resolve symlinks only when their real path stays inside skills/; otherwise skip them and keep copying regular entries. | codex/security-filesystem-trust-boundary |
| 4 | medium | Insecure install guidance allows remote script execution | skills/apify-actorization/SKILL.md |
still present but low practical risk | shell-safety | On origin/main, the Apify actorization skill still recommended curl/irm pipe-to-shell installation and apify login -t, which is documentation-only but directly instructs unsafe execution and credential handling. | Replace pipe-to-shell commands with package-manager guidance and remove command-line token examples. | codex/security-shell-safety |
| 5 | medium | setup_web.js now follows symlinks, enabling file exfiltration | tools/scripts/setup_web.js |
duplicate of another finding | filesystem-trust-boundary | Same origin/main behavior as finding 3: fs.statSync-based recursive copy in setup_web.js followed symlink targets during public asset setup. | Fix once in setup_web.js by constraining symlink resolution to the skills root. | codex/security-filesystem-trust-boundary |
| 6 | medium | Symlink traversal in web asset setup copies arbitrary files | tools/scripts/setup_web.js |
duplicate of another finding | filesystem-trust-boundary | Same origin/main behavior as finding 3: the setup_web recursive copy followed symlink targets and copied their resolved content. | Fix once in setup_web.js by constraining symlink resolution to the skills root. | codex/security-filesystem-trust-boundary |
| 7 | medium | Symlink file copying in .github/skills sync leaks host files | tools/scripts/sync_microsoft_skills.py |
still present and exploitable | filesystem-trust-boundary | On origin/main, find_skills_in_directory accepted symlinked skill dirs by item.resolve() and copy loops accepted regular files from resolved dirs without checking they remained under the clone root. | Reject symlink targets outside the clone root and copy only regular files whose resolved path stays under the clone root. | codex/security-filesystem-trust-boundary |
| 8 | medium | Symlinked file copy in Microsoft skill sync can leak host data | tools/scripts/sync_microsoft_skills.py |
duplicate of another finding | filesystem-trust-boundary | Same origin/main behavior as finding 7: the Microsoft sync path trusted resolved symlink targets and copied files from them. | Fix once in sync_microsoft_skills.py by constraining resolved paths to the clone root. | codex/security-filesystem-trust-boundary |
| 9 | medium | Committed Python bytecode can hide malicious logic | `skills/ui-ux-pro-max/scripts/pycache/core.cpython-314.pyc | skills/ui-ux-pro-max/scripts/pycache/design_system.cpython-314.pyc` | still present but low practical risk | robustness | On origin/main, tracked pycache artifacts were still present under skills/ui-ux-pro-max/scripts, which is review-hostile but not independently exploitable. | Remove tracked bytecode artifacts and rely on source-only review plus .gitignore. |
| 10 | medium | Symlinked SKILL.md can leak host files via index script | tools/scripts/generate_index.py |
still present but low practical risk | filesystem-trust-boundary | On origin/main, generate_index.py opened every SKILL.md it found via os.walk and did not skip symlinked SKILL.md files, so a malicious local symlink could exfiltrate another file into index metadata generation. | Skip symlinked SKILL.md files during indexing. | codex/security-filesystem-trust-boundary |
| 11 | low | Example loader trusts manifest paths, enabling file read | docs/integrations/jetski-gemini-loader/loader.mjs |
obsolete/not reproducible on current HEAD | n/a | On origin/main, the loader example resolves the requested file and rejects any path whose path.relative escapes the configured skills root, so the reported direct file read no longer reproduces. | n/a | n/a |
| 12 | low | TLS certificate verification disabled in new scrapers | `skills/junta-leiloeiros/scripts/scraper/base_scraper.py | skills/junta-leiloeiros/scripts/web_scraper_fallback.py` | still present but low practical risk | auth-integrity | On origin/main, both the base scraper and the direct fallback client instantiated HTTP clients with verify=False / ignore_https_errors=True, which weakens transport integrity but is a local-run scraper risk rather than an application RCE. | Enable TLS verification by default and require an explicit environment opt-out for insecure targets. |
| 13 | low | Complete bundle omits valid skill categories | `tools/lib/skill-filter.js | tools/scripts/build-catalog.js | data/bundles.json` | obsolete/not reproducible on current HEAD | n/a | On origin/main, shipped bundle data is generated by tools/scripts/build-catalog.js into data/bundles.json; the reported omission in tools/lib/skill-filter.js does not drive current shipped catalog data. |
| 14 | low | Malformed frontmatter delimiter breaks YAML parsing for skills | `skills/alpha-vantage/SKILL.md | tools/lib/skill-utils.js` | still present but low practical risk | robustness | On origin/main, skills/alpha-vantage/SKILL.md still contained an extra delimiter token (--- Unknown), which caused parser warnings and broken metadata interpretation. | Repair the malformed frontmatter so the file is a valid YAML frontmatter document. |
| 15 | low | ws_listener writes sensitive events to predictable /tmp files | skills/videodb/scripts/ws_listener.py |
still present but low practical risk | robustness | On origin/main, ws_listener defaulted to /tmp for event, pid, and websocket-id files, which is a same-host local confidentiality risk rather than a remote exploit. | Default to a user-owned state directory instead of shared /tmp when no explicit output dir is provided. | codex/security-robustness |
| 16 | low | Symlink traversal lets /skills/ serve arbitrary local files | apps/web-app/refresh-skills-plugin.js |
still present but low practical risk | filesystem-trust-boundary | On origin/main, refresh-skills-plugin.js used path.resolve(filePath).startsWith(...) and fs.statSync(filePath), so a symlink inside skills/ could still read a target outside the intended tree in local dev. | Resolve real paths and only serve files whose resolved path remains inside the skills root. | codex/security-filesystem-trust-boundary |
| 17 | low | Sync Skills endpoint follows symlinks from downloaded archive | apps/web-app/refresh-skills-plugin.js |
duplicate of another finding | filesystem-trust-boundary | On origin/main, the stale Home.jsx path no longer exists, but the live issue is the same plugin root cause as finding 16: once symlinked content lands under skills/, the dev server trusts it by lexical path only. | Fix once in refresh-skills-plugin.js by resolving and constraining real paths. | codex/security-filesystem-trust-boundary |
| 18 | low | Validation crash if YAML frontmatter is not a mapping | tools/scripts/validate_skills.py |
still present but low practical risk | robustness | On origin/main, validate_skills.parse_frontmatter returned yaml.safe_load output directly; scalar YAML values were not rejected before downstream key access. | Reject non-mapping frontmatter early and return a validation error instead of passing scalar values downstream. | codex/security-robustness |
| 19 | low | Anonymous Supabase writes allow skill star tampering | `apps/web-app/src/lib/supabase.ts | apps/web-app/src/hooks/useSkillStars.ts | apps/web-app/src/context/SkillContext.tsx` | still present and exploitable | auth-integrity | Inference from source: on origin/main, useSkillStars performed a direct upsert to skill_stars from frontend code using the public anon client. The repo contains no server-side gate or versioned policy proving that writes are constrained. |
| 20 | low | Metadata fixer overwrites symlinked SKILL.md targets | tools/scripts/fix_skills_metadata.py |
still present but low practical risk | filesystem-trust-boundary | On origin/main, fix_skills_metadata.py opened and rewrote every discovered SKILL.md without skipping symlinked files, so a crafted symlink could modify another file. | Skip symlinked SKILL.md files and only mutate real local skill files with mapping frontmatter. | codex/security-filesystem-trust-boundary |
| 21 | low | Installer now dereferences symlinks during copy | tools/bin/install.js |
still present and exploitable | filesystem-trust-boundary | On origin/main, copyRecursiveSync used fs.statSync on cloned content, so a malicious symlink in the repo could copy arbitrary local files into the install target. | Use lstat, resolve symlinks only when they stay inside the cloned repo root, and skip/ignore out-of-root links. | codex/security-filesystem-trust-boundary |
| 22 | low | Installer merge path dereferences symlinks when copying | tools/bin/install.js |
duplicate of another finding | filesystem-trust-boundary | Same origin/main behavior as finding 21: install.js dereferenced symlinks during install/merge copy. | Fix once in install.js by constraining or skipping symlink resolution. | codex/security-filesystem-trust-boundary |
| 23 | low | Cleanup sync deletes arbitrary paths via flat_name | tools/scripts/sync_microsoft_skills.py |
duplicate of another finding | filesystem-trust-boundary | Same origin/main root cause as finding 1: cleanup_previous_sync used flat_name from attribution without constraining it to skills/. | Fix once in sync_microsoft_skills.py by sanitizing flat names before delete/copy operations. | codex/security-filesystem-trust-boundary |
| 24 | low | Audio transcription example allows Python code injection | skills/audio-transcriber/examples/basic-transcription.sh |
still present but low practical risk | shell-safety | On origin/main, basic-transcription.sh used an unquoted heredoc and embedded $AUDIO_FILE/$MODEL/$TRANSCRIBER directly into Python source, so crafted input could break quoting and inject code in a local example script. | Use quoted heredocs and pass values through environment variables instead of interpolating them into Python source. | codex/security-shell-safety |
| 25 | low | Unbounded recursive skill traversal can crash catalog build | `tools/lib/skill-utils.js | tools/scripts/build-catalog.js` | obsolete/not reproducible on current HEAD | n/a | On origin/main, listSkillIdsRecursive walks Dirent directories from readdirSync({withFileTypes:true}); symlink entries are not treated as directories, so the reported unbounded symlink recursion does not reproduce. | n/a |
| 26 | low | Release scripts still use root skills_index.json path | `tools/scripts/update_readme.py | tools/scripts/generate_index.py | tools/scripts/release_cycle.sh` | obsolete/not reproducible on current HEAD | n/a | On origin/main, root skills_index.json is the canonical generated index and release_cycle.sh is only a wrapper around release_workflow.js, so the reported path mismatch no longer reproduces as a defect. |
| 27 | low | Symlink traversal in skill normalization allows file overwrite | `tools/lib/skill-utils.js | tools/scripts/normalize-frontmatter.js` | still present but low practical risk | filesystem-trust-boundary | On origin/main, listSkillIds used fs.statSync and fs.existsSync on child skill dirs, so normalize-frontmatter could treat symlinked skill folders as writable local skills. | Use lstat-based discovery and skip symlinked skill dirs / SKILL.md entries before normalization. |
| 28 | low | last30days skill passes user input directly to Bash command | skills/last30days/SKILL.md |
obsolete/not reproducible on current HEAD | n/a | On origin/main, the documented command passes "$ARGUMENTS" as a quoted argument to Python, so the reported direct Bash injection sink does not reproduce from the current text. | n/a | n/a |
| 29 | low | Unvalidated YAML frontmatter can crash index generation | tools/scripts/generate_index.py |
duplicate of another finding | robustness | Same origin/main root cause as finding 18, but in generate_index.py instead of validate_skills.py: scalar YAML values were passed through without a mapping check. | Fix once by rejecting non-mapping frontmatter in both parser paths. | codex/security-robustness |
| 30 | low | Predictable /tmp counter file enables local file clobbering | skills/cc-skill-strategic-compact/suggest-compact.sh |
still present but low practical risk | robustness | On origin/main, suggest-compact.sh stored state in /tmp/claude-tool-count-$$, which is predictable and shared-host local-only. | Move the counter file into a user-owned state directory. | codex/security-robustness |
| 31 | low | Symlink traversal risk in new sync script | tools/scripts/sync_recommended_skills.sh |
still present but low practical risk | filesystem-trust-boundary | On origin/main, sync_recommended_skills.sh copied a fixed allowlist from the repo with cp -r, which is local-only but still trusts symlink handling in source content. | Use cp -RP so symlinks are preserved instead of dereferenced. | codex/security-filesystem-trust-boundary |
| 32 | low | skills_manager allows path traversal in enable/disable operations | tools/scripts/skills_manager.py |
still present but low practical risk | filesystem-trust-boundary | On origin/main, enable_skill/disable_skill joined the user-supplied skill name directly under skills/.disabled and skills/, so ../ segments could escape the intended root. | Resolve the requested path and reject names that escape the intended skills directory. | codex/security-filesystem-trust-boundary |
| 33 | low | Zip Slip risk in Office unpack scripts | `skills/docx-official/ooxml/scripts/unpack.py | skills/pptx-official/ooxml/scripts/unpack.py` | still present and exploitable | filesystem-trust-boundary | On origin/main, both unpack.py scripts called ZipFile.extractall(output_path) directly, so a malicious Office archive could write outside the requested directory. | Validate each archive member path before extraction and reject path-traversal entries. |