abdian/laravel-upload-guard 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

abdian/laravel-upload-guard

Composer 安装命令:

composer require abdian/laravel-upload-guard

包简介

Secure file upload validation for Laravel — fail-closed scanning for polyglot web shells, malicious PDFs/SVGs, zip bombs, Office macros, and spoofed MIME types.

README 文档

README

🛡️ Laravel Upload Guard

Fail-closed file-upload validation for Laravel.

Stops polyglot web shells, malicious PDFs & SVGs, zip bombs, Office macros, and spoofed MIME types — using structural parsing and content sanitization, not just regex.

Latest Version Total Downloads Tests PHP Version License

// One rule. Fail-closed by default.
$request->validate([
    'file' => 'required|safeguard',
]);

Why?

Laravel's built-in mimes / mimetypes rules trust the client-declared type and a coarse extension map. An attacker can upload shell.php renamed to avatar.jpg, a real JPEG with PHP appended after the image data (a polyglot web shell), an SVG carrying <script>, a PDF with an auto-run /JavaScript action, or a 42 KB zip that expands to petabytes. None of those are caught by extension checks.

Upload Guard inspects the actual bytes — magic structure, decoded PDF/zip streams, sanitized SVG/Office internals — and blocks anything it cannot prove is safe.

🔒 Design principle: fail closed

When the package cannot be sure a file is safe, it blocks the upload. Unknown content types, unparsable containers, and scanner exceptions all resolve to reject — never to allow. Stricter than lax validators by design.

Threat coverage

Threat How Upload Guard handles it
🐚 Polyglot web shells (PHP in JPEG / PDF / ZIP) Always-on code scan on every upload, regardless of detected type
🎭 Spoofed MIME / double extension Structural byte detection + strict extension ↔ content matching
🖼️ Malicious SVG (XSS / XXE) Allowlist sanitization; DOCTYPE/entity/script stripping; stored clean
📄 Malicious PDF (/JavaScript, /OpenAction, /Launch) Decode-before-scan, indirect-/Filter resolution, bounded inflation
💣 Zip bombs & zip-slip Global actual-bytes cap across nested archives; traversal / symlink / NTFS-ADS rejection
📎 Office macros + macro-less RCE OOXML and legacy OLE/CFB; VBA, ActiveX, DDE/DDEAUTO, remote attachedTemplate
🧨 Image decompression bombs Header pixel/byte cap before any decode; optional re-encode to strip payloads
🌊 Upload DoS Hard size caps + optional per-IP rate limiting + opt-in forensic quarantine

Table of contents

Installation

composer require abdian/laravel-upload-guard

The service provider is auto-discovered. Publish the (fully commented) config to tune behavior:

php artisan vendor:publish --tag=safeguard-config

Requirements

PHP 8.1 · 8.2 · 8.3 · 8.4
Laravel 10 · 11 · 12
Required extensions fileinfo, zip, dom, libxml
Optional extensions exif (EXIF inspection/stripping) · gd or imagick (image re-encode mode)

Optional extensions degrade gracefully — the package installs and runs without them.

Quick start

public function store(\Illuminate\Http\Request $request)
{
    $request->validate([
        'file' => 'required|safeguard',
    ]);

    $request->file('file')->store('uploads');
}

The single safeguard rule runs — by default, no fluent calls required:

✅ structural MIME detection + dangerous-type blocking  ·  ✅ strict extension/content matching  ·  ✅ always-on code scanning  ·  ✅ SVG sanitization  ·  ✅ image & PDF scanning  ·  ✅ archive and Office-macro scanning.

Usage

With Laravel's mimes rule

$request->validate([
    'file' => 'required|safeguard|mimes:jpg,png,pdf',
]);

safeguard reads the allowed extensions and enforces that the file's real content type matches them.

Fluent configuration

use Abdian\UploadGuard\Rules\Safeguard;

$request->validate([
    'avatar' => ['required', (new Safeguard)
        ->imagesOnly()
        ->maxDimensions(1920, 1080)
        ->blockGps()
        ->stripMetadata(),
    ],

    'document' => ['required', (new Safeguard)
        ->pdfsOnly()
        ->maxPages(50)
        ->blockJavaScript()
        ->blockExternalLinks(),
    ],

    'report' => ['required', (new Safeguard)
        ->documentsOnly(),   // archive + macro scanning are already on by default
    ],
]);

Individual rules

Compose only the scanners you need:

$request->validate([
    'avatar'   => 'required|safeguard_mime:image/jpeg,image/png|safeguard_image',
    'icon'     => 'required|safeguard_svg',
    'document' => 'required|safeguard_pdf|safeguard_pages:1,10',
    'photo'    => 'required|safeguard_dimensions:100,100,4000,4000',
    'archive'  => 'required|safeguard_archive',
    'report'   => 'required|safeguard_office',
]);
Rule Description
safeguard All-in-one, fail-closed pipeline
safeguard_mime:type1,type2 Real content-type allowlist (+ dangerous-type block)
safeguard_php Always-on PHP/script code scan
safeguard_svg Allowlist SVG sanitization
safeguard_image Image bomb / metadata / byte / trailing-data scan
safeguard_pdf Decode-before-scan PDF analysis
safeguard_archive Streaming archive inspection (zip/tar/gz)
safeguard_office OOXML + legacy OLE macro / DDE / template detection
safeguard_dimensions:maxW,maxH,minW,minH Image dimension limits
safeguard_pages:min,max PDF page-count limits

Note on safeguard_archive string params: parameters are added to the block list (e.g. safeguard_archive:iso,bin also blocks .iso/.bin). To allow an otherwise-blocked extension, use the fluent rule: (new SafeguardArchive)->allow(['sh']).

Fluent API reference

All methods on Abdian\UploadGuard\Rules\Safeguard return $this (chainable).

