Common Weakness Enumeration

CWE-22

Allowed-with-Review

Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

Abstraction: Base · Status: Stable

The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.

13124 vulnerabilities reference this CWE, most recent first.

GHSA-RFGQ-WGG8-662P

Vulnerability from github – Published: 2026-05-05 18:52 – Updated: 2026-05-13 14:19
VLAI
Summary
S3-Proxy has Security Issues in its Resource Path Matching Implementation
Details

Background

The original concern is functional: a resource pattern should treat a percent-encoded segment like some%2Fvalue as a single opaque token rather than splitting it into two path segments at the decoded /. Investigation into why %2F was being decoded and how routes matched against the result surfaced three related security issues, documented below.

Rather than landing a fix directly, the problem space warrants discussion first. Different fixes carry different compliance and compatibility tradeoffs, and every viable option is a breaking change in some form. Aligning on a direction before committing to an implementation is the safer path.

Root cause: two different path representations

Go's net/http decodes percent-encoded characters when it parses an incoming URL: %2F becomes / in r.URL.Path, while the original encoded form is preserved in r.URL.RawPath. Two different parts of s3-proxy use different fields:

  • The auth middleware calls r.URL.RequestURI(), which returns the encoded form (from RawPath when available). It sees %2F as literal characters, not as path separators.
  • The bucket handler reads r.URL.Path to build the S3 key. It sees the decoded form, where %2F has already become /.

All three issues stem from this mismatch, combined with how glob patterns are compiled. The examples below use PUT for concreteness, but the auth bypass applies to any HTTP method — a config that restricts GET or DELETE on a namespace is equally affected, meaning an attacker could read from or delete objects in a protected namespace without credentials.

A note on RFC 3986

RFC 3986 §2.2 states that / and %2F are not equivalent in a URI path:

URIs that differ in the replacement of a reserved character with its corresponding percent-encoded octet are not equivalent.

/ is a reserved gen-delim used as a path segment separator. %2F is its percent-encoded form and, by the RFC, should be treated as data within a segment — not as a separator. So:

  • /foo/bar/baz → three segments: foo, bar, baz
  • /foo%2Fbar/baz → two segments: foo/bar (opaque data), baz

The original functional concern (wanting foo%2Fbar to match as a single token against a single-segment wildcard) is therefore RFC-correct behaviour. Go's r.URL.Path violates this by decoding %2F to /, collapsing the two representations into one. This is the underlying tension that makes fixing these issues non-trivial: the simplest security fix makes s3-proxy more RFC non-compliant, while the RFC-correct fix requires a more significant refactor.

A note on breaking changes

Any of the proposed fixes for these issues should be treated as a breaking change. Each option alters how path patterns in existing configs are interpreted — whether by changing how * matches segments, by shifting which path representation auth matches against, or by normalising paths before they reach the router. Operators upgrading to a fixed version will need to review their resource path definitions, and a clear migration note in the changelog is essential regardless of which approach is chosen.

One way to avoid a hard breaking change would be to introduce a new field — for example route: — that carries the fixed semantics, while keeping the existing path: field with its current behaviour (and marking it deprecated). Operators could migrate resource definitions incrementally, and the security fix would be available immediately without requiring a coordinated config update across all deployments. The obvious cost of this approach is maintaining two parallel implementations, duplicated test coverage, and the ongoing burden of supporting a deprecated code path until it can eventually be removed.


Issue 1 — * in resource paths matches across /

Background

Resource paths are matched using github.com/gobwas/glob. The call site is:

// pkg/s3-proxy/authx/authentication/main.go
g, err := glob.Compile(res.Path)

glob.Compile is called without a separator argument. Without a separator, * matches any character — including /. This means a pattern intended to protect a single path segment actually matches across directory boundaries.

Example

Consider a config with an open route and a protected route:

resources:
  # open — no auth required
  - path: /upload/*/drafts/
    methods: [PUT]
    whiteList: true

  # protected — basic auth required
  - path: /upload/*/restricted/
    methods: [PUT]
    basic:
      ...

The intent is clear: drafts is open, restricted is protected. The * is meant to match a single path segment (the object identifier).

However, because * crosses /, the pattern /upload/*/drafts/ also matches:

PUT /upload/foo/drafts/../restricted/

The path segment matched by * is foo, and then drafts/../restricted/ is consumed by the rest of the pattern — because without a separator, * is equivalent to .* and matches /, ., and everything else.

The result: an unauthenticated request is accepted by the open route.

Fix discussion

The straightforward fix is to pass '/' as the separator to glob.Compile:

// before
g, err := glob.Compile(res.Path)

// after
g, err := glob.Compile(res.Path, '/')

With a separator set: - * matches any sequence of non-/ characters (a single path segment). - ** matches any sequence including / (crossing path boundaries).

This fix closes the Issue 1 attack above: with a separator, drafts/../restricted/ is more than one segment and no longer matches the pattern /upload/*/drafts/.

Breaking change

Any existing config that relies on * crossing / must be updated to **. For example:

# before — worked accidentally because * crossed /
- path: /upload/*/drafts/

# after — single-segment match (behaviour unchanged for single-segment IDs)
- path: /upload/*/drafts/

# after — multi-segment match (e.g. nested object IDs containing /)
- path: /upload/**/drafts/

A migration note in the changelog would be needed.


Issue 2 — Percent-encoded slashes bypass auth via segment collapsing

Background

With Fix 1 applied, * only matches a single path segment. However, the auth middleware matches against r.URL.RequestURI() — the encoded path — while the bucket handler uses r.URL.Path — the decoded path. A client can use %2F to make what looks like a single segment in the encoded URI decode into multiple segments including a protected path component.

Example

Using the same config as Issue 1:

PUT /upload/foo%2Frestricted/drafts/

Step by step:

  1. r.URL.RawPath = /upload/foo%2Frestricted/drafts/
  2. r.URL.Path (decoded) = /upload/foo/restricted/drafts/
  3. Auth middleware calls r.URL.RequestURI() → returns the encoded form.
  4. With Fix 1's separator /, glob splits on the literal /. The segment between the first and second slash is foo%2Frestricted — one token with no literal / — so * matches it. Pattern /upload/*/drafts/ fires.
  5. Open route → request proceeds without auth.
  6. Bucket handler uses r.URL.Path → S3 key is upload/foo/restricted/drafts/…written into the restricted namespace without credentials.

Proof via integration test

I added TestPercentEncodedSlashBypass to pkg/s3-proxy/server/server_integration_test.go. The test sends a complete multipart PUT without credentials and asserts a 401 response. It currently fails with 204 — the file is written in full to the restricted namespace without any authentication.

Fix discussion

This issue has two fundamentally different classes of fix, each with a different stance on RFC 3986 compliance.

Option A — Match auth against the decoded path (r.URL.Path)

Change the auth middleware to use r.URL.Path instead of r.URL.RequestURI():

// before
requestURI := r.URL.RequestURI()

// after
requestURI := r.URL.Path

Both auth and the bucket handler now operate on the same decoded string, closing the mismatch that enables the bypass.

Pros: One-line change; no other code touched; closes the bypass completely.

