Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package trufflehog for openSUSE:Factory 
checked in at 2026-03-11 20:51:25
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/trufflehog (Old)
 and      /work/SRC/openSUSE:Factory/.trufflehog.new.8177 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "trufflehog"

Wed Mar 11 20:51:25 2026 rev:116 rq:1338015 version:3.93.8

Changes:
--------
--- /work/SRC/openSUSE:Factory/trufflehog/trufflehog.changes    2026-03-05 
18:20:47.650202785 +0100
+++ /work/SRC/openSUSE:Factory/.trufflehog.new.8177/trufflehog.changes  
2026-03-11 20:52:08.254090188 +0100
@@ -1,0 +2,7 @@
+Tue Mar 10 07:33:11 UTC 2026 - Johannes Kastl 
<[email protected]>
+
+- Update to version 3.93.8:
+  * Stop growing filesystem resume data (#4797)
+  * fix: make LDAP verification context-aware (#4768)
+
+-------------------------------------------------------------------

Old:
----
  trufflehog-3.93.7.obscpio

New:
----
  trufflehog-3.93.8.obscpio

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

Other differences:
------------------
++++++ trufflehog.spec ++++++
--- /var/tmp/diff_new_pack.FtgOK1/_old  2026-03-11 20:52:10.290172758 +0100
+++ /var/tmp/diff_new_pack.FtgOK1/_new  2026-03-11 20:52:10.290172758 +0100
@@ -17,7 +17,7 @@
 
 
 Name:           trufflehog
-Version:        3.93.7
+Version:        3.93.8
 Release:        0
 Summary:        CLI tool to find exposed secrets in source and archives
 License:        AGPL-3.0-or-later

++++++ _service ++++++
--- /var/tmp/diff_new_pack.FtgOK1/_old  2026-03-11 20:52:10.326174218 +0100
+++ /var/tmp/diff_new_pack.FtgOK1/_new  2026-03-11 20:52:10.330174380 +0100
@@ -2,7 +2,7 @@
   <service name="obs_scm" mode="manual">
     <param name="url">https://github.com/trufflesecurity/trufflehog.git</param>
     <param name="scm">git</param>
-    <param name="revision">v3.93.7</param>
+    <param name="revision">v3.93.8</param>
        <param name="match-tag">v*</param>
        <param name="versionformat">@PARENT_TAG@</param>
        <param name="versionrewrite-pattern">v(.*)</param>

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.FtgOK1/_old  2026-03-11 20:52:10.350175191 +0100
+++ /var/tmp/diff_new_pack.FtgOK1/_new  2026-03-11 20:52:10.354175353 +0100
@@ -1,6 +1,6 @@
 <servicedata>
 <service name="tar_scm">
                 <param 
name="url">https://github.com/trufflesecurity/trufflehog.git</param>
-              <param 
name="changesrevision">c3e599b7163e8198a55467f3133db0e7b2a492cb</param></service></servicedata>
+              <param 
name="changesrevision">6c05c4a00b91aa542267d8e32a8254774799d68d</param></service></servicedata>
 (No newline at EOF)
 

++++++ trufflehog-3.93.7.obscpio -> trufflehog-3.93.8.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/trufflehog-3.93.7/go.mod new/trufflehog-3.93.8/go.mod
--- old/trufflehog-3.93.7/go.mod        2026-03-04 16:56:42.000000000 +0100
+++ new/trufflehog-3.93.8/go.mod        2026-03-09 21:02:57.000000000 +0100
@@ -46,7 +46,6 @@
        github.com/getsentry/sentry-go v0.32.0
        github.com/go-errors/errors v1.5.1
        github.com/go-git/go-git/v5 v5.13.2
-       github.com/go-ldap/ldap/v3 v3.4.11
        github.com/go-logr/logr v1.4.3
        github.com/go-logr/zapr v1.3.0
        github.com/go-redis/redis v6.15.9+incompatible
@@ -70,6 +69,7 @@
        github.com/lestrrat-go/jwx/v3 v3.0.12
        github.com/lib/pq v1.10.9
        github.com/lrstanley/bubblezone v0.0.0-20250404061050-e13639e27357
+       github.com/mariduv/ldap-verify v0.0.2
        github.com/marusama/semaphore/v2 v2.5.0
        github.com/mattn/go-isatty v0.0.20
        github.com/mholt/archives v0.0.0-20241216060121-23e0af8fe73d
@@ -130,7 +130,7 @@
        dario.cat/mergo v1.0.0 // indirect
        filippo.io/edwards25519 v1.1.0 // indirect
        github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // 
indirect
-       github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // 
indirect
+       github.com/Azure/go-ntlmssp v0.1.0 // indirect
        github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 // 
indirect
        github.com/DataDog/zstd v1.5.5 // indirect
        
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp 
v1.30.0 // indirect
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/trufflehog-3.93.7/go.sum new/trufflehog-3.93.8/go.sum
--- old/trufflehog-3.93.7/go.sum        2026-03-04 16:56:42.000000000 +0100
+++ new/trufflehog-3.93.8/go.sum        2026-03-09 21:02:57.000000000 +0100
@@ -57,8 +57,8 @@
 github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0/go.mod 
h1:bTSOgj05NGRuHHhQwAdPnYr9TOdNmKlZTgGLL6nyAdI=
 github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 
h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
 github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod 
h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
-github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 
h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
-github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod 
h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
+github.com/Azure/go-ntlmssp v0.1.0 
h1:DjFo6YtWzNqNvQdrwEyr/e4nhU3vRiwenz5QX7sFz+A=
+github.com/Azure/go-ntlmssp v0.1.0/go.mod 
h1:NYqdhxd/8aAct/s4qSYZEerdPuH1liG2/X9DiVTbhpk=
 github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 
h1:kYRSnvJju5gYVyhkij+RTJ/VR6QIUaCfWeaFm2ycsjQ=
 github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2/go.mod 
h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
 github.com/BobuSumisu/aho-corasick v1.0.3 
h1:uuf+JHwU9CHP2Vx+wAy6jcksJThhJS9ehR8a+4nPE9g=
@@ -96,8 +96,6 @@
 github.com/alecthomas/repr v0.4.0/go.mod 
h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
 github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 
h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc=
 github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod 
h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
-github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa 
h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI=
-github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod 
h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
 github.com/andybalholm/brotli v1.1.1 
h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
 github.com/andybalholm/brotli v1.1.1/go.mod 
h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
 github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be 
h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
@@ -327,8 +325,6 @@
 github.com/go-jose/go-jose/v4 v4.1.3 
h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
 github.com/go-jose/go-jose/v4 v4.1.3/go.mod 
h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
 github.com/go-kit/log v0.1.0/go.mod 
h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
-github.com/go-ldap/ldap/v3 v3.4.11 
h1:4k0Yxweg+a3OyBLjdYn5OKglv18JNvfDykSoI8bW0gU=
-github.com/go-ldap/ldap/v3 v3.4.11/go.mod 
h1:bY7t0FLK8OAVpp/vV6sSlpz3EQDGcQwc8pF0ujLgKvM=
 github.com/go-logfmt/logfmt v0.5.0/go.mod 
h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
 github.com/go-logr/logr v1.2.2/go.mod 
h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
 github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
@@ -450,8 +446,6 @@
 github.com/hashicorp/go-multierror v1.1.1/go.mod 
h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
 github.com/hashicorp/go-retryablehttp v0.7.8 
h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48=
 github.com/hashicorp/go-retryablehttp v0.7.8/go.mod 
h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw=
-github.com/hashicorp/go-uuid v1.0.3 
h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
-github.com/hashicorp/go-uuid v1.0.3/go.mod 
h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
 github.com/hashicorp/golang-lru v0.5.0/go.mod 
h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hashicorp/golang-lru v0.5.1/go.mod 
h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hashicorp/golang-lru/v2 v2.0.7 
h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
@@ -471,18 +465,6 @@
 github.com/jackc/puddle/v2 v2.2.1/go.mod 
h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
 github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 
h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
 github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod 
h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
-github.com/jcmturner/aescts/v2 v2.0.0 
h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
-github.com/jcmturner/aescts/v2 v2.0.0/go.mod 
h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
-github.com/jcmturner/dnsutils/v2 v2.0.0 
h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
-github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod 
h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
-github.com/jcmturner/gofork v1.7.6 
h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=
-github.com/jcmturner/gofork v1.7.6/go.mod 
h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=
-github.com/jcmturner/goidentity/v6 v6.0.1 
h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o=
-github.com/jcmturner/goidentity/v6 v6.0.1/go.mod 
h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
-github.com/jcmturner/gokrb5/v8 v8.4.4 
h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8=
-github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod 
h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
-github.com/jcmturner/rpc/v2 v2.0.3 
h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
-github.com/jcmturner/rpc/v2 v2.0.3/go.mod 
h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
 github.com/jedib0t/go-pretty/v6 v6.6.8 
h1:JnnzQeRz2bACBobIaa/r+nqjvws4yEhcmaZ4n1QzsEc=
 github.com/jedib0t/go-pretty/v6 v6.6.8/go.mod 
h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU=
 github.com/jlaffaye/ftp v0.2.0 h1:lXNvW7cBu7R/68bknOX3MrRIIqZ61zELs1P2RAiA3lg=
@@ -546,6 +528,8 @@
 github.com/magiconair/properties v1.8.7 
h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
 github.com/magiconair/properties v1.8.7/go.mod 
h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
 github.com/mailru/easyjson v0.7.7/go.mod 
h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
+github.com/mariduv/ldap-verify v0.0.2 
h1:NBdDTYyWDr71CONVcizasqL/AA9tQ2RNgLhTgnyfquI=
+github.com/mariduv/ldap-verify v0.0.2/go.mod 
h1:d/7+kkMBGDs9LPZ/7hmduYqtOkRIJcgpa8dL+9CsveE=
 github.com/marusama/semaphore/v2 v2.5.0 
h1:o/1QJD9DBYOWRnDhPwDVAXQn6mQYD0gZaS1Tpx6DJGM=
 github.com/marusama/semaphore/v2 v2.5.0/go.mod 
h1:z9nMiNUekt/LTpTUQdpp+4sJeYqUGpwMHfW0Z8V8fnQ=
 github.com/mattn/go-colorable v0.1.13 
h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/trufflehog-3.93.7/pkg/detectors/ldap/ldap.go 
new/trufflehog-3.93.8/pkg/detectors/ldap/ldap.go
--- old/trufflehog-3.93.7/pkg/detectors/ldap/ldap.go    2026-03-04 
16:56:42.000000000 +0100
+++ new/trufflehog-3.93.8/pkg/detectors/ldap/ldap.go    2026-03-09 
21:02:57.000000000 +0100
@@ -3,13 +3,14 @@
 import (
        "context"
        "crypto/tls"
+       "errors"
        "fmt"
        "net"
        "net/url"
        "strings"
        "time"
 
-       "github.com/go-ldap/ldap/v3"
+       ldap "github.com/mariduv/ldap-verify"
        regexp "github.com/wasilibs/go-re2"
 
        "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
@@ -72,7 +73,7 @@
                                }
 
                                if verify {
-                                       verificationErr := 
verifyLDAP(username[1], password[1], ldapURL)
+                                       verificationErr := verifyLDAP(ctx, 
username[1], password[1], ldapURL)
                                        s1.Verified = verificationErr == nil
                                        if !isErrDeterminate(verificationErr) {
                                                
s1.SetVerificationError(verificationErr, password[1])
@@ -102,7 +103,7 @@
                }
 
                if verify {
-                       verificationError := verifyLDAP(username, password, 
ldapURL)
+                       verificationError := verifyLDAP(ctx, username, 
password, ldapURL)
 
                        s1.Verified = verificationError == nil
                        if !isErrDeterminate(verificationError) {
@@ -117,18 +118,18 @@
 }
 
 func isErrDeterminate(err error) bool {
-       switch e := err.(type) {
-       case *ldap.Error:
-               switch e.Err.(type) {
-               case *net.OpError:
-                       return false
-               }
+       var neterr *net.OpError
+
+       if errors.As(err, &neterr) ||
+               errors.Is(err, context.DeadlineExceeded) ||
+               errors.Is(err, context.Canceled) {
+               return false
        }
 
        return true
 }
 
-func verifyLDAP(username, password string, ldapURL *url.URL) error {
+func verifyLDAP(ctx context.Context, username, password string, ldapURL 
*url.URL) error {
        // Tests with non-TLS, TLS, and STARTTLS
 
        uri := ldapURL.String()
@@ -136,13 +137,13 @@
        switch ldapURL.Scheme {
        case "ldap":
                // Non-TLS dial
-               l, err := ldap.DialURL(uri)
+               l, err := ldap.DialURL(uri, ldap.DialWithContext(ctx))
                if err != nil {
                        return err
                }
                defer l.Close()
                // Non-TLS verify
-               err = l.Bind(username, password)
+               err = l.BindContext(ctx, username, password)
                if err == nil {
                        return nil
                }
@@ -153,20 +154,23 @@
                        return err
                }
                // STARTTLS verify
-               return l.Bind(username, password)
+               return l.BindContext(ctx, username, password)
        case "ldaps":
                // TLS dial
-               l, err := ldap.DialURL(uri, 
ldap.DialWithTLSConfig(&tls.Config{InsecureSkipVerify: true}))
+               l, err := ldap.DialURL(
+                       uri,
+                       ldap.DialWithContext(ctx),
+                       ldap.DialWithTLSConfig(&tls.Config{InsecureSkipVerify: 
true}),
+               )
                if err != nil {
                        return err
                }
                defer l.Close()
                // TLS verify
-               return l.Bind(username, password)
+               return l.BindContext(ctx, username, password)
        default:
                return fmt.Errorf("unknown ldap scheme %q", ldapURL.Scheme)
        }
-
 }
 
 func (s Scanner) Type() detectorspb.DetectorType {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/trufflehog-3.93.7/pkg/detectors/ldap/ldap_test.go 
new/trufflehog-3.93.8/pkg/detectors/ldap/ldap_test.go
--- old/trufflehog-3.93.7/pkg/detectors/ldap/ldap_test.go       2026-03-04 
16:56:42.000000000 +0100
+++ new/trufflehog-3.93.8/pkg/detectors/ldap/ldap_test.go       2026-03-09 
21:02:57.000000000 +0100
@@ -3,9 +3,11 @@
 import (
        "context"
        "fmt"
+       "net"
        "testing"
 
        "github.com/google/go-cmp/cmp"
+       ldap "github.com/mariduv/ldap-verify"
 
        "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
        "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
@@ -84,3 +86,25 @@
                })
        }
 }
+
+func Test_isErrDeterminate(t *testing.T) {
+       if isErrDeterminate(fmt.Errorf("anything")) != true {
+               t.Errorf("general errors should be determinate")
+       }
+
+       if isErrDeterminate(&ldap.Error{Err: fmt.Errorf("anything")}) != true {
+               t.Errorf("ldap general errors should be determinate")
+       }
+
+       if isErrDeterminate(&ldap.Error{Err: &net.OpError{}}) == true {
+               t.Errorf("ldap net.OpError{} should be indeterminate")
+       }
+
+       if isErrDeterminate(&ldap.Error{Err: context.DeadlineExceeded}) == true 
{
+               t.Errorf("ldap context deadline should be indeterminate")
+       }
+
+       if isErrDeterminate(&ldap.Error{Err: context.Canceled}) == true {
+               t.Errorf("ldap context deadline should be indeterminate")
+       }
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/trufflehog-3.93.7/pkg/sources/filesystem/filesystem.go 
new/trufflehog-3.93.8/pkg/sources/filesystem/filesystem.go
--- old/trufflehog-3.93.7/pkg/sources/filesystem/filesystem.go  2026-03-04 
16:56:42.000000000 +0100
+++ new/trufflehog-3.93.8/pkg/sources/filesystem/filesystem.go  2026-03-09 
21:02:57.000000000 +0100
@@ -5,6 +5,7 @@
        "io"
        "os"
        "path/filepath"
+       "strings"
 
        "github.com/go-errors/errors"
        "github.com/go-logr/logr"
@@ -136,7 +137,7 @@
                        initialDepth := 1
                        err = s.scanSymlink(ctx, cleanPath, chunksChan, 
workerPool, initialDepth, path)
                        _ = workerPool.Wait()
-                       s.ClearEncodedResumeContainingId(path + "#")
+                       s.ClearEncodedResumeInfoFor(path)
                } else if fileInfo.IsDir() {
                        ctx.Logger().V(5).Info("Root path is a dir", "path", 
cleanPath)
                        workerPool := new(errgroup.Group)
@@ -144,7 +145,7 @@
                        initialDepth := 1
                        err = s.scanDir(ctx, cleanPath, chunksChan, workerPool, 
initialDepth, path)
                        _ = workerPool.Wait()
-                       s.ClearEncodedResumeContainingId(path + "#")
+                       s.ClearEncodedResumeInfoFor(path)
                } else {
                        if !fileInfo.Mode().IsRegular() {
                                logger.Info("skipping non-regular file", 
"path", cleanPath)
@@ -217,13 +218,11 @@
        if s.filter != nil && !s.filter.Pass(resolvedPath) {
                return nil
        }
-       resumptionKey := rootPath + "#" + path
-       startState := s.GetEncodedResumeInfoFor(resumptionKey)
-       resuming := startState != ""
-       if resuming && startState == resolvedPath {
-               ctx.Logger().V(5).Info("skipping symlink, already scanned", 
"path", resolvedPath)
-               return nil
-       }
+
+       // Use a single resumption key for the entire scan rooted at rootPath.
+       // Resume checks are handled by the calling scanDir function.
+       resumptionKey := rootPath
+
        workerPool.Go(func() error {
                if !fileInfo.Mode().Type().IsRegular() {
                        ctx.Logger().V(5).Info("skipping non-regular file", 
"path", resolvedPath)
@@ -232,7 +231,7 @@
                if err := s.scanFile(ctx, resolvedPath, chunksChan); err != nil 
{
                        ctx.Logger().Error(err, "error scanning file", "path", 
resolvedPath)
                }
-               s.SetEncodedResumeInfoFor(resumptionKey, resolvedPath)
+               s.SetEncodedResumeInfoFor(resumptionKey, path)
                return nil
        })
 
@@ -249,12 +248,35 @@
 ) error {
        // check if the full path is not matching any pattern in include
        // FilterRuleSet and matching any exclude FilterRuleSet.
-       resumptionKey := rootPath + "#" + path
        if s.filter != nil && s.filter.ShouldExclude(path) {
                return nil
        }
-       startState := s.GetEncodedResumeInfoFor(resumptionKey)
-       resuming := startState != ""
+
+       // Use a single resumption key for the entire scan rooted at rootPath.
+       // The value stored is the full path of the last successfully scanned 
file.
+       // This avoids accumulating separate entries for each subdirectory 
visited.
+       resumptionKey := rootPath
+       resumeAfter := s.GetEncodedResumeInfoFor(resumptionKey)
+
+       // Only consider resumption if the resume point is within this 
directory's subtree.
+       // Since os.ReadDir returns entries sorted by filename:
+       // - If we're scanning /root/ccc and the resume point is 
/root/bbb/file.txt,
+       //   we've already passed it (bbb < ccc) and should process ccc 
normally.
+       // - If we're scanning /root/aaa and the resume point is 
/root/bbb/file.txt,
+       //   we haven't reached it yet (aaa < bbb), so aaa was already fully 
scanned
+       //   and should be skipped entirely.
+       if resumeAfter != "" && !strings.HasPrefix(resumeAfter, 
path+string(filepath.Separator)) && resumeAfter != path {
+               // Resume point is not in this subtree. Compare paths to 
determine if we
+               // should skip this directory (already scanned) or process it 
(already passed).
+               if path < resumeAfter {
+                       // This directory comes before the resume point 
lexicographically,
+                       // meaning it was already fully scanned. Skip it 
entirely.
+                       return nil
+               }
+               // This directory comes after the resume point, so we've 
already passed
+               // the resume point. Process this directory normally.
+               resumeAfter = ""
+       }
 
        ctx.Logger().V(5).Info("Full path found is", "fullPath", path)
 
@@ -271,11 +293,35 @@
                        }
                }
 
-               if resuming {
-                       if entryPath == startState {
-                               resuming = false
+               // Skip entries until we pass the resume point.
+               // We don't clear the resume info when we find the resume point 
- instead we
+               // keep it set until a new file is scanned. This ensures we 
don't lose progress
+               // if the scan is interrupted between finding the resume point 
and scanning
+               // the next file.
+               if resumeAfter != "" {
+                       // If this entry is the resume point, stop skipping.
+                       if entryPath == resumeAfter {
+                               resumeAfter = ""
+                               continue // Skip the resume point itself since 
it was already processed.
+                       }
+                       // If the resume point is within this entry (a 
descendant), we need to
+                       // traverse into it to find where to resume.
+                       if entry.IsDir() && strings.HasPrefix(resumeAfter, 
entryPath+string(filepath.Separator)) {
+                               // Recurse into this directory to find the 
resume point.
+                               if err := s.scanDir(ctx, entryPath, chunksChan, 
workerPool, depth, rootPath); err != nil {
+                                       ctx.Logger().Error(err, "error scanning 
directory", "path", entryPath)
+                               }
+                               // After recursing, clear local resumeAfter. 
The child scanDir will have
+                               // handled resumption within its subtree, and 
subsequent entries in this
+                               // directory should be processed normally.
+                               resumeAfter = ""
+                               continue
                        }
-               } else if entry.Type()&os.ModeSymlink != 0 {
+                       // Skip this entry - it comes before the resume point 
in traversal order.
+                       continue
+               }
+
+               if entry.Type()&os.ModeSymlink != 0 {
                        ctx.Logger().V(5).Info("Entry found is a symlink", 
"path", entryPath)
                        if !s.canFollowSymlinks() {
                                // If the file or directory is a symlink but 
the followSymlinks is disable ignore the path
@@ -401,7 +447,7 @@
                        initialDepth := 1
                        scanErr = s.scanSymlink(ctx, cleanPath, ch, workerPool, 
initialDepth, path)
                        _ = workerPool.Wait()
-                       s.ClearEncodedResumeContainingId(path + "#")
+                       s.ClearEncodedResumeInfoFor(path)
 
                } else if fileInfo.IsDir() {
                        ctx.Logger().V(5).Info("Root path is a dir", "path", 
cleanPath)
@@ -411,7 +457,7 @@
                        // TODO: Finer grain error tracking of individual 
chunks.
                        scanErr = s.scanDir(ctx, cleanPath, ch, workerPool, 
initialDepth, path)
                        _ = workerPool.Wait()
-                       s.ClearEncodedResumeContainingId(path + "#")
+                       s.ClearEncodedResumeInfoFor(path)
                } else {
                        ctx.Logger().V(5).Info("Root path is a file", "path", 
cleanPath)
                        // TODO: Finer grain error tracking of individual
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/trufflehog-3.93.7/pkg/sources/filesystem/filesystem_test.go 
new/trufflehog-3.93.8/pkg/sources/filesystem/filesystem_test.go
--- old/trufflehog-3.93.7/pkg/sources/filesystem/filesystem_test.go     
2026-03-04 16:56:42.000000000 +0100
+++ new/trufflehog-3.93.8/pkg/sources/filesystem/filesystem_test.go     
2026-03-09 21:02:57.000000000 +0100
@@ -1,9 +1,12 @@
 package filesystem
 
 import (
+       "encoding/json"
+       "fmt"
        "os"
        "path/filepath"
        "strings"
+       "sync"
        "testing"
        "time"
 
@@ -394,7 +397,7 @@
        // Create an IncludePathsFile with the absolute path of the file
        includeFilePath := filepath.Join(testDir, "include.txt")
        err = os.WriteFile(includeFilePath, []byte(strings.ReplaceAll(filePath, 
`\`, `\\`)+"\n"), 0644)
-  require.NoError(t, err)
+       require.NoError(t, err)
 
        conn, err := anypb.New(&sourcespb.Filesystem{
                IncludePathsFile: includeFilePath,
@@ -464,6 +467,285 @@
        require.NotContains(t, processedFiles, binaryFile, "Binary file should 
be skipped")
 }
 
+func TestResumptionInfoDoesNotGrowWithSubdirectories(t *testing.T) {
+       ctx := context.AddLogger(t.Context())
+
+       // Create a deeply nested directory structure with files at each level.
+       // Structure: root/dir0/dir1/dir2/.../dir9, each containing a file.
+       rootDir, err := os.MkdirTemp("", "trufflehog-resumption-test")
+       require.NoError(t, err)
+       t.Cleanup(func() { _ = os.RemoveAll(rootDir) })
+
+       const numSubdirs = 10
+       currentDir := rootDir
+       for i := 0; i < numSubdirs; i++ {
+               // Create a file in the current directory
+               filePath := filepath.Join(currentDir, fmt.Sprintf("file%d.txt", 
i))
+               err := os.WriteFile(filePath, []byte(fmt.Sprintf("content %d", 
i)), 0644)
+               require.NoError(t, err)
+
+               // Create the next subdirectory
+               subDir := filepath.Join(currentDir, fmt.Sprintf("subdir%d", i))
+               err = os.Mkdir(subDir, 0755)
+               require.NoError(t, err)
+               currentDir = subDir
+       }
+       // Create a file in the deepest directory
+       err = os.WriteFile(filepath.Join(currentDir, "deepest.txt"), 
[]byte("deepest"), 0644)
+       require.NoError(t, err)
+
+       conn, err := anypb.New(&sourcespb.Filesystem{MaxSymlinkDepth: 0})
+       require.NoError(t, err)
+
+       // Initialize the source.
+       s := Source{}
+       err = s.Init(ctx, "test resumption growth", 0, 0, true, conn, 1)
+       require.NoError(t, err)
+
+       // Track the maximum size of EncodedResumeInfo during the scan.
+       var maxResumeInfoSize int
+       var mu sync.Mutex
+
+       // We need to periodically check the resume info size during scanning.
+       // Run ChunkUnit in a goroutine and poll the progress.
+       done := make(chan struct{})
+       go func() {
+               defer close(done)
+               reporter := sourcestest.TestReporter{}
+               err := s.ChunkUnit(ctx, sources.CommonSourceUnit{
+                       ID: rootDir,
+               }, &reporter)
+               require.NoError(t, err)
+       }()
+
+       // Poll the resume info size while scanning is in progress.
+       ticker := time.NewTicker(1 * time.Millisecond)
+       defer ticker.Stop()
+
+polling:
+       for {
+               select {
+               case <-done:
+                       break polling
+               case <-ticker.C:
+                       progress := s.GetProgress()
+                       mu.Lock()
+                       if len(progress.EncodedResumeInfo) > maxResumeInfoSize {
+                               maxResumeInfoSize = 
len(progress.EncodedResumeInfo)
+                       }
+                       mu.Unlock()
+               }
+       }
+
+       // After scan completes, check the final state.
+       finalProgress := s.GetProgress()
+       t.Logf("Final EncodedResumeInfo length: %d", 
len(finalProgress.EncodedResumeInfo))
+       t.Logf("Max EncodedResumeInfo length during scan: %d", 
maxResumeInfoSize)
+
+       // Parse the resume info to count entries if it's not empty.
+       if maxResumeInfoSize > 0 {
+               var resumeMap map[string]string
+               err := json.Unmarshal([]byte(finalProgress.EncodedResumeInfo), 
&resumeMap)
+               if err == nil {
+                       t.Logf("Final resume info entries: %d", len(resumeMap))
+               }
+       }
+
+       // The key assertion: resumption info should NOT grow proportionally 
with
+       // the number of subdirectories. During the scan, it should only track 
the
+       // current position, not accumulate entries for every directory visited.
+       //
+       // With proper implementation, resume info should have at most a few 
entries
+       // (e.g., one per directory being actively scanned), not one entry per
+       // directory that has ever been visited.
+       //
+       // A reasonable upper bound for resume info size: each entry is roughly
+       // "rootPath#subPath": "filePath". With temp paths ~50 chars, one entry 
is
+       // ~150 bytes with JSON overhead. For 10 directories, accumulation would
+       // mean ~1500+ bytes. A non-accumulating implementation should stay well
+       // under that.
+       const maxAcceptableResumeInfoSize = 300 // bytes - allows for ~2 
entries max
+       assert.LessOrEqual(t, maxResumeInfoSize, maxAcceptableResumeInfoSize,
+               "Resume info grew to %d bytes during scan, suggesting 
accumulation across %d subdirectories. "+
+                       "Resume info should not accumulate entries for each 
subdirectory visited.",
+               maxResumeInfoSize, numSubdirs)
+}
+
+func TestResumptionSkipsAlreadyScannedFiles(t *testing.T) {
+       ctx := context.Background()
+
+       // Create a directory with files that have predictable alphabetical 
order.
+       rootDir, err := os.MkdirTemp("", "trufflehog-resumption-test")
+       require.NoError(t, err)
+       t.Cleanup(func() { _ = os.RemoveAll(rootDir) })
+
+       // Create files with predictable names for sorting.
+       files := []string{"aaa.txt", "bbb.txt", "ccc.txt", "ddd.txt"}
+       for _, name := range files {
+               filePath := filepath.Join(rootDir, name)
+               err := os.WriteFile(filePath, []byte("content of "+name), 0644)
+               require.NoError(t, err)
+       }
+
+       conn, err := anypb.New(&sourcespb.Filesystem{})
+       require.NoError(t, err)
+
+       // Initialize the source.
+       s := Source{}
+       err = s.Init(ctx, "test resumption", 0, 0, true, conn, 1)
+       require.NoError(t, err)
+
+       // Pre-set the resume point to simulate a previous interrupted scan.
+       // Setting it to bbb.txt means we should skip aaa.txt and bbb.txt,
+       // and only scan ccc.txt and ddd.txt.
+       resumePoint := filepath.Join(rootDir, "bbb.txt")
+       s.SetEncodedResumeInfoFor(rootDir, resumePoint)
+
+       // Run the scan.
+       reporter := sourcestest.TestReporter{}
+       err = s.ChunkUnit(ctx, sources.CommonSourceUnit{ID: rootDir}, &reporter)
+       require.NoError(t, err)
+
+       // Collect scanned file names.
+       scannedFiles := make(map[string]bool)
+       for _, chunk := range reporter.Chunks {
+               file := chunk.SourceMetadata.GetFilesystem().GetFile()
+               scannedFiles[filepath.Base(file)] = true
+       }
+
+       // Assert only files after the resume point were scanned.
+       assert.False(t, scannedFiles["aaa.txt"], "aaa.txt should have been 
skipped (before resume point)")
+       assert.False(t, scannedFiles["bbb.txt"], "bbb.txt should have been 
skipped (the resume point itself)")
+       assert.True(t, scannedFiles["ccc.txt"], "ccc.txt should have been 
scanned (after resume point)")
+       assert.True(t, scannedFiles["ddd.txt"], "ddd.txt should have been 
scanned (after resume point)")
+       assert.Equal(t, 2, len(reporter.Chunks), "expected exactly 2 files to 
be scanned")
+}
+
+func TestResumptionWithNestedDirectories(t *testing.T) {
+       ctx := context.Background()
+
+       // Create a nested directory structure:
+       // root/
+       //   aaa/
+       //     file1.txt
+       //   bbb/
+       //     file2.txt
+       //   ccc/
+       //     file3.txt
+       rootDir, err := os.MkdirTemp("", "trufflehog-resumption-nested-test")
+       require.NoError(t, err)
+       t.Cleanup(func() { _ = os.RemoveAll(rootDir) })
+
+       dirs := []string{"aaa", "bbb", "ccc"}
+       for i, dir := range dirs {
+               dirPath := filepath.Join(rootDir, dir)
+               err := os.Mkdir(dirPath, 0755)
+               require.NoError(t, err)
+
+               filePath := filepath.Join(dirPath, fmt.Sprintf("file%d.txt", 
i+1))
+               err = os.WriteFile(filePath, []byte(fmt.Sprintf("content of 
file%d", i+1)), 0644)
+               require.NoError(t, err)
+       }
+
+       conn, err := anypb.New(&sourcespb.Filesystem{})
+       require.NoError(t, err)
+
+       // Initialize the source.
+       s := Source{}
+       err = s.Init(ctx, "test resumption nested", 0, 0, true, conn, 1)
+       require.NoError(t, err)
+
+       // Pre-set the resume point to bbb/file2.txt.
+       // This should skip aaa/file1.txt and bbb/file2.txt, only scanning 
ccc/file3.txt.
+       resumePoint := filepath.Join(rootDir, "bbb", "file2.txt")
+       s.SetEncodedResumeInfoFor(rootDir, resumePoint)
+
+       // Run the scan.
+       reporter := sourcestest.TestReporter{}
+       err = s.ChunkUnit(ctx, sources.CommonSourceUnit{ID: rootDir}, &reporter)
+       require.NoError(t, err)
+
+       // Collect scanned file names.
+       scannedFiles := make(map[string]bool)
+       for _, chunk := range reporter.Chunks {
+               file := chunk.SourceMetadata.GetFilesystem().GetFile()
+               scannedFiles[filepath.Base(file)] = true
+       }
+
+       // Assert only file3.txt was scanned.
+       assert.False(t, scannedFiles["file1.txt"], "file1.txt should have been 
skipped (in aaa/, before resume point)")
+       assert.False(t, scannedFiles["file2.txt"], "file2.txt should have been 
skipped (the resume point itself)")
+       assert.True(t, scannedFiles["file3.txt"], "file3.txt should have been 
scanned (in ccc/, after resume point)")
+       assert.Equal(t, 1, len(reporter.Chunks), "expected exactly 1 file to be 
scanned")
+}
+
+func TestResumptionWithOutOfSubtreeResumePoint(t *testing.T) {
+       ctx := context.Background()
+
+       // Create a directory structure:
+       // root/
+       //   aaa/
+       //     file1.txt
+       //   bbb/
+       //     file2.txt
+       //   ccc/
+       //     file3.txt
+       //
+       // This test verifies correct behavior when scanDir is called for a 
directory
+       // with a resume point OUTSIDE that directory's subtree. Since 
os.ReadDir
+       // returns entries sorted by filename, directories that 
lexicographically
+       // precede the resume point were already fully scanned and should be 
skipped.
+       rootDir, err := os.MkdirTemp("", "trufflehog-resumption-subtree-test")
+       require.NoError(t, err)
+       t.Cleanup(func() { _ = os.RemoveAll(rootDir) })
+
+       dirs := []string{"aaa", "bbb", "ccc"}
+       for i, dir := range dirs {
+               dirPath := filepath.Join(rootDir, dir)
+               err := os.Mkdir(dirPath, 0755)
+               require.NoError(t, err)
+
+               filePath := filepath.Join(dirPath, fmt.Sprintf("file%d.txt", 
i+1))
+               err = os.WriteFile(filePath, []byte(fmt.Sprintf("content of 
file%d", i+1)), 0644)
+               require.NoError(t, err)
+       }
+
+       conn, err := anypb.New(&sourcespb.Filesystem{})
+       require.NoError(t, err)
+
+       // Initialize the source.
+       s := Source{}
+       err = s.Init(ctx, "test resumption subtree", 0, 0, true, conn, 1)
+       require.NoError(t, err)
+
+       // Pre-set the resume point to bbb/file2.txt using aaaDir as the key.
+       // This simulates an edge case where scanDir is called directly for a
+       // directory with a resume point outside its subtree.
+       aaaDir := filepath.Join(rootDir, "aaa")
+       resumePoint := filepath.Join(rootDir, "bbb", "file2.txt")
+       s.SetEncodedResumeInfoFor(aaaDir, resumePoint)
+
+       // Scan the aaa directory with a resume point outside its subtree.
+       reporter := sourcestest.TestReporter{}
+       err = s.ChunkUnit(ctx, sources.CommonSourceUnit{ID: aaaDir}, &reporter)
+       require.NoError(t, err)
+
+       // Collect scanned file names.
+       scannedFiles := make(map[string]bool)
+       for _, chunk := range reporter.Chunks {
+               file := chunk.SourceMetadata.GetFilesystem().GetFile()
+               scannedFiles[filepath.Base(file)] = true
+       }
+
+       // file1.txt should NOT be scanned because aaa/ comes before bbb/
+       // lexicographically, meaning aaa/ would have been fully processed
+       // before reaching the resume point.
+       assert.False(t, scannedFiles["file1.txt"],
+               "file1.txt should NOT be scanned because aaa/ comes before 
resume point bbb/file2.txt lexicographically")
+       assert.Equal(t, 0, len(reporter.Chunks),
+               "expected 0 files to be scanned since aaa/ was already fully 
processed before the resume point")
+}
+
 // createTempFile is a helper function to create a temporary file in the given
 // directory with the provided contents. If dir is "", the operating system's
 // temp directory is used.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/trufflehog-3.93.7/pkg/sources/sources.go 
new/trufflehog-3.93.8/pkg/sources/sources.go
--- old/trufflehog-3.93.7/pkg/sources/sources.go        2026-03-04 
16:56:42.000000000 +0100
+++ new/trufflehog-3.93.8/pkg/sources/sources.go        2026-03-09 
21:02:57.000000000 +0100
@@ -4,7 +4,6 @@
        "encoding/json"
        "errors"
        "runtime"
-       "strings"
        "sync"
 
        "google.golang.org/protobuf/types/known/anypb"
@@ -595,22 +594,6 @@
        p.EncodedResumeInfo = marshalEncodedResumeInfo(p.encodedResumeInfoByID)
 }
 
-// ClearEncodedResumeContainingId removes the encoded resume information
-// entries that contain the id
-func (p *Progress) ClearEncodedResumeContainingId(id string) {
-       p.mut.Lock()
-       defer p.mut.Unlock()
-       p.ensureEncodedResumeInfoByID()
-
-       for key := range p.encodedResumeInfoByID {
-               if strings.Contains(key, id) {
-                       delete(p.encodedResumeInfoByID, key)
-               }
-       }
-
-       p.EncodedResumeInfo = marshalEncodedResumeInfo(p.encodedResumeInfoByID)
-}
-
 // ensureEncodedResumeInfoByID ensures the encodedResumeInfoByID attribute is a
 // non-nil map. The mutex must be held when calling this function.
 func (p *Progress) ensureEncodedResumeInfoByID() {

++++++ trufflehog.obsinfo ++++++
--- /var/tmp/diff_new_pack.FtgOK1/_old  2026-03-11 20:52:13.574305940 +0100
+++ /var/tmp/diff_new_pack.FtgOK1/_new  2026-03-11 20:52:13.578306102 +0100
@@ -1,5 +1,5 @@
 name: trufflehog
-version: 3.93.7
-mtime: 1772639802
-commit: c3e599b7163e8198a55467f3133db0e7b2a492cb
+version: 3.93.8
+mtime: 1773086577
+commit: 6c05c4a00b91aa542267d8e32a8254774799d68d
 

++++++ vendor.tar.gz ++++++
/work/SRC/openSUSE:Factory/trufflehog/vendor.tar.gz 
/work/SRC/openSUSE:Factory/.trufflehog.new.8177/vendor.tar.gz differ: char 13, 
line 1

Reply via email to