Method Effect
allowedMimes(array $mimes) Restrict to a real-content-type allowlist ('image/*' wildcards supported)
imagesOnly() / pdfsOnly() / documentsOnly() / archivesOnly() Restrict to a file family
maxDimensions(int $w, int $h) / minDimensions(int $w, int $h) Image dimension bounds
dimensions(int $minW, int $minH, int $maxW, int $maxH) All four bounds at once
maxPages(int) / minPages(int) / pages(int $min, int $max) PDF page-count bounds
blockGps() Reject images that contain GPS/EXIF location data
stripMetadata() Strip metadata from images
blockJavaScript() Reject PDFs containing JavaScript
blockExternalLinks() Reject PDFs containing external links
strictExtensionMatching(bool = true) Force/disable extension ↔ content matching
scanArchives(bool = true) Toggle archive scanning (on by default)
blockMacros(bool = true) / allowMacros() Toggle Office-macro blocking (on by default)

Configuration

The published config/safeguard.php is fully commented; highlights:

'max_scan_size'   => 25 * 1024 * 1024, // files larger than this are rejected
'over_cap_policy' => 'reject',         // or 'header_only'

'mime_validation' => [
    'strict_check'       => true,
    'block_dangerous'    => true,
    'block_undetectable' => false,     // set true to reject unknown content types
],

'archive_scanning' => [
    'enabled'               => true,                 // ON by default
    'max_decompressed_size' => 500 * 1024 * 1024,    // hard cap on ACTUAL bytes (global)
    'max_files_count'       => 10000,
    'max_nesting_depth'     => 3,
],

'office_scanning' => [
    'enabled'       => true,           // ON by default
    'block_macros'  => true,
    'block_activex' => true,
],

'svg_scanning'   => ['mode' => 'sanitize'],                 // or 'reject'
'image_scanning' => ['max_pixels' => 64_000_000, 'reencode' => false],

'rate_limiting'  => ['enabled' => false],  // DoS guard (opt-in)
'quarantine'     => ['enabled' => false],  // forensic quarantine (opt-in)

Every key is also overridable via environment variables (e.g. SAFEGUARD_ARCHIVE_SCAN, SAFEGUARD_SVG_MODE, SAFEGUARD_IMAGE_REENCODE).

How it works

Always-on code scanning

Every upload is scanned for PHP/script openers (<?php, <?=, bare <?, <script language=php>, <%, __halt_compiler) regardless of detected type — a valid image/PDF/ZIP header never exempts a file, so polyglot web shells appended after a magic header are caught. The dangerous-function layer only triggers inside real PHP regions, so .js/.py/.csv text never false-positives.

Structural MIME detection

Classifies by byte structure (≥512-byte header window), disambiguates OLE/ftyp/RIFF/ZIP families (real .xls → Excel, JAR/APK detected), validates short signatures, and returns untrusted (null) for unknown content — never "binary safe".

SVG sanitization

SVGs run through an allowlist sanitizer and the cleaned output is stored. Unquoted handlers, encoded javascript: URIs, <script>, and all DTD/DOCTYPE/entities are removed. XML parsing installs a denying external-entity loader (XXE-safe).

PDF decode-before-scan

Flate/LZW/ASCII85/ASCIIHex and object streams are inflated (with bounded output) and #xx names decoded before matching /JavaScript, /JS, /OpenAction, /AA, /Launch, /EmbeddedFile. Indirect and decoy /Filter references are resolved so compressed payloads can't hide. Matches are delimiter-anchored and case-sensitive. Encrypted PDFs that can't be inspected are rejected.

Real zip-bomb detection

Archives are streamed against a hard cap on actual decompressed bytes that is global across the whole nested-archive tree (nested fan-out can't multiply it); forged central-directory / TAR sizes can't bypass it. Traversal (both separators), absolute paths, NTFS ADS, dangerous extensions on any name segment, symlinks, and unreadable entries are all rejected.

Office macros & macro-less vectors

VBA/OLE/ActiveX in both OOXML and legacy OLE/CFB (.doc/.xls/.ppt), resolved via relationships and content types (case-insensitive). Also detects DDE/DDEAUTO field codes and external/remote-template (attachedTemplate) injection. The CFB reader follows the full DIFAT chain and fails closed on truncated containers.

Image hardening

Decompression-bomb guard enforced from the header before any decode (also inside the re-encode path), full EXIF/metadata + byte scanning (works without ext-exif), trailing-data detection, and an optional GD/Imagick re-encode that strips appended payloads.

Hardening notes

  • What it is not: a synchronous validation library, not an antivirus — no AV signatures, sandboxed detonation, or ML classification.
  • TOCTOU: scanning the temp file doesn't close the temp→storage window. Move validated uploads carefully and prefer enabling image_scanning.reencode.
  • SVG storage: in sanitize mode the uploaded file is rewritten in place with the cleaned SVG, so ->store() persists the safe version.
  • Workers: rate-limiter counters are atomic, the MIME cache is bounded, and per-instance rule overrides are restored after each validation — safe under Octane/queue workers.

Testing

composer test      # PHPUnit (Testbench) — 142 tests with malicious fixtures
composer analyse   # PHPStan (level 5)
composer check     # validate + analyse + test

Security

Please report vulnerabilities privately — see SECURITY.md (email esanjdev@gmail.com or open a private GitHub Security Advisory). Please do not open public issues for security reports.

Contributing

Contributions are welcome — see CONTRIBUTING.md. Run composer check before opening a PR.

License

Open-sourced under the MIT license.

统计信息

  • 总下载量: 0
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 4
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-06-20

承接程序开发

PHP开发

VUE

Vue开发

前端开发

小程序开发

公众号开发

系统定制

数据库设计

云部署

网站建设

安全加固