Cons: RFC 3986 non-compliant — /foo%2Fbar/baz and /foo/bar/baz become indistinguishable at the auth layer. A pattern like /upload/*/drafts/ will match both PUT /upload/foo/drafts/ and PUT /upload/foo%2F.../drafts/ identically after decoding, making it impossible for operators to write a pattern that distinguishes the two. Any path segment containing a literal / encoded as %2F can never be matched as a single token by *.

Option B — Use the raw path in both auth and key construction

Keep r.URL.RequestURI() in the auth middleware (reverting the Option A change) and replace the bucket handler's decoded path extraction with r.URL.EscapedPath() stripped of the mount path prefix. The AWS SDK then handles percent-encoding the key in the HTTP request to S3, with no manual segment splitting required.

This keeps %2F opaque at both layers: auth matches against the encoded form, and the S3 key preserves the encoded characters verbatim.

Security mechanism: the bypass attack (PUT /upload/foo%2Frestricted/drafts/) still returns 204 — the open route genuinely matches, because foo%2Frestricted is one encoded segment and * accepts it. However, the key written to S3 is upload/foo%2Frestricted/drafts/… — a distinct namespace from upload/foo/restricted/drafts/…. The attacker cannot reach the protected prefix because %2F and / are treated as different characters all the way to storage.

AWS S3 compatibility confirmed: S3 natively supports %2F in key names. A key upload/foo%2Fbar/file.txt is stored and retrieved as a distinct object from upload/foo/bar/file.txt. All four operations (HEAD, GET, PUT, DELETE) work correctly with %2F-containing paths.

Pros: RFC-compliant; %2F remains a meaningful encoding — foo%2Fbar is one token and * correctly matches it as a single segment; /foo%2Fbar/baz and /foo/bar/baz are distinct at both auth and storage layers; simpler than it sounds — no custom segment-splitting utility needed, just r.URL.EscapedPath() in the handler. The breaking change is contained to config files, not clients: the only clients that break are those relying on * crossing literal / — and those require a config change to ** under any fix option. Clients that encode user input containing / as %2F in a path segment are preserved: foo%2Fbar is still one encoded segment, and * still matches it. Under Option A those same clients break — the decoded form splits into multiple segments that no longer match *. The required client-side fix would be to filter or transform any / out of user input before building the URL, which may not always be feasible if the / carries meaning.

Cons: The auth middleware reverts to using the encoded path, which re-opens the door to dot-segment bypass (Issue 3) if the path-cleaning middleware is not also in place — the two fixes must be applied together.

A note on the 204 response: a request like PUT /upload/foo%2Frestricted/drafts/ returns 204 under this option, which may look like a bypass at first glance. It is not. If %2F carries meaning, foo%2Frestricted is a valid identifier indistinguishable from any other — the server has no basis to treat it as suspicious. The correct security responsibility is to handle all inputs consistently and safely, not to guess intent based on the content of user-provided values. The namespace separation guarantee satisfies that: whatever the client sends is handled the same way at both the auth and storage layers.

Option C — Reject requests containing %2F in the path

Return 400 Bad Request for any request whose raw path contains %2F:

if strings.Contains(r.URL.RawPath, "%2F") || strings.Contains(r.URL.RawPath, "%2f") {
    http.Error(w, "Bad Request", http.StatusBadRequest)
    return
}

Pros: Simplest possible enforcement; eliminates the ambiguity entirely.

Cons: Breaks any client that sends object names containing / encoded as %2F; rules out a legitimate and RFC-sanctioned use of percent-encoding.


Issue 3 — Dot-dot segments bypass authentication with prefix patterns

Background

Issues 1 and 2 both involve * (single-segment wildcard). A different class of bypass survives Fix 1 and Fix 2 when configs use prefix-style patterns with ** at the end, such as /open/**. This is a natural and common pattern for "allow everything under this prefix." The ** token is explicitly designed to cross /, so .. traversal within that prefix still reaches protected paths.

Note that %2F..%2F encoded traversal is a variant of this issue: the decoded form (/../) contains dot segments that ** can consume, as described in the root cause section.

Example

Consider this config:

resources:
  # protected — basic auth required for anything under /restricted/
  - path: /restricted/**
    methods: [PUT]
    basic:
      ...

  # open — no auth required for anything under /open/
  - path: /open/**
    methods: [PUT]
    whiteList: true

Without any path normalization, the following request bypasses auth:

PUT /open/../restricted/secret.json

Step by step:

  1. Go's net/url resolves dot segments when parsing the request URI: r.URL.Path is /restricted/secret.json. The raw form ../ is preserved only in r.URL.RawPath.
  2. The auth middleware calls r.URL.RequestURI(), which returns the encoded form — /open/../restricted/secret.json — and evaluates resources against that.
  3. /restricted/** does not match because the raw path does not start with /restricted/.
  4. /open/** matches: ** is allowed to cross /, so it consumes ../restricted/secret.json.
  5. The open route fires — no auth required — the request returns 204.
  6. The bucket handler reads r.URL.Path — already /restricted/secret.json — and writes the file directly into the restricted namespace.

Confirmed against AWS S3: the file lands at restricted/secret.json — not at a key containing ../. Go resolves the dot segments before the bucket handler runs, so the write goes straight into the protected prefix. This makes the attack more severe than a key-naming anomaly: it is a direct, confirmed write into the restricted namespace with no authentication.

Proof via integration test

I added TestPathTraversalDoubleStarPrefix to pkg/s3-proxy/server/server_integration_test.go. It uses the exact config above and shows that, with a path-cleaning middleware applied before the auth middleware, the traversal returns 401 instead of 204:

{
    // /open/** still matches /open/../restricted/file because ** crosses '/'.
    // cleanPathMiddleware resolves the path to /restricted/file first, which
    // matches the protected resource -> 401.
    // Without cleanPathMiddleware this would return 204 (auth bypassed).
    name:         "traversal from open to restricted via ** prefix pattern is blocked",
    inputMethod:  "PUT",
    inputURL:     "http://localhost/open/../restricted/file.txt",
    expectedCode: 401,
},

Note on %2E (percent-encoded dots)

Go's net/http decodes %2E. in r.URL.Path before any middleware runs, so %2E%2E arrives as .. by the time any of the options below apply. All options operate on the already-decoded r.URL.Path and therefore handle encoded dots without any extra work.

Fix discussion

All options below address the same root problem: r.URL.RequestURI() preserves dot segments while r.URL.Path has already resolved them, and auth sees the un-resolved form. The options differ in where the resolution happens and how invasive the change is.

Option A — Reject requests containing dot segments

Reject (400 Bad Request) any request whose decoded path contains /./ or /../:

func rejectDotSegmentsMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        p := r.URL.Path
        if strings.Contains(p, "/./") || strings.Contains(p, "/../") ||
            strings.HasSuffix(p, "/.") || strings.HasSuffix(p, "/..") {
            http.Error(w, "Bad Request", http.StatusBadRequest)
            return
        }
        next.ServeHTTP(w, r)
    })
}

Pros: Simple, explicit, no normalization side-effects.
Cons: Rejects requests that some clients may legitimately send (though dot segments in HTTP paths are unusual and ill-advised).

Option B — Use path.Clean

func cleanPathMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        p := r.URL.Path
        cleaned := path.Clean(p)
        if cleaned != p {
            r2 := r.Clone(r.Context())
            r2.URL.Path = cleaned
            r2.URL.RawPath = ""
            next.ServeHTTP(w, r2)
            return
        }
        next.ServeHTTP(w, r)
    })
}

path.Clean resolves .. and ., collapses double slashes, and also removes the trailing slash. The trailing-slash removal is a breaking change for any config that uses paths ending in / — resource patterns, mount paths, or anything else matched against the incoming path. A request to /upload/foo/drafts/ would be cleaned to /upload/foo/drafts, and any pattern or handler that expects the trailing slash would no longer match.

This can be mitigated by restoring the trailing slash after cleaning:

if len(p) > 1 && p[len(p)-1] == '/' {
    cleaned += "/"
}

Implementation note: An approach that stores the cleaned path in the request context rather than modifying r.URL.Path and clearing r.URL.RawPath will not work: both the auth middleware and the bucket handler read from r.URL directly, so a context-stored override is invisible to them.

Pros: Uses the standard library; less custom code.
Cons: The trailing-slash removal is mitigable by restoring the trailing slash after cleaning (as shown above), but it adds a correctness requirement to the middleware that is easy to overlook — omitting it silently breaks any config using trailing-slash patterns, which is the default convention in s3-proxy examples and documentation.


Interaction between Issue 2 and Issue 3 fixes

The choice made for Issue 2 affects the tradeoffs for Issue 3:

  • If Option A is chosen for Issue 2 (auth uses r.URL.Path), then dot segments have already been resolved by Go before any middleware runs, so Issue 3 is partially addressed without any additional middleware — but Option A's RFC non-compliance tradeoff still applies.
  • If Option B is chosen for Issue 2 (raw path in both layers), the auth middleware sees the encoded form, which still contains literal ../ dot segments. Issue 3 is not addressed by Option B alone — one of the Issue 3 options must also be applied. Importantly, whichever dot-segment option is chosen must clear r.URL.RawPath when it modifies the path, so that r.URL.EscapedPath() in the bucket handler reflects the cleaned path. This works naturally with both Issue 3 options (which operate on r.URL.Path and clear RawPath), and the fixes compose cleanly in practice.
  • In all cases, an explicit dot-segment policy (reject or resolve) is clearer than relying on Go's implicit resolution as a side-effect.

Combined effect

Attack Issue 1 fix Issue 2 fix Issue 3 fix
* crosses / (/upload/*/drafts/ matches ../restricted/) Fixed
%2F segment injection (foo%2Frestricted/drafts/ bypasses */restricted/) No Fixed
.. traversal via ** prefix pattern (/open/../restricted/) No No Fixed
%2F..%2F encoded traversal (decoded .. consumed by **) No Fixed* Fixed

* Issue 2's fix (auth using decoded path, Option A) also prevents %2F-encoded dot segments from being treated as opaque tokens, so the decoded .. is visible to the glob before matching.


Suggested combination of fixes

  • Issue 1: Pass '/' as the separator to glob.Compile. Unambiguously correct; * should never have crossed /.
  • Issue 2: Option B — use the raw path (r.URL.EscapedPath()) in both the auth middleware and the bucket handler. This is the only option that avoids client-side breaking changes for operators whose clients encode user input containing / as %2F. The security guarantee is namespace separation, which is the right model: the server has no basis to distinguish a legitimate %2F-encoded identifier from one that "looks like" a traversal attempt, so consistent handling at both layers is the correct responsibility boundary.
  • Issue 3: Option B — cleanPathMiddleware using path.Clean with trailing slash restored. Required when using Issue 2 Option B, since auth still sees the raw path. The two fixes compose cleanly: the middleware modifies r.URL.Path and clears r.URL.RawPath, so r.URL.EscapedPath() in the bucket handler reflects the cleaned path.

The combined breaking change is limited to config files: operators need to replace * with ** wherever multi-segment wildcard matching is intended. Client-facing URLs require no changes.


