You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Imagine a post office that opens every parcel to inspect it for safety, but the inspection room has no blast containment — so a parcel designed to explode on opening detonates in the very facility meant to keep everyone safe. That's the paradox here: SkillSpector is a security scanner that is itself vulnerable to the attacks it should be detecting.
Two separate vulnerabilities exist in the InputHandler:
Zip Slip (Path Traversal): When processing .zip skill archives, _extract_zip() calls zipfile.extractall() without validating that extracted paths stay within the target directory. A malicious zip can write files anywhere on the filesystem.
SSRF (Server-Side Request Forgery): The _is_git_url() validation uses substring matching (if any(host in parsed.netloc for host in git_hosts)) and accepts any URL ending in .git. This allows an attacker to trick SkillSpector into making requests to internal network endpoints, cloud metadata services, or arbitrary hosts.
This issue consolidates both under one coherent attack surface analysis of InputHandler, as they share the same root cause: the ingest layer processes attacker-controlled input without validation.
Why This Matters — Real-World Scenario
Scenario 1: Zip Slip in a CI/CD scanning pipeline
A company runs SkillSpector in their CI pipeline to vet community-submitted skills before publishing to an internal registry. The scanner runs as a GitHub Action on a shared runner.
A malicious contributor submits a skill as a .zip archive. Inside, the zip contains:
The CI pipeline downloads and scans the zip. SkillSpector's _extract_zip() unpacks it, writing to ../../../home/runner/.bashrc. The next time any job runs on that runner, the injected script exfiltrates secrets and source code.
Scenario 2: SSRF via Git URL on a cloud instance
The same pipeline accepts Git URLs for scanning. An attacker submits:
SkillSpector validates this URL: 169.254.169.254 is not in the default git_hosts list, but the URL ends in .git, and the fallback path accepts it. The scanner's host (an EC2 instance) issues a git clone to the metadata endpoint. Even though the clone fails, the HTTP request reaches the metadata service — and in some configurations, the error output or network logs expose IAM credentials.
In both cases, the security scanner itself becomes the attack vector.
Reproduction
Zip Slip
importzipfile, io, os, tempfile# Create malicious zipbuf=io.BytesIO()
withzipfile.ZipFile(buf, 'w') aszf:
zf.writestr("SKILL.md", "---\nname: test\n---\n# Normal")
zf.writestr("../../etc/pwned.txt", "you have been pwned")
buf.seek(0)
# Save and scanwithopen("/tmp/evil-skill.zip", "wb") asf:
f.write(buf.read())
skillspector scan /tmp/evil-skill.zip --no-llm
# Check if /tmp/etc/pwned.txt was created outside the temp extract dir
ls -la /tmp/etc/pwned.txt # Should NOT exist, but it does
SSRF
# Point to an internal HTTP service (simulated)
skillspector scan "http://169.254.169.254/latest/meta-data.git" --no-llm
# Observe: git clone attempt is made to the metadata endpoint# Even on failure, the HTTP request reaches the target
Root Cause
Zip Slip — src/skillspector/input_handler.py lines 181-182:
Python's zipfile.extractall() does not prevent path traversal — it extracts entries with ../ prefixes to locations outside the target directory. The fix is to validate each entry's resolved path stays within extract_dir before extraction.
def_is_git_url(self, input_str: str) ->bool:
git_hosts= ["github.com", "gitlab.com", "bitbucket.org"]
parsed=urlparse(input_str)
ifparsed.schemein ("http", "https", "git", "ssh"):
ifany(hostinparsed.netlocforhostingit_hosts): # Substring match!returnTrueifinput_str.endswith(".git"):
returnTrue# Any URL ending in .git is acceptedreturnFalse
Two problems:
Substring matching: "github.com" in "evil-github.com" is True — an attacker-controlled domain evil-github.com passes validation
.git suffix fallback: Any URL ending in .git is accepted regardless of host, allowing internal network targets with a .git suffix
The _clone_git() method (lines 125-148) then runs git clone without --depth 1 safety flags or environment variables like GIT_TERMINAL_PROMPT=0 and GIT_ASKPASS=/bin/true to prevent credential leakage.
Impact
Zip Slip: Arbitrary file write on the host filesystem — can overwrite configuration files, inject backdoors into CI runners, or corrupt system files
SSRF: Network requests to internal services, cloud metadata endpoints (169.254.169.254), or arbitrary external hosts from the scanner's network position
Privilege escalation path: Zip Slip + CI runner = code execution as the CI service account
Supply chain risk: A scanner vulnerability means the security gate itself is compromised — all skills passing through it are at risk
Trust violation: Security tools are granted elevated access precisely because they're trusted; a vulnerability here has outsized blast radius
Summary
Imagine a post office that opens every parcel to inspect it for safety, but the inspection room has no blast containment — so a parcel designed to explode on opening detonates in the very facility meant to keep everyone safe. That's the paradox here: SkillSpector is a security scanner that is itself vulnerable to the attacks it should be detecting.
Two separate vulnerabilities exist in the
InputHandler:Zip Slip (Path Traversal): When processing
.zipskill archives,_extract_zip()callszipfile.extractall()without validating that extracted paths stay within the target directory. A malicious zip can write files anywhere on the filesystem.SSRF (Server-Side Request Forgery): The
_is_git_url()validation uses substring matching (if any(host in parsed.netloc for host in git_hosts)) and accepts any URL ending in.git. This allows an attacker to trick SkillSpector into making requests to internal network endpoints, cloud metadata services, or arbitrary hosts.Relation to Existing Issues
filter="data"). That PR remains unmerged._is_git_url()is a separate, previously unreported vulnerability in the same component. It is not covered by [SECURITY] Zip Slip Vulnerability: Unchecked zip extraction in input_handler.py allows arbitrary file write #109, fix(security): prevent Zip Slip path traversal vulnerability in input… #116, Bound the input_handler ingest layer (URL download, zip extraction, git clone) before the per-file gate #21, or feat(analyzer): no detection for SSRF / cloud-metadata credential theft #62 (which is about detecting SSRF in scanned skills, not SSRF in SkillSpector itself).This issue consolidates both under one coherent attack surface analysis of
InputHandler, as they share the same root cause: the ingest layer processes attacker-controlled input without validation.Why This Matters — Real-World Scenario
Scenario 1: Zip Slip in a CI/CD scanning pipeline
A company runs SkillSpector in their CI pipeline to vet community-submitted skills before publishing to an internal registry. The scanner runs as a GitHub Action on a shared runner.
A malicious contributor submits a skill as a
.ziparchive. Inside, the zip contains:The CI pipeline downloads and scans the zip. SkillSpector's
_extract_zip()unpacks it, writing to../../../home/runner/.bashrc. The next time any job runs on that runner, the injected script exfiltrates secrets and source code.Scenario 2: SSRF via Git URL on a cloud instance
The same pipeline accepts Git URLs for scanning. An attacker submits:
SkillSpector validates this URL:
169.254.169.254is not in the defaultgit_hostslist, but the URL ends in.git, and the fallback path accepts it. The scanner's host (an EC2 instance) issues agit cloneto the metadata endpoint. Even though the clone fails, the HTTP request reaches the metadata service — and in some configurations, the error output or network logs expose IAM credentials.In both cases, the security scanner itself becomes the attack vector.
Reproduction
Zip Slip
SSRF
Root Cause
Zip Slip —
src/skillspector/input_handler.pylines 181-182:Python's
zipfile.extractall()does not prevent path traversal — it extracts entries with../prefixes to locations outside the target directory. The fix is to validate each entry's resolved path stays withinextract_dirbefore extraction.SSRF —
src/skillspector/input_handler.pylines 105-117:Two problems:
"github.com" in "evil-github.com"isTrue— an attacker-controlled domainevil-github.compasses validation.gitsuffix fallback: Any URL ending in.gitis accepted regardless of host, allowing internal network targets with a.gitsuffixThe
_clone_git()method (lines 125-148) then runsgit clonewithout--depth 1safety flags or environment variables likeGIT_TERMINAL_PROMPT=0andGIT_ASKPASS=/bin/trueto prevent credential leakage.Impact
Affected Version
SkillSpector v2.2.3