Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package jline3 for openSUSE:Factory checked 
in at 2026-07-01 17:12:40
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/jline3 (Old)
 and      /work/SRC/openSUSE:Factory/.jline3.new.11887 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "jline3"

Wed Jul  1 17:12:40 2026 rev:15 rq:1362999 version:3.30.15

Changes:
--------
--- /work/SRC/openSUSE:Factory/jline3/jline3.changes    2026-06-30 
15:14:41.438596643 +0200
+++ /work/SRC/openSUSE:Factory/.jline3.new.11887/jline3.changes 2026-07-01 
17:12:49.256093305 +0200
@@ -1,0 +2,30 @@
+Wed Jul  1 13:32:12 UTC 2026 - Fridrich Strba <[email protected]>
+
+- Update to upstream version 3.30.15
+  * Security Fixes
+    + fix: guard regex matching against catastrophic backtracking
+      (ReDoS) (#2018, backport of #2012)
+      ° Adds SafeRegex utility with TimeoutCharSequence to enforce
+        wall-clock deadlines during regex matching
+      ° Fixes 8 locations across terminal, reader, and builtins
+        where user-controlled input could trigger catastrophic
+        backtracking
+      ° Addresses GHSA-r2xf-8xr9-62gw, GHSA-2v9w-34q6-wpqx,
+        GHSA-ph9c-7hw9-vhhw, GHSA-5q95-hrpc-m3w3
+    + fix: backport security hardening (#1986, #1995)
+      ° Create persisted history file with owner-only permissions
+      ° Use exclusive create for extracted native library temp files
+  * Bug Fixes
+    + fix: warn on insecure permissions when history file created
+      concurrently
+  * Dependency Updates
+    + chore: bump eu.maveniverse.maven.njord:extension3 from 0.9.8
+      to 0.9.9 (#1999)
+    + chore: bump com.palantir.javaformat:palantir-java-format
+      (#1991)
+    + chore: bump actions/cache from 5 to 6 (#1989)
+- Modified patch:
+  * 0001-Remove-optional-dependency-on-universalchardet.patch
+    + rebase
+
+-------------------------------------------------------------------

Old:
----
  jline-3.30.14.tar.gz

New:
----
  jline-3.30.15.tar.gz

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

Other differences:
------------------
++++++ jline3.spec ++++++
--- /var/tmp/diff_new_pack.OzWhHC/_old  2026-07-01 17:12:51.728178504 +0200
+++ /var/tmp/diff_new_pack.OzWhHC/_new  2026-07-01 17:12:51.728178504 +0200
@@ -31,7 +31,7 @@
 %endif
 %bcond_with ssh
 Name:           jline3
-Version:        3.30.14
+Version:        3.30.15
 Release:        0
 Summary:        Java library for handling console input
 License:        BSD-3-Clause

++++++ 0001-Remove-optional-dependency-on-universalchardet.patch ++++++
--- /var/tmp/diff_new_pack.OzWhHC/_old  2026-07-01 17:12:51.756179469 +0200
+++ /var/tmp/diff_new_pack.OzWhHC/_new  2026-07-01 17:12:51.760179607 +0200
@@ -1,4 +1,4 @@
-From f3eda0bf4fa1b9efb0562224fa3c090ba59f0253 Mon Sep 17 00:00:00 2001
+From 122ad3693ebf643e8f3d0eafd9ec4ac5b13130d7 Mon Sep 17 00:00:00 2001
 From: Mikolaj Izdebski <[email protected]>
 Date: Wed, 26 Feb 2025 16:26:49 +0100
 Subject: [PATCH] Remove optional dependency on universalchardet
@@ -8,18 +8,18 @@
  1 file changed, 12 deletions(-)
 
 diff --git a/builtins/src/main/java/org/jline/builtins/Nano.java 
b/builtins/src/main/java/org/jline/builtins/Nano.java
-index 491d0bc3..be5748c1 100644
+index b5112c2e..7d3bb3a7 100644
 --- a/builtins/src/main/java/org/jline/builtins/Nano.java
 +++ b/builtins/src/main/java/org/jline/builtins/Nano.java
-@@ -51,7 +51,6 @@ import org.jline.terminal.impl.MouseSupport;
- import org.jline.utils.*;
- import org.jline.utils.InfoCmp.Capability;
+@@ -53,7 +53,6 @@ import org.jline.utils.InfoCmp.Capability;
+ import org.jline.utils.RegexTimeoutException;
+ import org.jline.utils.SafeRegex;
  import org.jline.utils.Status;
 -import org.mozilla.universalchardet.UniversalDetector;
  
  import static org.jline.builtins.SyntaxHighlighter.*;
  import static org.jline.keymap.KeyMap.KEYMAP_LENGTH;
-@@ -380,17 +379,6 @@ public class Nano implements Editor {
+@@ -382,17 +381,6 @@ public class Nano implements Editor {
              }
              byte[] bytes = bos.toByteArray();
  
@@ -38,6 +38,6 @@
              try (BufferedReader reader =
                      new BufferedReader(new InputStreamReader(new 
ByteArrayInputStream(bytes), charset))) {
 -- 
-2.49.0
+2.54.0
 
 

++++++ _scmsync.obsinfo ++++++
--- /var/tmp/diff_new_pack.OzWhHC/_old  2026-07-01 17:12:51.804181124 +0200
+++ /var/tmp/diff_new_pack.OzWhHC/_new  2026-07-01 17:12:51.816181537 +0200
@@ -1,6 +1,6 @@
-mtime: 1782808776
-commit: ed2991ad1cda5cd9fc917625410e7471557791f8e6156aece1d4759ea7a32b27
+mtime: 1782913240
+commit: c3113bd96cf7b714c5019d392c92a1b96b2ec4ac6b6e2504d14dac0677a69ee9
 url: https://src.opensuse.org/java-packages/jline3
-revision: ed2991ad1cda5cd9fc917625410e7471557791f8e6156aece1d4759ea7a32b27
+revision: c3113bd96cf7b714c5019d392c92a1b96b2ec4ac6b6e2504d14dac0677a69ee9
 projectscmsync: https://src.opensuse.org/java-packages/_ObsPrj
 

++++++ build.specials.obscpio ++++++

++++++ build.specials.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/.gitignore new/.gitignore
--- old/.gitignore      1970-01-01 01:00:00.000000000 +0100
+++ new/.gitignore      2026-07-01 15:40:40.000000000 +0200
@@ -0,0 +1 @@
+.osc

++++++ jline-3.30.14.tar.gz -> jline-3.30.15.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jline3-jline-3.30.14/.github/workflows/master-build.yml 
new/jline3-jline-3.30.15/.github/workflows/master-build.yml
--- old/jline3-jline-3.30.14/.github/workflows/master-build.yml 2026-06-26 
20:10:56.000000000 +0200
+++ new/jline3-jline-3.30.15/.github/workflows/master-build.yml 2026-06-30 
21:19:48.000000000 +0200
@@ -40,7 +40,7 @@
           distribution: temurin
 
       - name: Cache Maven dependencies
-        uses: actions/cache@v5
+        uses: actions/cache@v6
         with:
           path: ~/.m2/repository
           key: ${{ runner.os }}-maven-${{ matrix.java }}-${{ 
hashFiles('**/pom.xml') }}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jline3-jline-3.30.14/.mvn/extensions.xml 
new/jline3-jline-3.30.15/.mvn/extensions.xml
--- old/jline3-jline-3.30.14/.mvn/extensions.xml        2026-06-26 
20:10:56.000000000 +0200
+++ new/jline3-jline-3.30.15/.mvn/extensions.xml        2026-06-30 
21:19:48.000000000 +0200
@@ -3,6 +3,6 @@
   <extension>
     <groupId>eu.maveniverse.maven.njord</groupId>
     <artifactId>extension3</artifactId>
-    <version>0.9.8</version>
+    <version>0.9.9</version>
   </extension>
 </extensions>
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jline3-jline-3.30.14/builtins/pom.xml 
new/jline3-jline-3.30.15/builtins/pom.xml
--- old/jline3-jline-3.30.14/builtins/pom.xml   2026-06-26 20:10:56.000000000 
+0200
+++ new/jline3-jline-3.30.15/builtins/pom.xml   2026-06-30 21:19:48.000000000 
+0200
@@ -16,7 +16,7 @@
     <parent>
         <groupId>org.jline</groupId>
         <artifactId>jline-parent</artifactId>
-        <version>3.30.14</version>
+        <version>3.30.15</version>
     </parent>
 
     <artifactId>jline-builtins</artifactId>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jline3-jline-3.30.14/builtins/src/main/java/org/jline/builtins/Commands.java
 
new/jline3-jline-3.30.15/builtins/src/main/java/org/jline/builtins/Commands.java
--- 
old/jline3-jline-3.30.14/builtins/src/main/java/org/jline/builtins/Commands.java
    2026-06-26 20:10:56.000000000 +0200
+++ 
new/jline3-jline-3.30.15/builtins/src/main/java/org/jline/builtins/Commands.java
    2026-06-30 21:19:48.000000000 +0200
@@ -45,6 +45,7 @@
 import org.jline.terminal.Terminal;
 import org.jline.utils.AttributedStringBuilder;
 import org.jline.utils.AttributedStyle;
+import org.jline.utils.SafeRegex;
 import org.jline.utils.StyleResolver;
 
 import static org.jline.builtins.SyntaxHighlighter.*;
@@ -360,16 +361,7 @@
 
         Pattern pattern = null;
         if (opt.isSet("m") && opt.args().size() > argId) {
-            StringBuilder sb = new StringBuilder();
-            char prev = '0';
-            for (char c : opt.args().get(argId++).toCharArray()) {
-                if (c == '*' && prev != '\\' && prev != '.') {
-                    sb.append('.');
-                }
-                sb.append(c);
-                prev = c;
-            }
-            pattern = Pattern.compile(sb.toString(), Pattern.DOTALL);
+            pattern = SafeRegex.compileGlob(opt.args().get(argId++), 
Pattern.DOTALL);
         }
         boolean reverse = opt.isSet("r") || (opt.isSet("s") && 
opt.args().size() <= argId);
         int firstId = opt.args().size() > argId
@@ -399,7 +391,7 @@
         while (iter.hasNext() && listed < tot) {
             History.Entry entry = iter.next();
             listed++;
-            if (pattern != null && !pattern.matcher(entry.line()).matches()) {
+            if (pattern != null && !SafeRegex.matches(pattern, entry.line())) {
                 continue;
             }
             if (execute.isExecute()) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jline3-jline-3.30.14/builtins/src/main/java/org/jline/builtins/Less.java 
new/jline3-jline-3.30.15/builtins/src/main/java/org/jline/builtins/Less.java
--- 
old/jline3-jline-3.30.14/builtins/src/main/java/org/jline/builtins/Less.java    
    2026-06-26 20:10:56.000000000 +0200
+++ 
new/jline3-jline-3.30.15/builtins/src/main/java/org/jline/builtins/Less.java    
    2026-06-30 21:19:48.000000000 +0200
@@ -42,6 +42,7 @@
 import org.jline.utils.Display;
 import org.jline.utils.InfoCmp.Capability;
 import org.jline.utils.NonBlockingReader;
+import org.jline.utils.SafeRegex;
 import org.jline.utils.Status;
 
 import static org.jline.builtins.SyntaxHighlighter.*;
@@ -1095,7 +1096,7 @@
                     break;
                 } else if (!toBeDisplayed(line, dpCompiled)) {
                     continue;
-                } else if (compiled.matcher(line).find()) {
+                } else if (SafeRegex.find(compiled, line)) {
                     display.clear();
                     firstLineToDisplay = lineNumber;
                     offsetInLine = 0;
@@ -1135,7 +1136,7 @@
                     break;
                 } else if (!toBeDisplayed(line, dpCompiled)) {
                     continue;
-                } else if (compiled.matcher(line).find()) {
+                } else if (SafeRegex.find(compiled, line)) {
                     display.clear();
                     firstLineToDisplay = lineNumber;
                     offsetInLine = 0;
@@ -1309,10 +1310,7 @@
     }
 
     private boolean toBeDisplayed(AttributedString curLine, Pattern 
dpCompiled) {
-        return curLine == null
-                || dpCompiled == null
-                || sourceIdx == 0
-                || dpCompiled.matcher(curLine).find();
+        return curLine == null || dpCompiled == null || sourceIdx == 0 || 
SafeRegex.find(dpCompiled, curLine);
     }
 
     synchronized boolean display(boolean oneScreen) throws IOException {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jline3-jline-3.30.14/builtins/src/main/java/org/jline/builtins/Nano.java 
new/jline3-jline-3.30.15/builtins/src/main/java/org/jline/builtins/Nano.java
--- 
old/jline3-jline-3.30.14/builtins/src/main/java/org/jline/builtins/Nano.java    
    2026-06-26 20:10:56.000000000 +0200
+++ 
new/jline3-jline-3.30.15/builtins/src/main/java/org/jline/builtins/Nano.java    
    2026-06-30 21:19:48.000000000 +0200
@@ -50,6 +50,8 @@
 import org.jline.terminal.impl.MouseSupport;
 import org.jline.utils.*;
 import org.jline.utils.InfoCmp.Capability;
+import org.jline.utils.RegexTimeoutException;
+import org.jline.utils.SafeRegex;
 import org.jline.utils.Status;
 import org.mozilla.universalchardet.UniversalDetector;
 
@@ -1321,15 +1323,20 @@
         }
 
         private List<Integer> doSearch(String text) {
+            matchedLength = 0;
             Pattern pat = Pattern.compile(
                     searchTerm,
                     (searchCaseSensitive ? 0 : Pattern.CASE_INSENSITIVE | 
Pattern.UNICODE_CASE)
                             | (searchRegexp ? 0 : Pattern.LITERAL));
-            Matcher m = pat.matcher(text);
+            Matcher m = SafeRegex.matcher(pat, text);
             List<Integer> res = new ArrayList<>();
-            while (m.find()) {
-                res.add(m.start());
-                matchedLength = m.group(0).length();
+            try {
+                while (m.find()) {
+                    res.add(m.start());
+                    matchedLength = m.end() - m.start();
+                }
+            } catch (RegexTimeoutException e) {
+                // Return whatever matches were found before timeout
             }
             return res;
         }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jline3-jline-3.30.14/builtins/src/main/java/org/jline/builtins/PosixCommands.java
 
new/jline3-jline-3.30.15/builtins/src/main/java/org/jline/builtins/PosixCommands.java
--- 
old/jline3-jline-3.30.14/builtins/src/main/java/org/jline/builtins/PosixCommands.java
       2026-06-26 20:10:56.000000000 +0200
+++ 
new/jline3-jline-3.30.15/builtins/src/main/java/org/jline/builtins/PosixCommands.java
       2026-06-30 21:19:48.000000000 +0200
@@ -41,6 +41,8 @@
 import org.jline.utils.AttributedStringBuilder;
 import org.jline.utils.InfoCmp.Capability;
 import org.jline.utils.OSUtils;
+import org.jline.utils.RegexTimeoutException;
+import org.jline.utils.SafeRegex;
 import org.jline.utils.StyleResolver;
 
 /**
@@ -1319,10 +1321,9 @@
         if (opt.isSet("word-regexp")) {
             regexp = "\\b" + regexp + "\\b";
         }
-        if (opt.isSet("line-regexp")) {
+        boolean lineRegexp = opt.isSet("line-regexp");
+        if (lineRegexp) {
             regexp = "^" + regexp + "$";
-        } else {
-            regexp = ".*" + regexp + ".*";
         }
         Pattern p;
         Pattern p2;
@@ -1394,7 +1395,7 @@
                     int lineno = 1;
                     int lineMatch = 0;
                     while ((line = r.readLine()) != null) {
-                        boolean matches = p.matcher(line).matches();
+                        boolean matches = lineRegexp ? SafeRegex.matches(p, 
line) : SafeRegex.find(p, line);
                         if (invert) {
                             matches = !matches;
                         }
@@ -1423,14 +1424,18 @@
                                     sbl.append(":");
                                 }
                                 if (colored) {
-                                    Matcher matcher2 = p2.matcher(line);
+                                    Matcher matcher2 = SafeRegex.matcher(p2, 
line);
                                     int cur = 0;
-                                    while (matcher2.find()) {
-                                        sbl.append(line, cur, 
matcher2.start());
-                                        applyStyle(sbl, colors, "ms");
-                                        sbl.append(line, matcher2.start(), 
matcher2.end());
-                                        applyStyle(sbl, colors, "se");
-                                        cur = matcher2.end();
+                                    try {
+                                        while (matcher2.find()) {
+                                            sbl.append(line, cur, 
matcher2.start());
+                                            applyStyle(sbl, colors, "ms");
+                                            sbl.append(line, matcher2.start(), 
matcher2.end());
+                                            applyStyle(sbl, colors, "se");
+                                            cur = matcher2.end();
+                                        }
+                                    } catch (RegexTimeoutException e) {
+                                        // Append remaining text without 
highlighting
                                     }
                                     sbl.append(line, cur, line.length());
                                 } else {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jline3-jline-3.30.14/console/pom.xml 
new/jline3-jline-3.30.15/console/pom.xml
--- old/jline3-jline-3.30.14/console/pom.xml    2026-06-26 20:10:56.000000000 
+0200
+++ new/jline3-jline-3.30.15/console/pom.xml    2026-06-30 21:19:48.000000000 
+0200
@@ -16,7 +16,7 @@
     <parent>
         <groupId>org.jline</groupId>
         <artifactId>jline-parent</artifactId>
-        <version>3.30.14</version>
+        <version>3.30.15</version>
     </parent>
 
     <artifactId>jline-console</artifactId>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jline3-jline-3.30.14/console-ui/pom.xml 
new/jline3-jline-3.30.15/console-ui/pom.xml
--- old/jline3-jline-3.30.14/console-ui/pom.xml 2026-06-26 20:10:56.000000000 
+0200
+++ new/jline3-jline-3.30.15/console-ui/pom.xml 2026-06-30 21:19:48.000000000 
+0200
@@ -5,7 +5,7 @@
     <parent>
         <groupId>org.jline</groupId>
         <artifactId>jline-parent</artifactId>
-        <version>3.30.14</version>
+        <version>3.30.15</version>
     </parent>
 
     <artifactId>jline-console-ui</artifactId>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jline3-jline-3.30.14/curses/pom.xml 
new/jline3-jline-3.30.15/curses/pom.xml
--- old/jline3-jline-3.30.14/curses/pom.xml     2026-06-26 20:10:56.000000000 
+0200
+++ new/jline3-jline-3.30.15/curses/pom.xml     2026-06-30 21:19:48.000000000 
+0200
@@ -16,7 +16,7 @@
     <parent>
         <groupId>org.jline</groupId>
         <artifactId>jline-parent</artifactId>
-        <version>3.30.14</version>
+        <version>3.30.15</version>
     </parent>
 
     <artifactId>jline-curses</artifactId>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jline3-jline-3.30.14/demo/pom.xml 
new/jline3-jline-3.30.15/demo/pom.xml
--- old/jline3-jline-3.30.14/demo/pom.xml       2026-06-26 20:10:56.000000000 
+0200
+++ new/jline3-jline-3.30.15/demo/pom.xml       2026-06-30 21:19:48.000000000 
+0200
@@ -16,7 +16,7 @@
     <parent>
         <groupId>org.jline</groupId>
         <artifactId>jline-parent</artifactId>
-        <version>3.30.14</version>
+        <version>3.30.15</version>
     </parent>
 
     <artifactId>jline-demo</artifactId>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jline3-jline-3.30.14/graal/pom.xml 
new/jline3-jline-3.30.15/graal/pom.xml
--- old/jline3-jline-3.30.14/graal/pom.xml      2026-06-26 20:10:56.000000000 
+0200
+++ new/jline3-jline-3.30.15/graal/pom.xml      2026-06-30 21:19:48.000000000 
+0200
@@ -16,7 +16,7 @@
     <parent>
         <groupId>org.jline</groupId>
         <artifactId>jline-parent</artifactId>
-        <version>3.30.14</version>
+        <version>3.30.15</version>
     </parent>
 
     <artifactId>jline-graal</artifactId>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jline3-jline-3.30.14/groovy/pom.xml 
new/jline3-jline-3.30.15/groovy/pom.xml
--- old/jline3-jline-3.30.14/groovy/pom.xml     2026-06-26 20:10:56.000000000 
+0200
+++ new/jline3-jline-3.30.15/groovy/pom.xml     2026-06-30 21:19:48.000000000 
+0200
@@ -14,7 +14,7 @@
     <parent>
         <groupId>org.jline</groupId>
         <artifactId>jline-parent</artifactId>
-        <version>3.30.14</version>
+        <version>3.30.15</version>
     </parent>
     <artifactId>jline-groovy</artifactId>
     <name>JLine Groovy</name>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jline3-jline-3.30.14/jansi/pom.xml 
new/jline3-jline-3.30.15/jansi/pom.xml
--- old/jline3-jline-3.30.14/jansi/pom.xml      2026-06-26 20:10:56.000000000 
+0200
+++ new/jline3-jline-3.30.15/jansi/pom.xml      2026-06-30 21:19:48.000000000 
+0200
@@ -16,7 +16,7 @@
     <parent>
         <groupId>org.jline</groupId>
         <artifactId>jline-parent</artifactId>
-        <version>3.30.14</version>
+        <version>3.30.15</version>
     </parent>
 
     <artifactId>jansi</artifactId>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jline3-jline-3.30.14/jansi-core/pom.xml 
new/jline3-jline-3.30.15/jansi-core/pom.xml
--- old/jline3-jline-3.30.14/jansi-core/pom.xml 2026-06-26 20:10:56.000000000 
+0200
+++ new/jline3-jline-3.30.15/jansi-core/pom.xml 2026-06-30 21:19:48.000000000 
+0200
@@ -16,7 +16,7 @@
     <parent>
         <groupId>org.jline</groupId>
         <artifactId>jline-parent</artifactId>
-        <version>3.30.14</version>
+        <version>3.30.15</version>
     </parent>
 
     <artifactId>jansi-core</artifactId>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jline3-jline-3.30.14/jline/pom.xml 
new/jline3-jline-3.30.15/jline/pom.xml
--- old/jline3-jline-3.30.14/jline/pom.xml      2026-06-26 20:10:56.000000000 
+0200
+++ new/jline3-jline-3.30.15/jline/pom.xml      2026-06-30 21:19:48.000000000 
+0200
@@ -16,7 +16,7 @@
     <parent>
         <groupId>org.jline</groupId>
         <artifactId>jline-parent</artifactId>
-        <version>3.30.14</version>
+        <version>3.30.15</version>
     </parent>
 
     <artifactId>jline</artifactId>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jline3-jline-3.30.14/native/pom.xml 
new/jline3-jline-3.30.15/native/pom.xml
--- old/jline3-jline-3.30.14/native/pom.xml     2026-06-26 20:10:56.000000000 
+0200
+++ new/jline3-jline-3.30.15/native/pom.xml     2026-06-30 21:19:48.000000000 
+0200
@@ -15,7 +15,7 @@
     <parent>
         <groupId>org.jline</groupId>
         <artifactId>jline-parent</artifactId>
-        <version>3.30.14</version>
+        <version>3.30.15</version>
     </parent>
 
     <artifactId>jline-native</artifactId>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jline3-jline-3.30.14/native/src/main/java/org/jline/nativ/JLineNativeLoader.java
 
new/jline3-jline-3.30.15/native/src/main/java/org/jline/nativ/JLineNativeLoader.java
--- 
old/jline3-jline-3.30.14/native/src/main/java/org/jline/nativ/JLineNativeLoader.java
        2026-06-26 20:10:56.000000000 +0200
+++ 
new/jline3-jline-3.30.15/native/src/main/java/org/jline/nativ/JLineNativeLoader.java
        2026-06-30 21:19:48.000000000 +0200
@@ -29,17 +29,18 @@
 
 import java.io.File;
 import java.io.FileInputStream;
-import java.io.FileOutputStream;
 import java.io.FilenameFilter;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.StandardOpenOption;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Properties;
-import java.util.Random;
+import java.util.concurrent.ThreadLocalRandom;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
@@ -327,12 +328,13 @@
         File extractedLckFile = new File(targetFolder, extractedLckFileName);
 
         try {
-            // Extract a native library file into the target directory
+            // Extract a native library file into the target directory.
+            // The target sits in the shared system temp directory, so open 
both
+            // files with CREATE_NEW: an existing path or a symlink planted 
there
+            // by another local user is refused instead of being followed.
             try (InputStream in = 
JLineNativeLoader.class.getResourceAsStream(nativeLibraryFilePath)) {
-                if (!extractedLckFile.exists()) {
-                    new FileOutputStream(extractedLckFile).close();
-                }
-                try (OutputStream out = new 
FileOutputStream(extractedLibFile)) {
+                newExclusiveStream(extractedLckFile).close();
+                try (OutputStream out = newExclusiveStream(extractedLibFile)) {
                     copy(in, out);
                 }
             } finally {
@@ -371,7 +373,17 @@
     }
 
     private static String randomUUID() {
-        return Long.toHexString(new Random().nextLong());
+        return Long.toHexString(ThreadLocalRandom.current().nextLong());
+    }
+
+    /**
+     * Opens an output stream that creates a brand new file, failing if the 
path already
+     * exists. {@code CREATE_NEW} maps to an exclusive create at the OS level, 
so a symlink
+     * or a regular file planted at the target path in the shared temp 
directory is rejected
+     * rather than followed.
+     */
+    static OutputStream newExclusiveStream(File file) throws IOException {
+        return Files.newOutputStream(file.toPath(), 
StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE);
     }
 
     private static void copy(InputStream in, OutputStream out) throws 
IOException {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jline3-jline-3.30.14/native/src/test/java/org/jline/nativ/JLineNativeLoaderTest.java
 
new/jline3-jline-3.30.15/native/src/test/java/org/jline/nativ/JLineNativeLoaderTest.java
--- 
old/jline3-jline-3.30.14/native/src/test/java/org/jline/nativ/JLineNativeLoaderTest.java
    2026-06-26 20:10:56.000000000 +0200
+++ 
new/jline3-jline-3.30.15/native/src/test/java/org/jline/nativ/JLineNativeLoaderTest.java
    2026-06-30 21:19:48.000000000 +0200
@@ -8,7 +8,17 @@
  */
 package org.jline.nativ;
 
+import java.io.File;
+import java.io.OutputStream;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 public class JLineNativeLoaderTest {
 
@@ -16,4 +26,22 @@
     public void testLoadLibrary() {
         JLineNativeLoader.initialize();
     }
+
+    @Test
+    void exclusiveStreamRefusesExistingTarget(@TempDir Path dir) throws 
Exception {
+        // A file already sitting at the target path stands in for a symlink 
an attacker
+        // planted in the shared temp directory: the open must fail instead of 
writing through it.
+        File planted = dir.resolve("jlinenative-planted").toFile();
+        assertTrue(planted.createNewFile());
+        assertThrows(
+                FileAlreadyExistsException.class,
+                () -> JLineNativeLoader.newExclusiveStream(planted).close());
+
+        // A fresh path is created normally.
+        File fresh = dir.resolve("jlinenative-fresh").toFile();
+        try (OutputStream out = JLineNativeLoader.newExclusiveStream(fresh)) {
+            out.write(new byte[] {1, 2, 3});
+        }
+        assertTrue(Files.isRegularFile(fresh.toPath()));
+    }
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jline3-jline-3.30.14/pom.xml 
new/jline3-jline-3.30.15/pom.xml
--- old/jline3-jline-3.30.14/pom.xml    2026-06-26 20:10:56.000000000 +0200
+++ new/jline3-jline-3.30.15/pom.xml    2026-06-30 21:19:48.000000000 +0200
@@ -15,7 +15,7 @@
 
     <groupId>org.jline</groupId>
     <artifactId>jline-parent</artifactId>
-    <version>3.30.14</version>
+    <version>3.30.15</version>
     <packaging>pom</packaging>
     <name>JLine</name>
     <description>JLine</description>
@@ -74,7 +74,7 @@
     <scm child.scm.connection.inherit.append.path="false" 
child.scm.developerConnection.inherit.append.path="false" 
child.scm.url.inherit.append.path="false">
         <connection>scm:git:https://github.com/jline/jline3.git</connection>
         
<developerConnection>scm:git:https://github.com/jline/jline3.git</developerConnection>
-        <tag>jline-3.30.14</tag>
+        <tag>jline-3.30.15</tag>
         <url>https://github.com/jline/jline3</url>
     </scm>
 
@@ -98,7 +98,7 @@
 
     <properties>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
-        
<project.build.outputTimestamp>2026-06-26T18:08:31Z</project.build.outputTimestamp>
+        
<project.build.outputTimestamp>2026-06-30T19:17:34Z</project.build.outputTimestamp>
 
         <java.build.version>22</java.build.version>
         <java.release.version>8</java.release.version>
@@ -121,7 +121,7 @@
         <ivy.version>2.5.3</ivy.version>
         <graal.version>25.0.3</graal.version>
         <graal.plugin.version>21.2.0</graal.plugin.version>
-        <palantir.version>2.93.0</palantir.version>
+        <palantir.version>2.94.0</palantir.version>
 
         <surefire.argLine>--add-opens 
java.base/java.io=ALL-UNNAMED</surefire.argLine>
     </properties>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jline3-jline-3.30.14/reader/pom.xml 
new/jline3-jline-3.30.15/reader/pom.xml
--- old/jline3-jline-3.30.14/reader/pom.xml     2026-06-26 20:10:56.000000000 
+0200
+++ new/jline3-jline-3.30.15/reader/pom.xml     2026-06-30 21:19:48.000000000 
+0200
@@ -16,7 +16,7 @@
     <parent>
         <groupId>org.jline</groupId>
         <artifactId>jline-parent</artifactId>
-        <version>3.30.14</version>
+        <version>3.30.15</version>
     </parent>
 
     <artifactId>jline-reader</artifactId>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jline3-jline-3.30.14/reader/src/main/java/org/jline/reader/impl/LineReaderImpl.java
 
new/jline3-jline-3.30.15/reader/src/main/java/org/jline/reader/impl/LineReaderImpl.java
--- 
old/jline3-jline-3.30.14/reader/src/main/java/org/jline/reader/impl/LineReaderImpl.java
     2026-06-26 20:10:56.000000000 +0200
+++ 
new/jline3-jline-3.30.15/reader/src/main/java/org/jline/reader/impl/LineReaderImpl.java
     2026-06-30 21:19:48.000000000 +0200
@@ -49,6 +49,8 @@
 import org.jline.utils.Display;
 import org.jline.utils.InfoCmp.Capability;
 import org.jline.utils.Log;
+import org.jline.utils.RegexTimeoutException;
+import org.jline.utils.SafeRegex;
 import org.jline.utils.Status;
 import org.jline.utils.StyleResolver;
 import org.jline.utils.WCWidth;
@@ -2856,9 +2858,13 @@
 
     private List<Pair<Integer, Integer>> matches(Pattern p, String line, int 
index) {
         List<Pair<Integer, Integer>> starts = new ArrayList<>();
-        Matcher m = p.matcher(line);
-        while (m.find()) {
-            starts.add(new Pair<>(index, m.start()));
+        Matcher m = SafeRegex.matcher(p, line);
+        try {
+            while (m.find()) {
+                starts.add(new Pair<>(index, m.start()));
+            }
+        } catch (RegexTimeoutException e) {
+            // Return whatever matches were found before timeout
         }
         return starts;
     }
@@ -4118,25 +4124,22 @@
             return "";
         }
         History history = getHistory();
-        StringBuilder sb = new StringBuilder();
-        for (char c : buffer.replace("\\", "\\\\").toCharArray()) {
-            if (c == '(' || c == ')' || c == '[' || c == ']' || c == '{' || c 
== '}' || c == '^' || c == '*' || c == '$'
-                    || c == '.' || c == '?' || c == '+' || c == '|' || c == 
'<' || c == '>' || c == '!' || c == '-') {
-                sb.append('\\');
-            }
-            sb.append(c);
-        }
-        Pattern pattern = Pattern.compile(sb.toString() + ".*", 
Pattern.DOTALL);
+        Pattern pattern = Pattern.compile(Pattern.quote(buffer) + ".*", 
Pattern.DOTALL);
         Iterator<History.Entry> iter = history.reverseIterator(history.last());
         String suggestion = "";
         int tot = 0;
         while (iter.hasNext()) {
             History.Entry entry = iter.next();
-            Matcher matcher = pattern.matcher(entry.line());
-            if (matcher.matches()) {
-                suggestion = entry.line().substring(buffer.length());
-                break;
-            } else if (tot > 200) {
+            try {
+                Matcher matcher = SafeRegex.matcher(pattern, entry.line());
+                if (matcher.matches()) {
+                    suggestion = entry.line().substring(buffer.length());
+                    break;
+                }
+            } catch (RegexTimeoutException e) {
+                // Treat timeout as non-match, continue searching
+            }
+            if (tot > 200) {
                 break;
             }
             tot++;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jline3-jline-3.30.14/reader/src/main/java/org/jline/reader/impl/history/DefaultHistory.java
 
new/jline3-jline-3.30.15/reader/src/main/java/org/jline/reader/impl/history/DefaultHistory.java
--- 
old/jline3-jline-3.30.14/reader/src/main/java/org/jline/reader/impl/history/DefaultHistory.java
     2026-06-26 20:10:56.000000000 +0200
+++ 
new/jline3-jline-3.30.15/reader/src/main/java/org/jline/reader/impl/history/DefaultHistory.java
     2026-06-30 21:19:48.000000000 +0200
@@ -10,13 +10,17 @@
 
 import java.io.*;
 import java.nio.file.*;
+import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.PosixFilePermissions;
 import java.time.DateTimeException;
 import java.time.Instant;
 import java.util.*;
+import java.util.regex.Pattern;
 
 import org.jline.reader.History;
 import org.jline.reader.LineReader;
 import org.jline.utils.Log;
+import org.jline.utils.SafeRegex;
 
 import static org.jline.reader.LineReader.HISTORY_IGNORE;
 import static org.jline.reader.impl.ReaderUtils.*;
@@ -388,12 +392,13 @@
             if (!Files.exists(parent)) {
                 Files.createDirectories(parent);
             }
-            // Append new items to the history file
+            createWithOwnerOnlyPermissions(path.toAbsolutePath());
+            // Append new items to the history file. 
createWithOwnerOnlyPermissions guarantees the
+            // file exists, so CREATE is intentionally omitted: should the 
file be removed in the
+            // narrow window before this open, fail with NoSuchFileException 
rather than silently
+            // re-creating it with umask-default (group/world readable) 
permissions.
             try (BufferedWriter writer = Files.newBufferedWriter(
-                    path.toAbsolutePath(),
-                    StandardOpenOption.WRITE,
-                    StandardOpenOption.APPEND,
-                    StandardOpenOption.CREATE)) {
+                    path.toAbsolutePath(), StandardOpenOption.WRITE, 
StandardOpenOption.APPEND)) {
                 for (Entry entry : items.subList(from, items.size())) {
                     if (isPersistable(entry)) {
                         writer.append(format(entry));
@@ -410,6 +415,63 @@
     }
 
     /**
+     * Creates the history file restricted to the owner when it does not exist 
yet.
+     * History lines can contain secrets typed on the command line, so the file
+     * must not be left group/world readable as the umask default (0644) would.
+     * On non-POSIX filesystems the file is created with the platform default.
+     */
+    private static void createWithOwnerOnlyPermissions(Path path) throws 
IOException {
+        if (Files.exists(path)) {
+            // A pre-existing file (e.g. created by an older JLine without 
this fix) keeps its
+            // current permissions; we don't silently tighten it, but warn so 
the user can.
+            warnIfAccessibleByOthers(path);
+            return;
+        }
+        try {
+            Files.createFile(
+                    path,
+                    PosixFilePermissions.asFileAttribute(
+                            EnumSet.of(PosixFilePermission.OWNER_READ, 
PosixFilePermission.OWNER_WRITE)));
+        } catch (FileAlreadyExistsException e) {
+            // created concurrently between the check and the call — warn if 
the
+            // other creator left the file group/world readable
+            warnIfAccessibleByOthers(path);
+        } catch (UnsupportedOperationException e) {
+            // non-POSIX filesystem (e.g. Windows): fall back to default 
creation
+            try {
+                Files.createFile(path);
+            } catch (FileAlreadyExistsException ignore) {
+                // created concurrently (no POSIX perms to warn about)
+            }
+        }
+    }
+
+    private static final Set<Path> WARNED_INSECURE_PERMS = 
Collections.synchronizedSet(new HashSet<>());
+
+    private static final Set<PosixFilePermission> NON_OWNER_PERMS = EnumSet.of(
+            PosixFilePermission.GROUP_READ,
+            PosixFilePermission.GROUP_WRITE,
+            PosixFilePermission.GROUP_EXECUTE,
+            PosixFilePermission.OTHERS_READ,
+            PosixFilePermission.OTHERS_WRITE,
+            PosixFilePermission.OTHERS_EXECUTE);
+
+    /**
+     * Warns, at most once per path, when an existing history file is 
accessible by users other
+     * than the owner. The file's permissions are left untouched so as not to 
surprise the user.
+     */
+    private static void warnIfAccessibleByOthers(Path path) {
+        try {
+            Set<PosixFilePermission> perms = 
Files.getPosixFilePermissions(path);
+            if (perms.stream().anyMatch(NON_OWNER_PERMS::contains) && 
WARNED_INSECURE_PERMS.add(path)) {
+                Log.warn("History file ", path, " is accessible by other 
users; consider 'chmod 600'");
+            }
+        } catch (UnsupportedOperationException | IOException e) {
+            // non-POSIX filesystem or attributes unavailable: nothing to warn 
about
+        }
+    }
+
+    /**
      * Trims the history file to the specified maximum number of entries.
      *
      * @param path the path to the history file
@@ -608,21 +670,25 @@
         if (patterns == null || patterns.isEmpty()) {
             return false;
         }
+        // HISTORY_IGNORE uses a glob-like syntax where '*' matches any string,
+        // ':' separates alternatives, and '\' escapes the next character.
+        // All other characters must match literally — regex metacharacters
+        // are quoted to prevent ReDoS via catastrophic backtracking.
         StringBuilder sb = new StringBuilder();
         for (int i = 0; i < patterns.length(); i++) {
             char ch = patterns.charAt(i);
-            if (ch == '\\') {
+            if (ch == '\\' && i + 1 < patterns.length()) {
                 ch = patterns.charAt(++i);
-                sb.append(ch);
+                sb.append(Pattern.quote(String.valueOf(ch)));
             } else if (ch == ':') {
                 sb.append('|');
             } else if (ch == '*') {
-                sb.append('.').append('*');
+                sb.append(".*");
             } else {
-                sb.append(ch);
+                sb.append(Pattern.quote(String.valueOf(ch)));
             }
         }
-        return line.matches(sb.toString());
+        return SafeRegex.matches(Pattern.compile(sb.toString()), line);
     }
 
     /**
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jline3-jline-3.30.14/reader/src/test/java/org/jline/reader/impl/history/HistoryPersistenceTest.java
 
new/jline3-jline-3.30.15/reader/src/test/java/org/jline/reader/impl/history/HistoryPersistenceTest.java
--- 
old/jline3-jline-3.30.14/reader/src/test/java/org/jline/reader/impl/history/HistoryPersistenceTest.java
     2026-06-26 20:10:56.000000000 +0200
+++ 
new/jline3-jline-3.30.15/reader/src/test/java/org/jline/reader/impl/history/HistoryPersistenceTest.java
     2026-06-30 21:19:48.000000000 +0200
@@ -9,10 +9,16 @@
 package org.jline.reader.impl.history;
 
 import java.io.IOException;
+import java.nio.file.FileStore;
 import java.nio.file.Files;
+import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.nio.file.attribute.PosixFileAttributeView;
+import java.nio.file.attribute.PosixFilePermission;
 import java.time.Instant;
+import java.util.EnumSet;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.BrokenBarrierException;
 import java.util.concurrent.CyclicBarrier;
 import java.util.stream.IntStream;
@@ -21,6 +27,7 @@
 import org.jline.reader.impl.ReaderTestSupport;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Assumptions;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
@@ -119,6 +126,30 @@
     }
 
     @Test
+    public void testHistoryFilePermissions() throws Exception {
+        Path dir = Files.createTempDirectory("jline-hist-perm");
+        Path testPath = dir.resolve("history");
+        try {
+            FileStore store = Files.getFileStore(dir);
+            
Assumptions.assumeTrue(store.supportsFileAttributeView(PosixFileAttributeView.class));
+
+            reader.setVariable(LineReader.HISTORY_FILE, testPath);
+            DefaultHistory history = new DefaultHistory(reader);
+            history.add("password=hunter2");
+            history.save();
+
+            Set<PosixFilePermission> perms = 
Files.getPosixFilePermissions(testPath);
+            assertEquals(
+                    EnumSet.of(PosixFilePermission.OWNER_READ, 
PosixFilePermission.OWNER_WRITE),
+                    perms,
+                    "newly created history file must not be group/world 
readable");
+        } finally {
+            Files.deleteIfExists(testPath);
+            Files.deleteIfExists(dir);
+        }
+    }
+
+    @Test
     public void testHistoryTrimNonTimestamped() {
         testHistoryTrim(false);
     }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jline3-jline-3.30.14/remote-ssh/pom.xml 
new/jline3-jline-3.30.15/remote-ssh/pom.xml
--- old/jline3-jline-3.30.14/remote-ssh/pom.xml 2026-06-26 20:10:56.000000000 
+0200
+++ new/jline3-jline-3.30.15/remote-ssh/pom.xml 2026-06-30 21:19:48.000000000 
+0200
@@ -16,7 +16,7 @@
     <parent>
         <groupId>org.jline</groupId>
         <artifactId>jline-parent</artifactId>
-        <version>3.30.14</version>
+        <version>3.30.15</version>
     </parent>
 
     <artifactId>jline-remote-ssh</artifactId>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jline3-jline-3.30.14/remote-telnet/pom.xml 
new/jline3-jline-3.30.15/remote-telnet/pom.xml
--- old/jline3-jline-3.30.14/remote-telnet/pom.xml      2026-06-26 
20:10:56.000000000 +0200
+++ new/jline3-jline-3.30.15/remote-telnet/pom.xml      2026-06-30 
21:19:48.000000000 +0200
@@ -16,7 +16,7 @@
     <parent>
         <groupId>org.jline</groupId>
         <artifactId>jline-parent</artifactId>
-        <version>3.30.14</version>
+        <version>3.30.15</version>
     </parent>
 
     <artifactId>jline-remote-telnet</artifactId>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jline3-jline-3.30.14/style/pom.xml 
new/jline3-jline-3.30.15/style/pom.xml
--- old/jline3-jline-3.30.14/style/pom.xml      2026-06-26 20:10:56.000000000 
+0200
+++ new/jline3-jline-3.30.15/style/pom.xml      2026-06-30 21:19:48.000000000 
+0200
@@ -16,7 +16,7 @@
     <parent>
         <groupId>org.jline</groupId>
         <artifactId>jline-parent</artifactId>
-        <version>3.30.14</version>
+        <version>3.30.15</version>
     </parent>
 
     <artifactId>jline-style</artifactId>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jline3-jline-3.30.14/terminal/pom.xml 
new/jline3-jline-3.30.15/terminal/pom.xml
--- old/jline3-jline-3.30.14/terminal/pom.xml   2026-06-26 20:10:56.000000000 
+0200
+++ new/jline3-jline-3.30.15/terminal/pom.xml   2026-06-30 21:19:48.000000000 
+0200
@@ -16,7 +16,7 @@
     <parent>
         <groupId>org.jline</groupId>
         <artifactId>jline-parent</artifactId>
-        <version>3.30.14</version>
+        <version>3.30.15</version>
     </parent>
 
     <artifactId>jline-terminal</artifactId>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jline3-jline-3.30.14/terminal/src/main/java/org/jline/utils/AttributedString.java
 
new/jline3-jline-3.30.15/terminal/src/main/java/org/jline/utils/AttributedString.java
--- 
old/jline3-jline-3.30.14/terminal/src/main/java/org/jline/utils/AttributedString.java
       2026-06-26 20:10:56.000000000 +0200
+++ 
new/jline3-jline-3.30.15/terminal/src/main/java/org/jline/utils/AttributedString.java
       2026-06-30 21:19:48.000000000 +0200
@@ -440,16 +440,25 @@
      * @return a new AttributedString with the specified style applied to 
matching regions
      */
     public AttributedString styleMatches(Pattern pattern, AttributedStyle 
style) {
-        Matcher matcher = pattern.matcher(this);
-        boolean result = matcher.find();
+        Matcher matcher = SafeRegex.matcher(pattern, this);
+        boolean result;
+        try {
+            result = matcher.find();
+        } catch (RegexTimeoutException e) {
+            return this;
+        }
         if (result) {
             long[] newstyle = this.style.clone();
-            do {
-                for (int i = matcher.start(); i < matcher.end(); i++) {
-                    newstyle[this.start + i] = (newstyle[this.start + i] & 
~style.getMask()) | style.getStyle();
-                }
-                result = matcher.find();
-            } while (result);
+            try {
+                do {
+                    for (int i = matcher.start(); i < matcher.end(); i++) {
+                        newstyle[this.start + i] = (newstyle[this.start + i] & 
~style.getMask()) | style.getStyle();
+                    }
+                    result = matcher.find();
+                } while (result);
+            } catch (RegexTimeoutException e) {
+                // Apply whatever matches we found so far
+            }
             return new AttributedString(buffer, newstyle, start, end);
         }
         return this;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jline3-jline-3.30.14/terminal/src/main/java/org/jline/utils/RegexTimeoutException.java
 
new/jline3-jline-3.30.15/terminal/src/main/java/org/jline/utils/RegexTimeoutException.java
--- 
old/jline3-jline-3.30.14/terminal/src/main/java/org/jline/utils/RegexTimeoutException.java
  1970-01-01 01:00:00.000000000 +0100
+++ 
new/jline3-jline-3.30.15/terminal/src/main/java/org/jline/utils/RegexTimeoutException.java
  2026-06-30 21:19:48.000000000 +0200
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2026, the original author(s).
+ *
+ * This software is distributable under the BSD license. See the terms of the
+ * BSD license in the documentation provided with this software.
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ */
+package org.jline.utils;
+
+/**
+ * Thrown when a regular expression match exceeds its time budget.
+ *
+ * <p>This is an unchecked exception because it is thrown from within
+ * {@link CharSequence#charAt(int)}, which the {@code java.util.regex}
+ * engine calls during matching. Callers that use
+ * {@link SafeRegex#matcher(java.util.regex.Pattern, CharSequence)}
+ * directly should catch this exception; the convenience methods
+ * {@link SafeRegex#matches} and {@link SafeRegex#find} catch it
+ * internally and return {@code false}.</p>
+ */
+public class RegexTimeoutException extends RuntimeException {
+
+    private static final long serialVersionUID = 1L;
+
+    public RegexTimeoutException() {
+        super("Regular expression matching timed out");
+    }
+
+    public RegexTimeoutException(String message) {
+        super(message);
+    }
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jline3-jline-3.30.14/terminal/src/main/java/org/jline/utils/SafeRegex.java 
new/jline3-jline-3.30.15/terminal/src/main/java/org/jline/utils/SafeRegex.java
--- 
old/jline3-jline-3.30.14/terminal/src/main/java/org/jline/utils/SafeRegex.java  
    1970-01-01 01:00:00.000000000 +0100
+++ 
new/jline3-jline-3.30.15/terminal/src/main/java/org/jline/utils/SafeRegex.java  
    2026-06-30 21:19:48.000000000 +0200
@@ -0,0 +1,218 @@
+/*
+ * Copyright (c) 2026, the original author(s).
+ *
+ * This software is distributable under the BSD license. See the terms of the
+ * BSD license in the documentation provided with this software.
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ */
+package org.jline.utils;
+
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Regex utilities that guard against catastrophic backtracking (ReDoS).
+ *
+ * <p>Java's {@code java.util.regex} engine uses backtracking, so patterns with
+ * nested quantifiers (e.g. {@code (a+)+b}) can take exponential time on
+ * non-matching input. This class wraps input in a {@link CharSequence} that
+ * enforces a wall-clock deadline, throwing {@link RegexTimeoutException} if
+ * matching takes too long.</p>
+ *
+ * <p>Usage:</p>
+ * <ul>
+ *   <li>For simple boolean checks, use {@link #matches} or {@link #find} —
+ *       they catch timeouts and return {@code false}.</li>
+ *   <li>When you need the {@link Matcher} (e.g. for a find loop or to read
+ *       match groups), use {@link #matcher} and catch
+ *       {@link RegexTimeoutException} yourself.</li>
+ *   <li>For glob-style patterns (only {@code *} is special), use
+ *       {@link #compileGlob} to compile a {@link Pattern} that properly
+ *       escapes literal characters. Note: {@code compileGlob} only builds
+ *       the pattern; to get timeout protection, pass the result through
+ *       {@link #matcher}, {@link #matches}, or {@link #find}.</li>
+ * </ul>
+ */
+public final class SafeRegex {
+
+    /** Default timeout for regex matching operations. */
+    private static final long DEFAULT_TIMEOUT_MS = 1500;
+
+    /**
+     * How often (in {@code charAt} calls) the deadline is checked.
+     * Checking every call would add measurable overhead; checking every
+     * 1024 calls keeps the cost negligible while still catching runaway
+     * backtracking within milliseconds on typical input.
+     */
+    private static final int CHECK_INTERVAL = 1024;
+
+    private SafeRegex() {}
+
+    // ---- matcher factory ---------------------------------------------------
+
+    /**
+     * Create a {@link Matcher} that will throw {@link RegexTimeoutException}
+     * if matching exceeds the default timeout.
+     */
+    public static Matcher matcher(Pattern pattern, CharSequence input) {
+        return matcher(pattern, input, DEFAULT_TIMEOUT_MS);
+    }
+
+    /**
+     * Create a {@link Matcher} that will throw {@link RegexTimeoutException}
+     * if matching exceeds the given timeout.  The timeout starts lazily on
+     * the first deadline check during matching (not when the {@link Matcher}
+     * is created), so it measures actual matching time.
+     */
+    public static Matcher matcher(Pattern pattern, CharSequence input, long 
timeoutMs) {
+        long timeoutNanos = TimeUnit.MILLISECONDS.toNanos(timeoutMs);
+        return pattern.matcher(new TimeoutCharSequence(input, timeoutNanos));
+    }
+
+    // ---- convenience boolean methods ---------------------------------------
+
+    /**
+     * Test whether the pattern matches the entire input, with timeout
+     * protection. Returns {@code false} on timeout.
+     */
+    public static boolean matches(Pattern pattern, CharSequence input) {
+        try {
+            return matcher(pattern, input).matches();
+        } catch (RegexTimeoutException e) {
+            return false;
+        }
+    }
+
+    /**
+     * Test whether the pattern is found anywhere in the input, with timeout
+     * protection. Returns {@code false} on timeout.
+     */
+    public static boolean find(Pattern pattern, CharSequence input) {
+        try {
+            return matcher(pattern, input).find();
+        } catch (RegexTimeoutException e) {
+            return false;
+        }
+    }
+
+    // ---- glob compilation --------------------------------------------------
+
+    /**
+     * Compile a glob-style pattern into a {@link Pattern}.
+     *
+     * <p>Only {@code *} (match any string) and {@code \} (escape) are
+     * special; every other character is regex-quoted so it matches
+     * literally. This is suitable for user-facing wildcard syntax where
+     * full regex power is not intended.</p>
+     *
+     * @param globPattern the glob pattern (e.g. {@code "foo*bar"})
+     * @return a compiled regex pattern
+     */
+    public static Pattern compileGlob(String globPattern) {
+        return compileGlob(globPattern, 0);
+    }
+
+    /**
+     * Compile a glob-style pattern into a {@link Pattern} with the given
+     * regex flags.
+     *
+     * @param globPattern the glob pattern
+     * @param flags       regex flags (e.g. {@link Pattern#DOTALL})
+     * @return a compiled regex pattern
+     */
+    public static Pattern compileGlob(String globPattern, int flags) {
+        StringBuilder sb = new StringBuilder();
+        int i = 0;
+        while (i < globPattern.length()) {
+            char ch = globPattern.charAt(i);
+            if (ch == '\\' && i + 1 < globPattern.length()) {
+                appendQuoted(sb, globPattern.charAt(i + 1));
+                i += 2;
+            } else if (ch == '*') {
+                sb.append(".*");
+                i++;
+            } else {
+                appendQuoted(sb, ch);
+                i++;
+            }
+        }
+        return Pattern.compile(sb.toString(), flags);
+    }
+
+    // ---- internal ----------------------------------------------------------
+
+    private static void appendQuoted(StringBuilder sb, char ch) {
+        // Quote individual regex metacharacters inline rather than using
+        // Pattern.quote() (\Q...\E) to keep the generated regex readable.
+        if ("\\^$.|?*+()[]{}".indexOf(ch) >= 0) {
+            sb.append('\\');
+        }
+        sb.append(ch);
+    }
+
+    /**
+     * A {@link CharSequence} wrapper that enforces a wall-clock deadline.
+     *
+     * <p>The Java regex engine calls {@link #charAt(int)} for every position
+     * it considers during matching.  During catastrophic backtracking the
+     * call count explodes; we check {@link System#nanoTime()} every
+     * {@value #CHECK_INTERVAL} calls and throw if the deadline has passed.</p>
+     *
+     * <p>The deadline is lazily initialised on the first {@code charAt}
+     * check so the timeout measures actual matching time, not the gap
+     * between {@link Matcher} creation and first use.  Instances created
+     * via {@link #subSequence} share the same deadline array, so the
+     * timeout covers the entire matching operation.</p>
+     */
+    private static final class TimeoutCharSequence implements CharSequence {
+
+        private final CharSequence inner;
+        private final long timeoutNanos;
+        /** Shared across {@link #subSequence} calls; element 0 holds the 
deadline (0 = not yet started). */
+        private final long[] sharedDeadline;
+
+        private int calls;
+
+        TimeoutCharSequence(CharSequence inner, long timeoutNanos) {
+            this.inner = inner;
+            this.timeoutNanos = timeoutNanos;
+            this.sharedDeadline = new long[1];
+        }
+
+        private TimeoutCharSequence(CharSequence inner, long timeoutNanos, 
long[] sharedDeadline) {
+            this.inner = inner;
+            this.timeoutNanos = timeoutNanos;
+            this.sharedDeadline = sharedDeadline;
+        }
+
+        @Override
+        public char charAt(int index) {
+            if (++calls % CHECK_INTERVAL == 0) {
+                long now = System.nanoTime();
+                if (sharedDeadline[0] == 0) {
+                    sharedDeadline[0] = now + timeoutNanos;
+                } else if (now > sharedDeadline[0]) {
+                    throw new RegexTimeoutException();
+                }
+            }
+            return inner.charAt(index);
+        }
+
+        @Override
+        public int length() {
+            return inner.length();
+        }
+
+        @Override
+        public CharSequence subSequence(int start, int end) {
+            return new TimeoutCharSequence(inner.subSequence(start, end), 
timeoutNanos, sharedDeadline);
+        }
+
+        @Override
+        public String toString() {
+            return inner.toString();
+        }
+    }
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jline3-jline-3.30.14/terminal-ffm/pom.xml 
new/jline3-jline-3.30.15/terminal-ffm/pom.xml
--- old/jline3-jline-3.30.14/terminal-ffm/pom.xml       2026-06-26 
20:10:56.000000000 +0200
+++ new/jline3-jline-3.30.15/terminal-ffm/pom.xml       2026-06-30 
21:19:48.000000000 +0200
@@ -16,7 +16,7 @@
     <parent>
         <groupId>org.jline</groupId>
         <artifactId>jline-parent</artifactId>
-        <version>3.30.14</version>
+        <version>3.30.15</version>
     </parent>
 
     <artifactId>jline-terminal-ffm</artifactId>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jline3-jline-3.30.14/terminal-jansi/pom.xml 
new/jline3-jline-3.30.15/terminal-jansi/pom.xml
--- old/jline3-jline-3.30.14/terminal-jansi/pom.xml     2026-06-26 
20:10:56.000000000 +0200
+++ new/jline3-jline-3.30.15/terminal-jansi/pom.xml     2026-06-30 
21:19:48.000000000 +0200
@@ -16,7 +16,7 @@
     <parent>
         <groupId>org.jline</groupId>
         <artifactId>jline-parent</artifactId>
-        <version>3.30.14</version>
+        <version>3.30.15</version>
     </parent>
 
     <artifactId>jline-terminal-jansi</artifactId>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jline3-jline-3.30.14/terminal-jna/pom.xml 
new/jline3-jline-3.30.15/terminal-jna/pom.xml
--- old/jline3-jline-3.30.14/terminal-jna/pom.xml       2026-06-26 
20:10:56.000000000 +0200
+++ new/jline3-jline-3.30.15/terminal-jna/pom.xml       2026-06-30 
21:19:48.000000000 +0200
@@ -16,7 +16,7 @@
     <parent>
         <groupId>org.jline</groupId>
         <artifactId>jline-parent</artifactId>
-        <version>3.30.14</version>
+        <version>3.30.15</version>
     </parent>
 
     <artifactId>jline-terminal-jna</artifactId>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jline3-jline-3.30.14/terminal-jni/pom.xml 
new/jline3-jline-3.30.15/terminal-jni/pom.xml
--- old/jline3-jline-3.30.14/terminal-jni/pom.xml       2026-06-26 
20:10:56.000000000 +0200
+++ new/jline3-jline-3.30.15/terminal-jni/pom.xml       2026-06-30 
21:19:48.000000000 +0200
@@ -16,7 +16,7 @@
     <parent>
         <groupId>org.jline</groupId>
         <artifactId>jline-parent</artifactId>
-        <version>3.30.14</version>
+        <version>3.30.15</version>
     </parent>
 
     <artifactId>jline-terminal-jni</artifactId>

++++++ jline3-build.tar.xz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/common.xml new/common.xml
--- old/common.xml      2026-06-30 10:26:47.683438582 +0200
+++ new/common.xml      2026-07-01 15:23:56.001337071 +0200
@@ -3,7 +3,7 @@
 <project name="common" basedir=".">
 
   <property file="build.properties"/>
-  <property name="project.version" value="3.30.14"/>
+  <property name="project.version" value="3.30.15"/>
   <property name="project.groupId" value="org.jline"/>
   <property name="project.url" value="https://github.com/jline/jline3"/>
 

Reply via email to