Resources

  • pkg/s3-proxy/authx/authentication/main.gofindResource, the glob.Compile call
  • pkg/s3-proxy/server/server_integration_test.goTestPercentEncodedSlashBypass, TestPathTraversalDoubleStarPrefix, TestPathCleaning
  • github.com/gobwas/glob — separator documentation
  • RFC 3986 §2.2 — equivalence of percent-encoded reserved characters
  • RFC 3986 §3.3 — path segment semantics
  • RFC 3986 §5.2.4 — dot-segment resolution in URI paths
Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Go",
        "name": "github.com/oxyno-zeta/s3-proxy"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "0.0.0-20260424211602-1320e4abd46a"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-42882"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-22",
      "CWE-863"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-05-05T18:52:56Z",
    "nvd_published_at": "2026-05-11T20:25:44Z",
    "severity": "CRITICAL"
  },
  "details": "## Background\n\nThe original concern is functional: a resource pattern should treat a percent-encoded segment like some%2Fvalue as a single opaque token rather than splitting it into two path segments at the decoded /. Investigation into why %2F was being decoded and how routes matched against the result surfaced three related security issues, documented below.\n\nRather than landing a fix directly, the problem space warrants discussion first. Different fixes carry different compliance and compatibility tradeoffs, and every viable option is a breaking change in some form. Aligning on a direction before committing to an implementation is the safer path.\n\n## Root cause: two different path representations\n\nGo\u0027s `net/http` decodes percent-encoded characters when it parses an incoming URL:\n`%2F` becomes `/` in `r.URL.Path`, while the original encoded form is preserved in\n`r.URL.RawPath`. Two different parts of s3-proxy use different fields:\n\n- The **auth middleware** calls `r.URL.RequestURI()`, which returns the encoded\n  form (from `RawPath` when available). It sees `%2F` as literal characters, not\n  as path separators.\n- The **bucket handler** reads `r.URL.Path` to build the S3 key. It sees the\n  decoded form, where `%2F` has already become `/`.\n\nAll three issues stem from this mismatch, combined with how glob patterns are\ncompiled. The examples below use PUT for concreteness, but the auth bypass applies\nto any HTTP method \u2014 a config that restricts GET or DELETE on a namespace is\nequally affected, meaning an attacker could read from or delete objects in a\nprotected namespace without credentials.\n\n### A note on RFC 3986\n\nRFC 3986 \u00a72.2 states that `/` and `%2F` are **not equivalent** in a URI path:\n\n\u003e URIs that differ in the replacement of a reserved character with its\n\u003e corresponding percent-encoded octet are **not** equivalent.\n\n`/` is a reserved gen-delim used as a path segment separator. `%2F` is its\npercent-encoded form and, by the RFC, should be treated as data *within* a\nsegment \u2014 not as a separator. So:\n\n- `/foo/bar/baz` \u2192 three segments: `foo`, `bar`, `baz`\n- `/foo%2Fbar/baz` \u2192 two segments: `foo/bar` (opaque data), `baz`\n\nThe original functional concern (wanting `foo%2Fbar` to match as a single token\nagainst a single-segment wildcard) is therefore RFC-correct behaviour. Go\u0027s\n`r.URL.Path` violates this by decoding `%2F` to `/`, collapsing the two\nrepresentations into one. This is the underlying tension that makes fixing these\nissues non-trivial: the simplest security fix makes s3-proxy *more* RFC\nnon-compliant, while the RFC-correct fix requires a more significant refactor.\n\n### A note on breaking changes\n\nAny of the proposed fixes for these issues should be treated as a **breaking\nchange**. Each option alters how path patterns in existing configs are interpreted\n\u2014 whether by changing how `*` matches segments, by shifting which path\nrepresentation auth matches against, or by normalising paths before they reach the\nrouter. Operators upgrading to a fixed version will need to review their resource\npath definitions, and a clear migration note in the changelog is essential\nregardless of which approach is chosen.\n\nOne way to avoid a hard breaking change would be to introduce a new field \u2014 for\nexample `route:` \u2014 that carries the fixed semantics, while keeping the existing\n`path:` field with its current behaviour (and marking it deprecated). Operators\ncould migrate resource definitions incrementally, and the security fix would be\navailable immediately without requiring a coordinated config update across all\ndeployments. The obvious cost of this approach is maintaining two parallel\nimplementations, duplicated test coverage, and the ongoing burden of supporting\na deprecated code path until it can eventually be removed.\n\n---\n\n## Issue 1 \u2014 `*` in resource paths matches across `/`\n\n### Background\n\nResource paths are matched using `github.com/gobwas/glob`. The call site is:\n\n```go\n// pkg/s3-proxy/authx/authentication/main.go\ng, err := glob.Compile(res.Path)\n```\n\n`glob.Compile` is called **without a separator argument**. Without a separator,\n`*` matches any character \u2014 including `/`. This means a pattern intended to protect\na single path segment actually matches across directory boundaries.\n\n### Example\n\nConsider a config with an open route and a protected route:\n\n```yaml\nresources:\n  # open \u2014 no auth required\n  - path: /upload/*/drafts/\n    methods: [PUT]\n    whiteList: true\n\n  # protected \u2014 basic auth required\n  - path: /upload/*/restricted/\n    methods: [PUT]\n    basic:\n      ...\n```\n\nThe intent is clear: `drafts` is open, `restricted` is protected. The `*` is meant\nto match a single path segment (the object identifier).\n\nHowever, because `*` crosses `/`, the pattern `/upload/*/drafts/` also matches:\n\n```\nPUT /upload/foo/drafts/../restricted/\n```\n\nThe path segment matched by `*` is `foo`, and then `drafts/../restricted/` is\nconsumed by the rest of the pattern \u2014 because without a separator, `*` is equivalent\nto `.*` and matches `/`, `.`, and everything else.\n\nThe result: an unauthenticated request is accepted by the open route.\n\n### Fix discussion\n\nThe straightforward fix is to pass `\u0027/\u0027` as the separator to `glob.Compile`:\n\n```go\n// before\ng, err := glob.Compile(res.Path)\n\n// after\ng, err := glob.Compile(res.Path, \u0027/\u0027)\n```\n\nWith a separator set:\n- `*` matches any sequence of non-`/` characters (a single path segment).\n- `**` matches any sequence including `/` (crossing path boundaries).\n\nThis fix closes the Issue 1 attack above: with a separator, `drafts/../restricted/`\nis more than one segment and no longer matches the pattern `/upload/*/drafts/`.\n\n#### Breaking change\n\nAny existing config that relies on `*` crossing `/` must be updated to `**`. For\nexample:\n\n```yaml\n# before \u2014 worked accidentally because * crossed /\n- path: /upload/*/drafts/\n\n# after \u2014 single-segment match (behaviour unchanged for single-segment IDs)\n- path: /upload/*/drafts/\n\n# after \u2014 multi-segment match (e.g. nested object IDs containing /)\n- path: /upload/**/drafts/\n```\n\nA migration note in the changelog would be needed.\n\n---\n\n## Issue 2 \u2014 Percent-encoded slashes bypass auth via segment collapsing\n\n### Background\n\nWith Fix 1 applied, `*` only matches a single path segment. However, the auth\nmiddleware matches against `r.URL.RequestURI()` \u2014 the **encoded** path \u2014 while the\nbucket handler uses `r.URL.Path` \u2014 the **decoded** path. A client can use `%2F`\nto make what looks like a single segment in the encoded URI decode into multiple\nsegments including a protected path component.\n\n### Example\n\nUsing the same config as Issue 1:\n\n```\nPUT /upload/foo%2Frestricted/drafts/\n```\n\nStep by step:\n\n1. `r.URL.RawPath` = `/upload/foo%2Frestricted/drafts/`\n2. `r.URL.Path` (decoded) = `/upload/foo/restricted/drafts/`\n3. Auth middleware calls `r.URL.RequestURI()` \u2192 returns the encoded form.\n4. With Fix 1\u0027s separator `/`, glob splits on the literal `/`. The segment between\n   the first and second slash is `foo%2Frestricted` \u2014 one token with no literal `/`\n   \u2014 so `*` matches it. Pattern `/upload/*/drafts/` fires.\n5. Open route \u2192 request proceeds without auth.\n6. Bucket handler uses `r.URL.Path` \u2192 S3 key is `upload/foo/restricted/drafts/\u2026`\n   \u2014 **written into the restricted namespace without credentials**.\n\n### Proof via integration test\n\nI added `TestPercentEncodedSlashBypass` to\n`pkg/s3-proxy/server/server_integration_test.go`. The test sends a complete\nmultipart PUT without credentials and asserts a 401 response. It currently fails\nwith **204** \u2014 the file is written in full to the restricted namespace without any\nauthentication.\n\n### Fix discussion\n\nThis issue has two fundamentally different classes of fix, each with a different\nstance on RFC 3986 compliance.\n\n#### Option A \u2014 Match auth against the decoded path (`r.URL.Path`)\n\nChange the auth middleware to use `r.URL.Path` instead of `r.URL.RequestURI()`:\n\n```go\n// before\nrequestURI := r.URL.RequestURI()\n\n// after\nrequestURI := r.URL.Path\n```\n\nBoth auth and the bucket handler now operate on the same decoded string, closing\nthe mismatch that enables the bypass.\n\n**Pros:** One-line change; no other code touched; closes the bypass completely.\n\n**Cons:** RFC 3986 non-compliant \u2014 `/foo%2Fbar/baz` and `/foo/bar/baz` become\nindistinguishable at the auth layer. A pattern like `/upload/*/drafts/` will match\nboth `PUT /upload/foo/drafts/` and `PUT /upload/foo%2F.../drafts/` identically\nafter decoding, making it impossible for operators to write a pattern that\ndistinguishes the two. Any path segment containing a literal `/` encoded as `%2F`\ncan never be matched as a single token by `*`.\n\n#### Option B \u2014 Use the raw path in both auth and key construction\n\nKeep `r.URL.RequestURI()` in the auth middleware (reverting the Option A change)\nand replace the bucket handler\u0027s decoded path extraction with `r.URL.EscapedPath()`\nstripped of the mount path prefix. The AWS SDK then handles percent-encoding the\nkey in the HTTP request to S3, with no manual segment splitting required.\n\nThis keeps `%2F` opaque at both layers: auth matches against the encoded form, and\nthe S3 key preserves the encoded characters verbatim.\n\n**Security mechanism:** the bypass attack (`PUT /upload/foo%2Frestricted/drafts/`)\nstill returns **204** \u2014 the open route genuinely matches, because\n`foo%2Frestricted` is one encoded segment and `*` accepts it. However, the key\nwritten to S3 is `upload/foo%2Frestricted/drafts/\u2026` \u2014 a distinct namespace from\n`upload/foo/restricted/drafts/\u2026`. The attacker cannot reach the protected prefix\nbecause `%2F` and `/` are treated as different characters all the way to storage.\n\n**AWS S3 compatibility confirmed:** S3 natively supports `%2F` in key names. A\nkey `upload/foo%2Fbar/file.txt` is stored and retrieved as a distinct object from\n`upload/foo/bar/file.txt`. All four operations (HEAD, GET, PUT, DELETE) work\ncorrectly with `%2F`-containing paths.\n\n**Pros:** RFC-compliant; `%2F` remains a meaningful encoding \u2014 `foo%2Fbar` is one\ntoken and `*` correctly matches it as a single segment; `/foo%2Fbar/baz` and\n`/foo/bar/baz` are distinct at both auth and storage layers; simpler than it\nsounds \u2014 no custom segment-splitting utility needed, just `r.URL.EscapedPath()` in\nthe handler.\nThe breaking change is **contained to config files, not clients**: the only clients that break\nare those relying on `*` crossing literal `/` \u2014 and those require a config change\nto `**` under any fix option. Clients that encode user input containing `/` as\n`%2F` in a path segment are preserved: `foo%2Fbar` is still one encoded segment,\nand `*` still matches it. Under Option A those same clients break \u2014 the decoded\nform splits into multiple segments that no longer match `*`. The required\nclient-side fix would be to filter or transform any `/` out of user input before\nbuilding the URL, which may not always be feasible if the `/` carries meaning.\n\n**Cons:** The auth middleware reverts to using the encoded path, which re-opens\nthe door to dot-segment bypass (Issue 3) if the path-cleaning middleware is not\nalso in place \u2014 the two fixes must be applied together.\n\nA note on the 204 response: a request like `PUT /upload/foo%2Frestricted/drafts/`\nreturns 204 under this option, which may look like a bypass at first glance. It is\nnot. If `%2F` carries meaning, `foo%2Frestricted` is a valid identifier\nindistinguishable from any other \u2014 the server has no basis to treat it as\nsuspicious. The correct security responsibility is to handle all inputs\nconsistently and safely, not to guess intent based on the content of user-provided\nvalues. The namespace separation guarantee satisfies that: whatever the client\nsends is handled the same way at both the auth and storage layers.\n\n#### Option C \u2014 Reject requests containing `%2F` in the path\n\nReturn 400 Bad Request for any request whose raw path contains `%2F`:\n\n```go\nif strings.Contains(r.URL.RawPath, \"%2F\") || strings.Contains(r.URL.RawPath, \"%2f\") {\n    http.Error(w, \"Bad Request\", http.StatusBadRequest)\n    return\n}\n```\n\n**Pros:** Simplest possible enforcement; eliminates the ambiguity entirely.\n\n**Cons:** Breaks any client that sends object names containing `/` encoded as\n`%2F`; rules out a legitimate and RFC-sanctioned use of percent-encoding.\n\n---\n\n## Issue 3 \u2014 Dot-dot segments bypass authentication with prefix patterns\n\n### Background\n\nIssues 1 and 2 both involve `*` (single-segment wildcard). A different class of\nbypass survives Fix 1 and Fix 2 when configs use prefix-style patterns with `**`\nat the end, such as `/open/**`. This is a natural and common pattern for \"allow\neverything under this prefix.\" The `**` token is explicitly designed to cross `/`,\nso `..` traversal within that prefix still reaches protected paths.\n\nNote that `%2F..%2F` encoded traversal is a variant of this issue: the decoded\nform (`/../`) contains dot segments that `**` can consume, as described in the\nroot cause section.\n\n### Example\n\nConsider this config:\n\n```yaml\nresources:\n  # protected \u2014 basic auth required for anything under /restricted/\n  - path: /restricted/**\n    methods: [PUT]\n    basic:\n      ...\n\n  # open \u2014 no auth required for anything under /open/\n  - path: /open/**\n    methods: [PUT]\n    whiteList: true\n```\n\nWithout any path normalization, the following request bypasses auth:\n\n```\nPUT /open/../restricted/secret.json\n```\n\nStep by step:\n\n1. Go\u0027s `net/url` resolves dot segments when parsing the request URI: `r.URL.Path`\n   is `/restricted/secret.json`. The raw form `../` is preserved only in\n   `r.URL.RawPath`.\n2. The auth middleware calls `r.URL.RequestURI()`, which returns the encoded\n   form \u2014 `/open/../restricted/secret.json` \u2014 and evaluates resources against that.\n3. `/restricted/**` does not match because the raw path does not start with `/restricted/`.\n4. `/open/**` matches: `**` is allowed to cross `/`, so it consumes `../restricted/secret.json`.\n5. The open route fires \u2014 no auth required \u2014 the request returns 204.\n6. The bucket handler reads `r.URL.Path` \u2014 already `/restricted/secret.json` \u2014 and\n   writes the file directly into the restricted namespace.\n\n**Confirmed against AWS S3**: the file lands at `restricted/secret.json` \u2014 not at\na key containing `../`. Go resolves the dot segments before the bucket handler runs,\nso the write goes straight into the protected prefix. This makes the attack more\nsevere than a key-naming anomaly: it is a direct, confirmed write into the\nrestricted namespace with no authentication.\n\n### Proof via integration test\n\nI added `TestPathTraversalDoubleStarPrefix` to\n`pkg/s3-proxy/server/server_integration_test.go`. It uses the exact config above\nand shows that, with a path-cleaning middleware applied **before** the auth\nmiddleware, the traversal returns 401 instead of 204:\n\n```go\n{\n    // /open/** still matches /open/../restricted/file because ** crosses \u0027/\u0027.\n    // cleanPathMiddleware resolves the path to /restricted/file first, which\n    // matches the protected resource -\u003e 401.\n    // Without cleanPathMiddleware this would return 204 (auth bypassed).\n    name:         \"traversal from open to restricted via ** prefix pattern is blocked\",\n    inputMethod:  \"PUT\",\n    inputURL:     \"http://localhost/open/../restricted/file.txt\",\n    expectedCode: 401,\n},\n```\n\n### Note on `%2E` (percent-encoded dots)\n\nGo\u0027s `net/http` decodes `%2E` \u2192 `.` in `r.URL.Path` before any middleware runs,\nso `%2E%2E` arrives as `..` by the time any of the options below apply. All options\noperate on the already-decoded `r.URL.Path` and therefore handle encoded dots\nwithout any extra work.\n\n### Fix discussion\n\nAll options below address the same root problem: `r.URL.RequestURI()` preserves\ndot segments while `r.URL.Path` has already resolved them, and auth sees the\nun-resolved form. The options differ in where the resolution happens and how\ninvasive the change is.\n\n#### Option A \u2014 Reject requests containing dot segments\n\nReject (400 Bad Request) any request whose decoded path contains `/./` or `/../`:\n\n```go\nfunc rejectDotSegmentsMiddleware(next http.Handler) http.Handler {\n    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n        p := r.URL.Path\n        if strings.Contains(p, \"/./\") || strings.Contains(p, \"/../\") ||\n            strings.HasSuffix(p, \"/.\") || strings.HasSuffix(p, \"/..\") {\n            http.Error(w, \"Bad Request\", http.StatusBadRequest)\n            return\n        }\n        next.ServeHTTP(w, r)\n    })\n}\n```\n\n**Pros:** Simple, explicit, no normalization side-effects.  \n**Cons:** Rejects requests that some clients may legitimately send (though dot\nsegments in HTTP paths are unusual and ill-advised).\n\n#### Option B \u2014 Use `path.Clean`\n\n```go\nfunc cleanPathMiddleware(next http.Handler) http.Handler {\n    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n        p := r.URL.Path\n        cleaned := path.Clean(p)\n        if cleaned != p {\n            r2 := r.Clone(r.Context())\n            r2.URL.Path = cleaned\n            r2.URL.RawPath = \"\"\n            next.ServeHTTP(w, r2)\n            return\n        }\n        next.ServeHTTP(w, r)\n    })\n}\n```\n\n`path.Clean` resolves `..` and `.`, collapses double slashes, and also removes\nthe trailing slash. The trailing-slash removal is a breaking change for any config\nthat uses paths ending in `/` \u2014 resource patterns, mount paths, or anything else\nmatched against the incoming path. A request to `/upload/foo/drafts/` would be\ncleaned to `/upload/foo/drafts`, and any pattern or handler that expects the\ntrailing slash would no longer match.\n\nThis can be mitigated by restoring the trailing slash after cleaning:\n\n```go\nif len(p) \u003e 1 \u0026\u0026 p[len(p)-1] == \u0027/\u0027 {\n    cleaned += \"/\"\n}\n```\n\n**Implementation note:** An approach that stores the cleaned path in the request\ncontext rather than modifying `r.URL.Path` and clearing `r.URL.RawPath` will not\nwork: both the auth middleware and the bucket handler read from `r.URL` directly,\nso a context-stored override is invisible to them.\n\n**Pros:** Uses the standard library; less custom code.  \n**Cons:** The trailing-slash removal is mitigable by restoring the trailing slash\nafter cleaning (as shown above), but it adds a correctness requirement to the\nmiddleware that is easy to overlook \u2014 omitting it silently breaks any config using\ntrailing-slash patterns, which is the default convention in s3-proxy examples and\ndocumentation.\n\n---\n\n## Interaction between Issue 2 and Issue 3 fixes\n\nThe choice made for Issue 2 affects the tradeoffs for Issue 3:\n\n- If **Option A** is chosen for Issue 2 (auth uses `r.URL.Path`), then dot segments\n  have already been resolved by Go before any middleware runs, so Issue 3 is\n  partially addressed without any additional middleware \u2014 but Option A\u0027s RFC\n  non-compliance tradeoff still applies.\n- If **Option B** is chosen for Issue 2 (raw path in both layers), the auth\n  middleware sees the encoded form, which still contains literal `../` dot segments.\n  Issue 3 is **not** addressed by Option B alone \u2014 one of the Issue 3 options must\n  also be applied. Importantly, whichever dot-segment option is chosen must clear\n  `r.URL.RawPath` when it modifies the path, so that `r.URL.EscapedPath()` in the\n  bucket handler reflects the cleaned path. This works naturally with both Issue 3\n  options (which operate on `r.URL.Path` and clear `RawPath`), and the fixes\n  compose cleanly in practice.\n- In all cases, an explicit dot-segment policy (reject or resolve) is clearer than\n  relying on Go\u0027s implicit resolution as a side-effect.\n\n---\n\n## Combined effect\n\n| Attack | Issue 1 fix | Issue 2 fix | Issue 3 fix |\n|---|---|---|---|\n| `*` crosses `/` (`/upload/*/drafts/` matches `../restricted/`) | Fixed | \u2014 | \u2014 |\n| `%2F` segment injection (`foo%2Frestricted/drafts/` bypasses `*/restricted/`) | No | Fixed | \u2014 |\n| `..` traversal via `**` prefix pattern (`/open/../restricted/`) | No | No | Fixed |\n| `%2F..%2F` encoded traversal (decoded `..` consumed by `**`) | No | Fixed* | Fixed |\n\n\\* Issue 2\u0027s fix (auth using decoded path, Option A) also prevents `%2F`-encoded\ndot segments from being treated as opaque tokens, so the decoded `..` is visible\nto the glob before matching.\n\n---\n\n## Suggested combination of fixes\n\n- **Issue 1:** Pass `\u0027/\u0027` as the separator to `glob.Compile`. Unambiguously correct; `*` should never have crossed `/`.\n- **Issue 2:** Option B \u2014 use the raw path (`r.URL.EscapedPath()`) in both the auth middleware and the bucket handler. This is the only option that avoids client-side breaking changes for operators whose clients encode user input containing `/` as `%2F`. The security guarantee is namespace separation, which is the right model: the server has no basis to distinguish a legitimate `%2F`-encoded identifier from one that \"looks like\" a traversal attempt, so consistent handling at both layers is the correct responsibility boundary.\n- **Issue 3:** Option B \u2014 `cleanPathMiddleware` using `path.Clean` with trailing slash restored. Required when using Issue 2 Option B, since auth still sees the raw path. The two fixes compose cleanly: the middleware modifies `r.URL.Path` and clears `r.URL.RawPath`, so `r.URL.EscapedPath()` in the bucket handler reflects the cleaned path.\n\nThe combined breaking change is limited to config files: operators need to replace `*` with `**` wherever multi-segment wildcard matching is intended. Client-facing URLs require no changes.\n\n---\n\n\n## Resources\n\n- `pkg/s3-proxy/authx/authentication/main.go` \u2014 `findResource`, the `glob.Compile` call\n- `pkg/s3-proxy/server/server_integration_test.go` \u2014 `TestPercentEncodedSlashBypass`, `TestPathTraversalDoubleStarPrefix`, `TestPathCleaning`\n- `github.com/gobwas/glob` \u2014 separator documentation\n- RFC 3986 \u00a72.2 \u2014 equivalence of percent-encoded reserved characters\n- RFC 3986 \u00a73.3 \u2014 path segment semantics\n- RFC 3986 \u00a75.2.4 \u2014 dot-segment resolution in URI paths",
  "id": "GHSA-rfgq-wgg8-662p",
  "modified": "2026-05-13T14:19:05Z",
  "published": "2026-05-05T18:52:56Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/oxyno-zeta/s3-proxy/security/advisories/GHSA-rfgq-wgg8-662p"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-42882"
    },
    {
      "type": "WEB",
      "url": "https://github.com/oxyno-zeta/s3-proxy/commit/1320e4abd46ad18c2851fedde50dbb79df8b7a51"
    },
    {
      "type": "WEB",
      "url": "https://github.com/oxyno-zeta/s3-proxy/commit/af5ff57d8c6022459495b8fb50130073bca7b48a"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/oxyno-zeta/s3-proxy"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:L",
      "type": "CVSS_V3"
    }
  ],
  "summary": "S3-Proxy has Security Issues in its Resource Path Matching Implementation"
}

