* Harden skill importer against SSRF: block private targets + revalidate redirects per hop
The skill importer validated only the initial URL with the lenient SSRF guard
(block_private=False) and then fetched with follow_redirects=True, so a 3xx to
an internal/metadata address (169.254.169.254, 127.0.0.1, RFC-1918) was still
connected to — inconsistent with the hardened services/search/content.py
:_get_public_url path.
Add a _get_checked() helper that follows redirects manually and re-runs the
SSRF guard with block_private=True on every hop, and route all three fetch
sites (skills.sh unwrap, _fetch_bytes, _list_github_dir) through it. GitHub's
own redirects and the final-host _assert_github_url checks are preserved.
Adds hermetic regression tests (IP-literal hosts, faked HTTP layer) and updates
the existing mock signature for the new block_private kwarg.
Defense-in-depth: the endpoint is admin-gated (require_admin) and admins are
trusted per THREAT_MODEL.md, so this is not a cross-boundary vulnerability.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test: enforce follow_redirects=False invariant in mock client
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat(skills): import SKILL.md bundles from public GitHub URLs
Supports GitHub tree/blob/raw links and skills.sh pages that resolve to GitHub.
Installs SKILL.md plus sibling text assets under data/skills/imported/.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(skills): admin-gate URL import and validate redirect hosts
- require_admin on POST /api/skills/import-from-url (matches other skill admin routes)
- reject cross-host redirects after httpx follow_redirects
- test for redirect host validation
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(skills): match Brain Add panel import/submit button styles
- Skill URL Import: theme-io-btn + download icon (same as memory Import)
- Add Skill submit: confirm-btn confirm-btn-primary
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(skills): allow api.github.com during directory import
Real imports hit the GitHub contents API after redirects; whitelist
api.github.com and add regression tests. Shrink Import button with flex:none.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(skills): align skill Import button with URL input row
Match memory-add-input height (28px) in memory-add-row and center the
download icon with flexbox instead of vertical-align hacks.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(skills): cancel modal-body margin on skill Import button
The skill Import button sits in .memory-add-row beside an input; the
global .modal-body button { margin-top: 6px } rule only affected buttons,
pushing Import down and misaligning the download icon. Reset margin-top
and match Memory Import SVG markup at 28px row height.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(skills): surface GitHub API errors on URL import
Pass through GitHub response messages (especially 403 rate limits) as
SkillImportError instead of a generic download failure.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>