GHSA-RFH6-J3V9-HR67

Vulnerability from github – Published: 2023-12-15 06:30 – Updated: 2023-12-22 18:30
VLAI
Details

ITPison OMICARD EDM has a path traversal vulnerability within its parameter “FileName” in a specific function. An unauthenticated remote attacker can exploit this vulnerability to bypass authentication and download arbitrary system files.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2023-48373"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-22"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2023-12-15T05:15:08Z",
    "severity": "HIGH"
  },
  "details": "ITPison OMICARD EDM has a path traversal vulnerability within its parameter \u201cFileName\u201d in a specific function. An unauthenticated remote attacker can exploit this vulnerability to bypass authentication and download arbitrary system files.",
  "id": "GHSA-rfh6-j3v9-hr67",
  "modified": "2023-12-22T18:30:30Z",
  "published": "2023-12-15T06:30:25Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2023-48373"
    },
    {
      "type": "WEB",
      "url": "https://www.twcert.org.tw/tw/cp-132-7592-998bf-1.html"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-RFHC-J38X-F7QX

Vulnerability from github – Published: 2025-04-01 03:31 – Updated: 2025-04-01 03:31
VLAI
Details

A vulnerability, which was classified as critical, has been found in GuoMinJim PersonManage 1.0. This issue affects the function preHandle of the file /login/. The manipulation of the argument Request leads to path traversal. The attack may be initiated remotely. The exploit has been disclosed to the public and may be used. This product takes the approach of rolling releases to provide continious delivery. Therefore, version details for affected and updated releases are not available.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2025-3043"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-22"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2025-04-01T01:15:21Z",
    "severity": "MODERATE"
  },
  "details": "A vulnerability, which was classified as critical, has been found in GuoMinJim PersonManage 1.0. This issue affects the function preHandle of the file /login/. The manipulation of the argument Request leads to path traversal. The attack may be initiated remotely. The exploit has been disclosed to the public and may be used. This product takes the approach of rolling releases to provide continious delivery. Therefore, version details for affected and updated releases are not available.",
  "id": "GHSA-rfhc-j38x-f7qx",
  "modified": "2025-04-01T03:31:32Z",
  "published": "2025-04-01T03:31:32Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2025-3043"
    },
    {
      "type": "WEB",
      "url": "https://github.com/GuoMinJim/PersonManage/issues/7"
    },
    {
      "type": "WEB",
      "url": "https://github.com/GuoMinJim/PersonManage/issues/7#issue-2939940887"
    },
    {
      "type": "WEB",
      "url": "https://vuldb.com/?ctiid.302105"
    },
    {
      "type": "WEB",
      "url": "https://vuldb.com/?id.302105"
    },
    {
      "type": "WEB",
      "url": "https://vuldb.com/?submit.524949"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "type": "CVSS_V3"
    },
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X",
      "type": "CVSS_V4"
    }
  ]
}

GHSA-RFJ2-Q3H3-HM5J

Vulnerability from github – Published: 2022-09-16 17:12 – Updated: 2022-09-21 19:53
VLAI
Summary
Cargo extracting malicious crates can corrupt arbitrary files
Details

The Rust Security Response WG was notified that Cargo did not prevent extracting some malformed packages downloaded from alternate registries. An attacker able to upload packages to an alternate registry could corrupt arbitary files when Cargo downloaded the package.

The severity of this vulnerability is "low" for users of alternate registries. Users relying on crates.io are not affected.

Note that by design Cargo allows code execution at build time, due to build scripts and procedural macros. The vulnerabilities in this advisory allow performing a subset of the possible damage in a harder to track down way. Your dependencies must still be trusted if you want to be protected from attacks, as it's possible to perform the same attacks with build scripts and procedural macros.

Arbitrary file corruption

After a package is downloaded, Cargo extracts its source code in the ~/.cargo folder on disk, making it available to the Rust projects it builds. To record when an extraction is successfull, Cargo writes "ok" to the .cargo-ok file at the root of the extracted source code once it extracted all the files.

It was discovered that Cargo allowed packages to contain a .cargo-ok symbolic link, which Cargo would extract. Then, when Cargo attempted to write "ok" into .cargo-ok, it would actually replace the first two bytes of the file the symlink pointed to with ok. This would allow an attacker to corrupt one file on the machine using Cargo to extract the package.

Affected versions

The vulnerability is present in all versions of Cargo. Rust 1.64, to be released on September 22nd, will include a fix for it.

Since the vulnerability is just a more limited way to accomplish what a malicious build scripts or procedural macros can do, we decided not to publish Rust point releases backporting the security fix. Patch files are available for Rust 1.63.0 are available in the wg-security-response repository for people building their own toolchain.

Mitigations

We recommend users of alternate registries to excercise care in which package they download, by only including trusted dependencies in their projects. Please note that even with these vulnerabilities fixed, by design Cargo allows arbitrary code execution at build time thanks to build scripts and procedural macros: a malicious dependency will be able to cause damage regardless of these vulnerabilities.

crates.io implemented server-side checks to reject these kinds of packages years ago, and there are no packages on crates.io exploiting these vulnerabilities. crates.io users still need to excercise care in choosing their dependencies though, as remote code execution is allowed by design there as well.

Acknowledgements

We want to thank Ori Hollander from JFrog Security Research for responsibly disclosing this to us according to the Rust security policy.

We also want to thank Josh Triplett for developing the fixes, Weihang Lo for developing the tests, and Pietro Albini for writing this advisory. The disclosure was coordinated by Pietro Albini and Josh Stone.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "crates.io",
        "name": "cargo"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "0.65.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "crates.io",
        "name": "cargo"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0.66.0"
            },
            {
              "fixed": "0.67.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ],
      "versions": [
        "0.66.0"
      ]
    }
  ],
  "aliases": [
    "CVE-2022-36113"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-22",
      "CWE-59"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2022-09-16T17:12:30Z",
    "nvd_published_at": "2022-09-14T18:15:00Z",
    "severity": "LOW"
  },
  "details": "The Rust Security Response WG was notified that Cargo did not prevent extracting some malformed packages downloaded from alternate registries. An attacker able to upload packages to an alternate registry could corrupt arbitary files when Cargo downloaded the package.\n\nThe severity of this vulnerability is \"low\" for users of alternate registries. Users relying on crates.io are not affected.\n\nNote that **by design** Cargo allows code execution at build time, due to build scripts and procedural macros. The vulnerabilities in this advisory allow performing a subset of the possible damage in a harder to track down way. Your dependencies must still be trusted if you want to be protected from attacks, as it\u0027s possible to perform the same attacks with build scripts and procedural macros.\n\n## Arbitrary file corruption\n\nAfter a package is downloaded, Cargo extracts its source code in the `~/.cargo` folder on disk, making it available to the Rust projects it builds. To record when an extraction is successfull, Cargo writes \"ok\" to the `.cargo-ok` file at the root of the extracted source code once it extracted all the files.\n\nIt was discovered that Cargo allowed packages to contain a `.cargo-ok` *symbolic link*, which Cargo would extract. Then, when Cargo attempted to write \"ok\" into `.cargo-ok`, it would actually replace the first two bytes of the file the symlink pointed to with `ok`. This would allow an attacker to corrupt one file on the machine using Cargo to extract the package.\n\n## Affected versions\n\nThe vulnerability is present in all versions of Cargo. Rust 1.64, to be released on September 22nd, will include a fix for it.\n\nSince the vulnerability is just a more limited way to accomplish what a malicious build scripts or procedural macros can do, we decided not to publish Rust point releases backporting the security fix. Patch files are available for Rust 1.63.0 are available [in the wg-security-response repository][patches] for people building their own toolchain.\n\n## Mitigations\n\nWe recommend users of alternate registries to excercise care in which package they download, by only including trusted dependencies in their projects. Please note that even with these vulnerabilities fixed, by design Cargo allows arbitrary code execution at build time thanks to build scripts and procedural macros: a malicious dependency will be able to cause damage regardless of these vulnerabilities.\n\ncrates.io implemented server-side checks to reject these kinds of packages years ago, and there are no packages on crates.io exploiting these vulnerabilities. crates.io users still need to excercise care in choosing their dependencies though, as remote code execution is allowed by design there as well.\n\n## Acknowledgements\n\nWe want to thank Ori Hollander from JFrog Security Research for responsibly disclosing this to us according to the [Rust security policy][policy].\n\nWe also want to thank Josh Triplett for developing the fixes, Weihang Lo for developing the tests, and Pietro Albini for writing this advisory. The disclosure was coordinated by Pietro Albini and Josh Stone.\n\n[policy]: https://www.rust-lang.org/policies/security\n[patches]: https://github.com/rust-lang/wg-security-response/tree/master/patches",
  "id": "GHSA-rfj2-q3h3-hm5j",
  "modified": "2022-09-21T19:53:20Z",
  "published": "2022-09-16T17:12:30Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/rust-lang/cargo/security/advisories/GHSA-rfj2-q3h3-hm5j"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2022-36113"
    },
    {
      "type": "WEB",
      "url": "https://github.com/rust-lang/cargo/commit/15f1e4b0bf4b4fc20369e0a85d9b77957c4dd52a"
    },
    {
      "type": "WEB",
      "url": "https://github.com/rust-lang/cargo/commit/97b80919e404b0768ea31ae329c3b4da54bed05a"
    },
    {
      "type": "WEB",
      "url": "https://github.com/rust-lang/cargo/commit/dafe4a7ea016739680ec7998aebe1bc6de131a5b"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/rust-lang/cargo"
    },
    {
      "type": "WEB",
      "url": "https://security.gentoo.org/glsa/202210-09"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:H/PR:H/UI:R/S:U/C:L/I:L/A:L",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Cargo extracting malicious crates can corrupt arbitrary files"
}

GHSA-RFJV-Q482-66QF

Vulnerability from github – Published: 2022-04-11 00:00 – Updated: 2022-04-19 00:01
VLAI
Details

InHand Networks InRouter 900 Industrial 4G Router before v1.0.0.r11700 was discovered to contain an arbitrary file read via the function sub_177E0.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2022-27279"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-22"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2022-04-10T21:15:00Z",
    "severity": "HIGH"
  },
  "details": "InHand Networks InRouter 900 Industrial 4G Router before v1.0.0.r11700 was discovered to contain an arbitrary file read via the function sub_177E0.",
  "id": "GHSA-rfjv-q482-66qf",
  "modified": "2022-04-19T00:01:26Z",
  "published": "2022-04-11T00:00:20Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2022-27279"
    },
    {
      "type": "WEB",
      "url": "https://drive.google.com/drive/folders/1MPtl6pGa7GMIT1-jg69YUGSQdVTfbnay?usp=sharing"
    },
    {
      "type": "WEB",
      "url": "https://github.com/wu610777031/IoT_Hunter/blob/main/Inhand%20InRouter%20900%20Industrial%204G%20Router%20%20Vulnerabilities(Arbitrary%20File%20Deletion%20and%20Read).pdf"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-RFQQ-WQ6W-72JM

Vulnerability from github – Published: 2024-05-16 09:33 – Updated: 2025-04-08 22:00
VLAI
Summary
MLflow has a Local File Read/Path Traversal bypass
Details

A path traversal vulnerability exists in mlflow/mlflow version 2.11.0, identified as a bypass for the previously addressed CVE-2023-6909. The vulnerability arises from the application's handling of artifact URLs, where a '#' character can be used to insert a path into the fragment, effectively skipping validation. This allows an attacker to construct a URL that, when processed, ignores the protocol scheme and uses the provided path for filesystem access. As a result, an attacker can read arbitrary files, including sensitive information such as SSH and cloud keys, by exploiting the way the application converts the URL into a filesystem path. The issue stems from insufficient validation of the fragment portion of the URL, leading to arbitrary file read through path traversal.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "PyPI",
        "name": "mlflow"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "2.9.2"
            },
            {
              "fixed": "2.12.1"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2024-3848"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-22",
      "CWE-29"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2024-05-16T17:47:00Z",
    "nvd_published_at": "2024-05-16T09:15:14Z",
    "severity": "HIGH"
  },
  "details": "A path traversal vulnerability exists in mlflow/mlflow version 2.11.0, identified as a bypass for the previously addressed CVE-2023-6909. The vulnerability arises from the application\u0027s handling of artifact URLs, where a \u0027#\u0027 character can be used to insert a path into the fragment, effectively skipping validation. This allows an attacker to construct a URL that, when processed, ignores the protocol scheme and uses the provided path for filesystem access. As a result, an attacker can read arbitrary files, including sensitive information such as SSH and cloud keys, by exploiting the way the application converts the URL into a filesystem path. The issue stems from insufficient validation of the fragment portion of the URL, leading to arbitrary file read through path traversal.",
  "id": "GHSA-rfqq-wq6w-72jm",
  "modified": "2025-04-08T22:00:26Z",
  "published": "2024-05-16T09:33:08Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2024-3848"
    },
    {
      "type": "WEB",
      "url": "https://github.com/mlflow/mlflow/commit/f8d51e21523238280ebcfdb378612afd7844eca8"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/mlflow/mlflow"
    },
    {
      "type": "WEB",
      "url": "https://github.com/pypa/advisory-database/tree/main/vulns/mlflow/PYSEC-2024-244.yaml"
    },
    {
      "type": "WEB",
      "url": "https://huntr.com/bounties/8d5aadaa-522f-4839-b41b-d7da362dd610"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "MLflow has a Local File Read/Path Traversal bypass"
}

GHSA-RFV8-2G5X-RM48

Vulnerability from github – Published: 2026-02-11 15:30 – Updated: 2026-02-12 15:32
VLAI
Details

A path traversal vulnerability has been reported to affect Qsync Central. If a remote attacker gains a user account, they can then exploit the vulnerability to read the contents of unexpected files or system data.

We have already fixed the vulnerability in the following version: Qsync Central 5.0.0.4 ( 2026/01/20 ) and later

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2025-68406"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-22"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2026-02-11T13:15:58Z",
    "severity": "LOW"
  },
  "details": "A path traversal vulnerability has been reported to affect Qsync Central. If a remote attacker gains a user account, they can then exploit the vulnerability to read the contents of unexpected files or system data.\n\nWe have already fixed the vulnerability in the following version:\nQsync Central 5.0.0.4 ( 2026/01/20 ) and later",
  "id": "GHSA-rfv8-2g5x-rm48",
  "modified": "2026-02-12T15:32:43Z",
  "published": "2026-02-11T15:30:26Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2025-68406"
    },
    {
      "type": "WEB",
      "url": "https://www.qnap.com/en/security-advisory/qsa-26-02"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N",
      "type": "CVSS_V3"
    },
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N/E:U/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X",
      "type": "CVSS_V4"
    }
  ]
}

GHSA-RFX7-4XW3-GH4M

Vulnerability from github – Published: 2026-03-11 00:22 – Updated: 2026-03-11 00:22
VLAI
Summary
@appium/support has a Zip Slip arbitrary file write in its ZIP extraction
Details

Summary

@appium/support contains a ZIP extraction implementation (extractAllTo() via ZipExtractor.extract()) with a path traversal (Zip Slip) check that is non-functional. The check at line 88 of packages/support/lib/zip.js creates an Error object but never throws it, allowing malicious ZIP entries with ../ path components to write files outside the intended destination directory. This affects all JS-based extractions (the default code path), not only those using the fileNamesEncoding option.

Severity

Medium (CVSS 3.1: 6.5)

CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N

  • Attack Vector: Network — malicious ZIP files can be supplied over the network (e.g., app packages via URL)
  • Attack Complexity: Low — no special conditions required beyond providing a crafted ZIP
  • Privileges Required: None — no authentication needed to supply a malicious archive
  • User Interaction: Required — a user or automation system must initiate extraction of the attacker's archive
  • Scope: Unchanged — impact stays within the file system permissions of the Appium process
  • Confidentiality Impact: None — the vulnerability enables file writes, not reads
  • Integrity Impact: High — arbitrary file write to any location writable by the process
  • Availability Impact: None — no direct availability impact

Affected Component

  • packages/support/lib/zip.jsZipExtractor.extract() (line 88) and ZipExtractor.extractEntry() (lines 111-145)

CWE

  • CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

Description

Missing throw renders Zip Slip protection non-functional

The ZipExtractor.extract() method contains a path traversal check intended to prevent Zip Slip attacks. However, the check creates an Error object as a bare expression without the throw keyword, making it a no-op:

// packages/support/lib/zip.js, lines 80-93
const destDir = path.dirname(path.join(dir, fileName));
try {
    await fs.mkdir(destDir, {recursive: true});

    const canonicalDestDir = await fs.realpath(destDir);
    const relativeDestDir = path.relative(dir, canonicalDestDir);

    if (relativeDestDir.split(path.sep).includes('..')) {
        new Error(                                          // <-- BUG: missing `throw`
            `Out of bound path "${canonicalDestDir}" found while processing file ${fileName}`
        );
    }

    await this.extractEntry(entry);   // extraction proceeds unconditionally

The presence of a well-formatted error message and surrounding try/catch block (lines 95-99) strongly suggests the throw keyword was accidentally omitted.

yauzl does not provide its own traversal protection

The upstream yauzl library explicitly does not offer path traversal protection regardless of the decodeStrings setting. This means the vulnerability affects all JS-based extractions through ZipExtractor, not only those where fileNamesEncoding is set. The fileNamesEncoding option bypasses yauzl's string decoding (decodeStrings: false), but even with decodeStrings: true, yauzl passes through ../ path components without rejection.

Unprotected write sinks

The extractEntry method writes to attacker-controlled paths with no additional validation:

// packages/support/lib/zip.js, lines 111-145
const fileName = this.extractFileName(entry);
const dest = path.join(dir, fileName);         // resolves ../pwned.txt outside dir
// ...
await fs.symlink(link, dest);                  // symlink creation (line 143)
await pipeline(readStream, fs.createWriteStream(dest, {mode: procMode}));  // file write (line 145)

Additionally, _extractEntryTo() (line 263) used by readEntries() has no traversal check at all:

const dstPath = path.resolve(destDir, entry.fileName);  // no validation

Default code path is vulnerable

The extractAllTo() function uses the JS-based ZipExtractor by default. The system unzip fallback (useSystemUnzip: true) must be explicitly enabled and only provides protection if the system binary succeeds:

// packages/support/lib/zip.js, lines 203-210
if (opts.useSystemUnzip) {
    try {
        await extractWithSystemUnzip(zipFilePath, dir);
        return;
    } catch (err) {
        log.warn('unzip failed; falling back to JS: %s', err.stderr || err.message);
        // Falls through to the vulnerable JS implementation
    }
}

Proof of Concept

# 1) Install deps for the support package
cd packages/support
npm install --omit=dev --ignore-scripts --no-audit --no-fund --workspaces=false

# 2) Create a malicious ZIP containing a traversal entry
export WORK=/tmp/appium_zip_slip_poc
rm -rf "$WORK" && mkdir -p "$WORK/dest"
python3 - <<'PY'
import zipfile, os
work = os.environ['WORK']
zip_path = os.path.join(work, 'evil.zip')
with zipfile.ZipFile(zip_path, 'w') as z:
    z.writestr('../pwned.txt', 'ZIPSLIP_MARKER')
print('created', zip_path)
PY

# 3) Extract with the JS implementation (default path, no fileNamesEncoding needed)
node --experimental-default-type=module --experimental-specifier-resolution=node - <<'NODE'
import path from 'node:path';
import fs from 'node:fs/promises';
import { extractAllTo } from './lib/zip.js';

const work = process.env.WORK;
const zipPath = path.join(work, 'evil.zip');
const dest = path.join(work, 'dest');

await extractAllTo(zipPath, dest, { useSystemUnzip: false });

const outside = path.join(work, 'pwned.txt');
console.log('outside exists?', await fs.stat(outside).then(() => true, () => false));
console.log('outside content:', (await fs.readFile(outside, 'utf8')).trim());
NODE
# Expected output:
# outside exists? true
# outside content: ZIPSLIP_MARKER

Impact

  • Arbitrary file write: An attacker can write files to any location writable by the Appium process, outside the intended extraction directory.
  • Arbitrary symlink creation: Malicious ZIP entries with symlink attributes can create symlinks pointing to arbitrary targets, enabling further attacks on subsequent file operations.
  • Potential code execution: By overwriting scripts, configuration files, node_modules contents, cron jobs, shell profiles, or other executable artifacts, arbitrary file write can chain into remote code execution.
  • Affects all JS-based extractions: The default code path (without useSystemUnzip: true) is vulnerable regardless of whether fileNamesEncoding is set.

Recommended Remediation

Option 1: Add the missing throw keyword (preferred — minimal fix)

// packages/support/lib/zip.js, line 88
if (relativeDestDir.split(path.sep).includes('..')) {
    throw new Error(   // Add `throw`
        `Out of bound path "${canonicalDestDir}" found while processing file ${fileName}`
    );
}

This is the lowest-risk fix: it restores the clearly intended behavior of the existing check. The try/catch block at lines 95-99 will catch the error, set canceled = true, close the zip, and reject the promise — exactly the designed error-handling flow.

Option 2: Add traversal protection to _extractEntryTo as well

The _extractEntryTo function (line 262) also lacks a traversal check. For defense-in-depth, add validation there too:

async function _extractEntryTo(zipFile, entry, destDir) {
    const dstPath = path.resolve(destDir, entry.fileName);
    const canonicalDest = path.resolve(dstPath);
    const canonicalDestDir = path.resolve(destDir);
    if (!canonicalDest.startsWith(canonicalDestDir + path.sep) && canonicalDest !== canonicalDestDir) {
        throw new Error(
            `Out of bound path "${canonicalDest}" found while processing file ${entry.fileName}`
        );
    }
    // ... rest of function
}

Credit

This vulnerability was discovered and reported by bugbunny.ai.

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 7.0.5"
      },
      "package": {
        "ecosystem": "npm",
        "name": "@appium/support"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "7.0.6"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-30973"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-22"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-03-11T00:22:38Z",
    "nvd_published_at": "2026-03-10T18:18:56Z",
    "severity": "MODERATE"
  },
  "details": "## Summary\n\n`@appium/support` contains a ZIP extraction implementation (`extractAllTo()` via `ZipExtractor.extract()`) with a path traversal (Zip Slip) check that is non-functional. The check at line 88 of `packages/support/lib/zip.js` creates an `Error` object but never throws it, allowing malicious ZIP entries with `../` path components to write files outside the intended destination directory. This affects all JS-based extractions (the default code path), not only those using the `fileNamesEncoding` option.\n\n## Severity\n\n**Medium** (CVSS 3.1: 6.5)\n\n`CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N`\n\n- **Attack Vector:** Network \u2014 malicious ZIP files can be supplied over the network (e.g., app packages via URL)\n- **Attack Complexity:** Low \u2014 no special conditions required beyond providing a crafted ZIP\n- **Privileges Required:** None \u2014 no authentication needed to supply a malicious archive\n- **User Interaction:** Required \u2014 a user or automation system must initiate extraction of the attacker\u0027s archive\n- **Scope:** Unchanged \u2014 impact stays within the file system permissions of the Appium process\n- **Confidentiality Impact:** None \u2014 the vulnerability enables file writes, not reads\n- **Integrity Impact:** High \u2014 arbitrary file write to any location writable by the process\n- **Availability Impact:** None \u2014 no direct availability impact\n\n## Affected Component\n\n- `packages/support/lib/zip.js` \u2014 `ZipExtractor.extract()` (line 88) and `ZipExtractor.extractEntry()` (lines 111-145)\n\n## CWE\n\n- **CWE-22**: Improper Limitation of a Pathname to a Restricted Directory (\u0027Path Traversal\u0027)\n\n## Description\n\n### Missing `throw` renders Zip Slip protection non-functional\n\nThe `ZipExtractor.extract()` method contains a path traversal check intended to prevent Zip Slip attacks. However, the check creates an `Error` object as a bare expression without the `throw` keyword, making it a no-op:\n\n```javascript\n// packages/support/lib/zip.js, lines 80-93\nconst destDir = path.dirname(path.join(dir, fileName));\ntry {\n    await fs.mkdir(destDir, {recursive: true});\n\n    const canonicalDestDir = await fs.realpath(destDir);\n    const relativeDestDir = path.relative(dir, canonicalDestDir);\n\n    if (relativeDestDir.split(path.sep).includes(\u0027..\u0027)) {\n        new Error(                                          // \u003c-- BUG: missing `throw`\n            `Out of bound path \"${canonicalDestDir}\" found while processing file ${fileName}`\n        );\n    }\n\n    await this.extractEntry(entry);   // extraction proceeds unconditionally\n```\n\nThe presence of a well-formatted error message and surrounding try/catch block (lines 95-99) strongly suggests the `throw` keyword was accidentally omitted.\n\n### yauzl does not provide its own traversal protection\n\nThe upstream `yauzl` library explicitly [does not offer path traversal protection](https://github.com/thejoshwolfe/yauzl#no-path-traversal-protection) regardless of the `decodeStrings` setting. This means the vulnerability affects **all** JS-based extractions through `ZipExtractor`, not only those where `fileNamesEncoding` is set. The `fileNamesEncoding` option bypasses yauzl\u0027s string decoding (`decodeStrings: false`), but even with `decodeStrings: true`, yauzl passes through `../` path components without rejection.\n\n### Unprotected write sinks\n\nThe `extractEntry` method writes to attacker-controlled paths with no additional validation:\n\n```javascript\n// packages/support/lib/zip.js, lines 111-145\nconst fileName = this.extractFileName(entry);\nconst dest = path.join(dir, fileName);         // resolves ../pwned.txt outside dir\n// ...\nawait fs.symlink(link, dest);                  // symlink creation (line 143)\nawait pipeline(readStream, fs.createWriteStream(dest, {mode: procMode}));  // file write (line 145)\n```\n\nAdditionally, `_extractEntryTo()` (line 263) used by `readEntries()` has no traversal check at all:\n\n```javascript\nconst dstPath = path.resolve(destDir, entry.fileName);  // no validation\n```\n\n### Default code path is vulnerable\n\nThe `extractAllTo()` function uses the JS-based `ZipExtractor` by default. The system unzip fallback (`useSystemUnzip: true`) must be explicitly enabled and only provides protection if the system binary succeeds:\n\n```javascript\n// packages/support/lib/zip.js, lines 203-210\nif (opts.useSystemUnzip) {\n    try {\n        await extractWithSystemUnzip(zipFilePath, dir);\n        return;\n    } catch (err) {\n        log.warn(\u0027unzip failed; falling back to JS: %s\u0027, err.stderr || err.message);\n        // Falls through to the vulnerable JS implementation\n    }\n}\n```\n\n## Proof of Concept\n\n```bash\n# 1) Install deps for the support package\ncd packages/support\nnpm install --omit=dev --ignore-scripts --no-audit --no-fund --workspaces=false\n\n# 2) Create a malicious ZIP containing a traversal entry\nexport WORK=/tmp/appium_zip_slip_poc\nrm -rf \"$WORK\" \u0026\u0026 mkdir -p \"$WORK/dest\"\npython3 - \u003c\u003c\u0027PY\u0027\nimport zipfile, os\nwork = os.environ[\u0027WORK\u0027]\nzip_path = os.path.join(work, \u0027evil.zip\u0027)\nwith zipfile.ZipFile(zip_path, \u0027w\u0027) as z:\n    z.writestr(\u0027../pwned.txt\u0027, \u0027ZIPSLIP_MARKER\u0027)\nprint(\u0027created\u0027, zip_path)\nPY\n\n# 3) Extract with the JS implementation (default path, no fileNamesEncoding needed)\nnode --experimental-default-type=module --experimental-specifier-resolution=node - \u003c\u003c\u0027NODE\u0027\nimport path from \u0027node:path\u0027;\nimport fs from \u0027node:fs/promises\u0027;\nimport { extractAllTo } from \u0027./lib/zip.js\u0027;\n\nconst work = process.env.WORK;\nconst zipPath = path.join(work, \u0027evil.zip\u0027);\nconst dest = path.join(work, \u0027dest\u0027);\n\nawait extractAllTo(zipPath, dest, { useSystemUnzip: false });\n\nconst outside = path.join(work, \u0027pwned.txt\u0027);\nconsole.log(\u0027outside exists?\u0027, await fs.stat(outside).then(() =\u003e true, () =\u003e false));\nconsole.log(\u0027outside content:\u0027, (await fs.readFile(outside, \u0027utf8\u0027)).trim());\nNODE\n# Expected output:\n# outside exists? true\n# outside content: ZIPSLIP_MARKER\n```\n\n## Impact\n\n- **Arbitrary file write**: An attacker can write files to any location writable by the Appium process, outside the intended extraction directory.\n- **Arbitrary symlink creation**: Malicious ZIP entries with symlink attributes can create symlinks pointing to arbitrary targets, enabling further attacks on subsequent file operations.\n- **Potential code execution**: By overwriting scripts, configuration files, `node_modules` contents, cron jobs, shell profiles, or other executable artifacts, arbitrary file write can chain into remote code execution.\n- **Affects all JS-based extractions**: The default code path (without `useSystemUnzip: true`) is vulnerable regardless of whether `fileNamesEncoding` is set.\n\n## Recommended Remediation\n\n### Option 1: Add the missing `throw` keyword (preferred \u2014 minimal fix)\n\n```javascript\n// packages/support/lib/zip.js, line 88\nif (relativeDestDir.split(path.sep).includes(\u0027..\u0027)) {\n    throw new Error(   // Add `throw`\n        `Out of bound path \"${canonicalDestDir}\" found while processing file ${fileName}`\n    );\n}\n```\n\nThis is the lowest-risk fix: it restores the clearly intended behavior of the existing check. The try/catch block at lines 95-99 will catch the error, set `canceled = true`, close the zip, and reject the promise \u2014 exactly the designed error-handling flow.\n\n### Option 2: Add traversal protection to `_extractEntryTo` as well\n\nThe `_extractEntryTo` function (line 262) also lacks a traversal check. For defense-in-depth, add validation there too:\n\n```javascript\nasync function _extractEntryTo(zipFile, entry, destDir) {\n    const dstPath = path.resolve(destDir, entry.fileName);\n    const canonicalDest = path.resolve(dstPath);\n    const canonicalDestDir = path.resolve(destDir);\n    if (!canonicalDest.startsWith(canonicalDestDir + path.sep) \u0026\u0026 canonicalDest !== canonicalDestDir) {\n        throw new Error(\n            `Out of bound path \"${canonicalDest}\" found while processing file ${entry.fileName}`\n        );\n    }\n    // ... rest of function\n}\n```\n\n## Credit\n\nThis vulnerability was discovered and reported by [bugbunny.ai](https://bugbunny.ai).",
  "id": "GHSA-rfx7-4xw3-gh4m",
  "modified": "2026-03-11T00:22:38Z",
  "published": "2026-03-11T00:22:38Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/appium/appium/security/advisories/GHSA-rfx7-4xw3-gh4m"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-30973"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/appium/appium"
    },
    {
      "type": "WEB",
      "url": "https://github.com/appium/appium/releases/tag/@appium/support@7.0.6"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "@appium/support has a Zip Slip arbitrary file write in its ZIP extraction"
}

GHSA-RFXV-8GHR-G333

Vulnerability from github – Published: 2022-05-17 01:49 – Updated: 2022-05-17 01:49
VLAI
Details

Directory traversal vulnerability in the web player in NeoAxis NeoAxis web player 1.4 and earlier allows user-assisted remote attackers to write arbitrary files via a .. (dot dot) in a filename in the neoaxis_web_application_win32.zip ZIP archive.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2012-0907"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-22"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2012-01-20T17:55:00Z",
    "severity": "MODERATE"
  },
  "details": "Directory traversal vulnerability in the web player in NeoAxis NeoAxis web player 1.4 and earlier allows user-assisted remote attackers to write arbitrary files via a .. (dot dot) in a filename in the neoaxis_web_application_win32.zip ZIP archive.",
  "id": "GHSA-rfxv-8ghr-g333",
  "modified": "2022-05-17T01:49:38Z",
  "published": "2022-05-17T01:49:38Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2012-0907"
    },
    {
      "type": "WEB",
      "url": "https://exchange.xforce.ibmcloud.com/vulnerabilities/72427"
    },
    {
      "type": "WEB",
      "url": "http://aluigi.altervista.org/adv/neoaxis_1-adv.txt"
    },
    {
      "type": "WEB",
      "url": "http://osvdb.org/78311"
    }
  ],
  "schema_version": "1.4.0",
  "severity": []
}

GHSA-RG2Q-2Q6C-9X92

Vulnerability from github – Published: 2022-09-10 00:00 – Updated: 2022-09-11 00:00
VLAI
Details

An issue was discovered in Shirne CMS 1.2.0. There is a Path Traversal vulnerability which could cause arbitrary file read via /static/ueditor/php/controller.php

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2022-37299"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-22"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2022-09-09T15:15:00Z",
    "severity": "MODERATE"
  },
  "details": "An issue was discovered in Shirne CMS 1.2.0. There is a Path Traversal vulnerability which could cause arbitrary file read via /static/ueditor/php/controller.php",
  "id": "GHSA-rg2q-2q6c-9x92",
  "modified": "2022-09-11T00:00:30Z",
  "published": "2022-09-10T00:00:28Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2022-37299"
    },
    {
      "type": "WEB",
      "url": "https://gitee.com/shirnecn/ShirneCMS/issues/I5JRHJ?from=project-issue"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N",
      "type": "CVSS_V3"
    }
  ]
}

Mitigation MIT-5.1
Implementation

Strategy: Input Validation

  • Assume all input is malicious. Use an "accept known good" input validation strategy, i.e., use a list of acceptable inputs that strictly conform to specifications. Reject any input that does not strictly conform to specifications, or transform it into something that does.
  • When performing input validation, consider all potentially relevant properties, including length, type of input, the full range of acceptable values, missing or extra inputs, syntax, consistency across related fields, and conformance to business rules. As an example of business rule logic, "boat" may be syntactically valid because it only contains alphanumeric characters, but it is not valid if the input is only expected to contain colors such as "red" or "blue."
  • Do not rely exclusively on looking for malicious or malformed inputs. This is likely to miss at least one undesirable input, especially if the code's environment changes. This can give attackers enough room to bypass the intended validation. However, denylists can be useful for detecting potential attacks or determining which inputs are so malformed that they should be rejected outright.
  • When validating filenames, use stringent allowlists that limit the character set to be used. If feasible, only allow a single "." character in the filename to avoid weaknesses such as CWE-23, and exclude directory separators such as "/" to avoid CWE-36. Use a list of allowable file extensions, which will help to avoid CWE-434.
  • Do not rely exclusively on a filtering mechanism that removes potentially dangerous characters. This is equivalent to a denylist, which may be incomplete (CWE-184). For example, filtering "/" is insufficient protection if the filesystem also supports the use of "\" as a directory separator. Another possible error could occur when the filtering is applied in a way that still produces dangerous data (CWE-182). For example, if "../" sequences are removed from the ".../...//" string in a sequential fashion, two instances of "../" would be removed from the original string, but the remaining characters would still form the "../" string.
Mitigation MIT-15
Architecture and Design

For any security checks that are performed on the client side, ensure that these checks are duplicated on the server side, in order to avoid CWE-602. Attackers can bypass the client-side checks by modifying values after the checks have been performed, or by changing the client to remove the client-side checks entirely. Then, these modified values would be submitted to the server.

Mitigation MIT-20.1
Implementation

Strategy: Input Validation

  • Inputs should be decoded and canonicalized to the application's current internal representation before being validated (CWE-180). Make sure that the application does not decode the same input twice (CWE-174). Such errors could be used to bypass allowlist validation schemes by introducing dangerous inputs after they have been checked.
  • Use a built-in path canonicalization function (such as realpath() in C) that produces the canonical version of the pathname, which effectively removes ".." sequences and symbolic links (CWE-23, CWE-59). This includes:
  • realpath() in C
  • getCanonicalPath() in Java
  • GetFullPath() in ASP.NET
  • realpath() or abs_path() in Perl
  • realpath() in PHP
Mitigation MIT-4
Architecture and Design

Strategy: Libraries or Frameworks

Use a vetted library or framework that does not allow this weakness to occur or provides constructs that make this weakness easier to avoid [REF-1482].

Mitigation MIT-29
Operation

Strategy: Firewall

Use an application firewall that can detect attacks against this weakness. It can be beneficial in cases in which the code cannot be fixed (because it is controlled by a third party), as an emergency prevention measure while more comprehensive software assurance measures are applied, or to provide defense in depth [REF-1481].

Mitigation MIT-17
Architecture and Design Operation

Strategy: Environment Hardening

Run your code using the lowest privileges that are required to accomplish the necessary tasks [REF-76]. If possible, create isolated accounts with limited privileges that are only used for a single task. That way, a successful attack will not immediately give the attacker access to the rest of the software or its environment. For example, database applications rarely need to run as the database administrator, especially in day-to-day operations.

Mitigation MIT-21.1
Architecture and Design

Strategy: Enforcement by Conversion

  • When the set of acceptable objects, such as filenames or URLs, is limited or known, create a mapping from a set of fixed input values (such as numeric IDs) to the actual filenames or URLs, and reject all other inputs.
  • For example, ID 1 could map to "inbox.txt" and ID 2 could map to "profile.txt". Features such as the ESAPI AccessReferenceMap [REF-185] provide this capability.
Mitigation MIT-22
Architecture and Design Operation

Strategy: Sandbox or Jail

  • Run the code in a "jail" or similar sandbox environment that enforces strict boundaries between the process and the operating system. This may effectively restrict which files can be accessed in a particular directory or which commands can be executed by the software.
  • OS-level examples include the Unix chroot jail, AppArmor, and SELinux. In general, managed code may provide some protection. For example, java.io.FilePermission in the Java SecurityManager allows the software to specify restrictions on file operations.
  • This may not be a feasible solution, and it only limits the impact to the operating system; the rest of the application may still be subject to compromise.
  • Be careful to avoid CWE-243 and other weaknesses related to jails.
Mitigation MIT-34
Architecture and Design Operation

Strategy: Attack Surface Reduction

  • Store library, include, and utility files outside of the web document root, if possible. Otherwise, store them in a separate directory and use the web server's access control capabilities to prevent attackers from directly requesting them. One common practice is to define a fixed constant in each calling program, then check for the existence of the constant in the library/include file; if the constant does not exist, then the file was directly requested, and it can exit immediately.
  • This significantly reduces the chance of an attacker being able to bypass any protection mechanisms that are in the base program but not in the include files. It will also reduce the attack surface.
Mitigation MIT-39
Implementation
  • Ensure that error messages only contain minimal details that are useful to the intended audience and no one else. The messages need to strike the balance between being too cryptic (which can confuse users) or being too detailed (which may reveal more than intended). The messages should not reveal the methods that were used to determine the error. Attackers can use detailed information to refine or optimize their original attack, thereby increasing their chances of success.
  • If errors must be captured in some detail, record them in log messages, but consider what could occur if the log messages can be viewed by attackers. Highly sensitive information such as passwords should never be saved to log files.
  • Avoid inconsistent messaging that might accidentally tip off an attacker about internal state, such as whether a user account exists or not.
  • In the context of path traversal, error messages which disclose path information can help attackers craft the appropriate attack strings to move through the file system hierarchy.
Mitigation MIT-16
Operation Implementation

Strategy: Environment Hardening

When using PHP, configure the application so that it does not use register_globals. During implementation, develop the application so that it does not rely on this feature, but be wary of implementing a register_globals emulation that is subject to weaknesses such as CWE-95, CWE-621, and similar issues.

CAPEC-126: Path Traversal

An adversary uses path manipulation methods to exploit insufficient input validation of a target to obtain access to data that should be not be retrievable by ordinary well-formed requests. A typical variety of this attack involves specifying a path to a desired file together with dot-dot-slash characters, resulting in the file access API or function traversing out of the intended directory structure and into the root file system. By replacing or modifying the expected path information the access function or API retrieves the file desired by the attacker. These attacks either involve the attacker providing a complete path to a targeted file or using control characters (e.g. path separators (/ or \) and/or dots (.)) to reach desired directories or files.

CAPEC-64: Using Slashes and URL Encoding Combined to Bypass Validation Logic

This attack targets the encoding of the URL combined with the encoding of the slash characters. An attacker can take advantage of the multiple ways of encoding a URL and abuse the interpretation of the URL. A URL may contain special character that need special syntax handling in order to be interpreted. Special characters are represented using a percentage character followed by two digits representing the octet code of the original character (%HEX-CODE). For instance US-ASCII space character would be represented with %20. This is often referred as escaped ending or percent-encoding. Since the server decodes the URL from the requests, it may restrict the access to some URL paths by validating and filtering out the URL requests it received. An attacker will try to craft an URL with a sequence of special characters which once interpreted by the server will be equivalent to a forbidden URL. It can be difficult to protect against this attack since the URL can contain other format of encoding such as UTF-8 encoding, Unicode-encoding, etc.

CAPEC-76: Manipulating Web Input to File System Calls

An attacker manipulates inputs to the target software which the target software passes to file system calls in the OS. The goal is to gain access to, and perhaps modify, areas of the file system that the target software did not intend to be accessible.

CAPEC-78: Using Escaped Slashes in Alternate Encoding

This attack targets the use of the backslash in alternate encoding. An adversary can provide a backslash as a leading character and causes a parser to believe that the next character is special. This is called an escape. By using that trick, the adversary tries to exploit alternate ways to encode the same character which leads to filter problems and opens avenues to attack.

CAPEC-79: Using Slashes in Alternate Encoding

This attack targets the encoding of the Slash characters. An adversary would try to exploit common filtering problems related to the use of the slashes characters to gain access to resources on the target host. Directory-driven systems, such as file systems and databases, typically use the slash character to indicate traversal between directories or other container components. For murky historical reasons, PCs (and, as a result, Microsoft OSs) choose to use a backslash, whereas the UNIX world typically makes use of the forward slash. The schizophrenic result is that many MS-based systems are required to understand both forms of the slash. This gives the adversary many opportunities to discover and abuse a number of common filtering problems. The goal of this pattern is to discover server software that only applies filters to one version, but not the other.