Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
d47c27c
Windows actions
neykov May 30, 2021
ffb1663
Windows improvements
neykov May 31, 2021
5b76094
look into java install
neykov May 31, 2021
e8c7d6e
testing
neykov May 31, 2021
31dc4ef
Add workflow to reproduce issue #7 (WindowsAttachProvider could not b…
claude Apr 1, 2026
d7801e5
Trigger reproduce-issue-7 workflow on push to this branch
claude Apr 1, 2026
12cd7c4
Update workflow: assert fix works instead of unfixed behavior
claude Apr 1, 2026
262748f
Fix assertion and add reproduce-without-fix job
claude Apr 1, 2026
d18d08b
Rework library isolation: use JVM flags instead of PATH manipulation
claude Apr 1, 2026
23cc222
Use reflection to null ClassLoader.sys_paths for reliable library pat…
claude Apr 1, 2026
e34b248
Fix YAML syntax: replace PowerShell here-strings with base64-decoded …
claude Apr 1, 2026
58a842f
Reproduce issue #7 using real JRE/JDK environment split, no in-proces…
claude Apr 1, 2026
179de21
Fix workflow: copy JRE and remove attach.dll instead of using package…
claude Apr 1, 2026
44087c4
Remove attach.dll from JDK bin/ (on PATH) to prevent wrong DLL being …
claude Apr 1, 2026
a4d5be3
Accept both error manifestations for issue #7 reproduction
claude Apr 1, 2026
6aa7f5e
Reset \$LASTEXITCODE after java run to prevent step failure before as…
claude Apr 1, 2026
ee34e2c
Add -Djava.library.path=. to force System.loadLibrary("attach") failure
claude Apr 1, 2026
364f178
Replace workflow: reproduce issue #7 on stock Temurin JDK 8, no env m…
claude Apr 2, 2026
6b63167
Trigger workflow on push to branch
claude Apr 2, 2026
87a445c
Add check-jre job: inspect standalone Temurin JRE 8 for attach.dll
claude Apr 3, 2026
275cf81
standalnoe jre
neykov Apr 3, 2026
79ee010
standalnoe jre
neykov Apr 3, 2026
7c6b1cb
Merge ; commit '275cf81a6f705e776afe251280148c5559e5235a'
neykov Apr 3, 2026
c0ddd8b
path
neykov Apr 3, 2026
3143830
Comment out loadAttachLibrary() to reproduce issue #7
claude Apr 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions .github/workflows/reproduce-issue-7.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
name: Reproduce WindowsAttachProvider ServiceConfigurationError

on:
workflow_dispatch:
push:
branches:
- claude/reproduce-issue-7-Qj92G

jobs:
reproduce:
runs-on: windows-latest

steps:
- uses: actions/checkout@v4

- name: Set up Temurin JDK 8 (JAVA_HOME will point here)
uses: actions/setup-java@v4
with:
java-version: '8'
distribution: 'temurin'

- name: Build jar
run: mvn package -DskipTests -q

- name: Download Temurin JRE-only archive (simulates Oracle standalone JRE)
shell: pwsh
run: |
$url = "https://github.com/adoptium/temurin8-binaries/releases/download/jdk8u442-b06/OpenJDK8U-jre_x64_windows_hotspot_8u442b06.zip"
Invoke-WebRequest -Uri $url -OutFile jre8.zip
Expand-Archive jre8.zip -DestinationPath jre8
$jreHome = (Get-ChildItem jre8 -Directory)[0].FullName
echo "STANDALONE_JRE=$jreHome" >> $env:GITHUB_ENV

- name: Show preconditions
shell: pwsh
run: |
Write-Host "=== JDK (JAVA_HOME) ==="
Write-Host "Path : $env:JAVA_HOME"
Write-Host "tools.jar : $(Test-Path "$env:JAVA_HOME\lib\tools.jar")"
Write-Host "attach.dll : $(Test-Path "$env:JAVA_HOME\jre\bin\attach.dll")"
Write-Host ""
Write-Host "=== Standalone JRE (on PATH in real-world scenario) ==="
Write-Host "Path : $env:STANDALONE_JRE"
Write-Host "attach.dll : $(Test-Path "$env:STANDALONE_JRE\bin\attach.dll")"
Write-Host "tools.jar : $(Test-Path "$env:STANDALONE_JRE\lib\tools.jar")"
Write-Host ""
Write-Host "JVM library paths when launched from standalone JRE:"
& "$env:STANDALONE_JRE\bin\java.exe" -XshowSettings:property -version 2>&1 |
Select-String "library.path"

- name: Reproduce error
shell: pwsh
# Explicitly invoke the standalone JRE's java.exe (not the JDK's).
# JAVA_HOME still points to the JDK so tools.jar is found and
# the URLClassLoader path in AgentAttach is taken.
# sun.boot.library.path is derived from the standalone JRE's jvm.dll
# and points to a bin\ with no attach.dll -- causing UnsatisfiedLinkError
# inside WindowsAttachProvider's static initializer, which the SPI loader
# wraps into:
# ServiceConfigurationError: com.sun.tools.attach.spi.AttachProvider:
# Provider sun.tools.attach.WindowsAttachProvider could not be instantiated
run: |
# Remove all JDK bin directories from PATH so java.library.path
# also can't find any attach.dll as a fallback
$env:PATH = ($env:PATH -split ';' |
Where-Object { $_ -notmatch 'jdk|jre|java|hostedtoolcache' }) -join ';'

$java = "$env:STANDALONE_JRE\bin\java.exe"
$jar = (Get-ChildItem target\extract-tls-secrets-*.jar)[0].FullName

Write-Host "PATH: $env:PATH"
Write-Host "java.exe : $java (standalone JRE - no attach.dll)"
Write-Host "JAVA_HOME: $env:JAVA_HOME (JDK - has tools.jar)"
Write-Host "jar : $jar"
Write-Host ""
Write-Host "Running..."
& $java -jar $jar list
continue-on-error: true

50 changes: 40 additions & 10 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,45 @@ name: Integration Tests
on: [push]

jobs:
build:

runs-on: ubuntu-latest
# build-linux:
#
# runs-on: ubuntu-latest
#
# steps:
# - uses: actions/checkout@v2
# - name: Set up JDK 1.8
# uses: actions/setup-java@v2
# with:
# java-version: 1.8
# distribution: adopt
# - name: Build and run tests
# run: mvn -B verify
#
Comment on lines +6 to +19
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Linux CI job is commented out, so CI now only runs the Windows job (which uses mvn package rather than mvn verify). This removes the existing Ubuntu-based integration test coverage (Docker-based mvn verify per pom.xml). Consider keeping the Linux verify job enabled and adding Windows as an additional job/matrix entry.

Suggested change
# build-linux:
#
# runs-on: ubuntu-latest
#
# steps:
# - uses: actions/checkout@v2
# - name: Set up JDK 1.8
# uses: actions/setup-java@v2
# with:
# java-version: 1.8
# distribution: adopt
# - name: Build and run tests
# run: mvn -B verify
#
build-linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 1.8
uses: actions/setup-java@v2
with:
java-version: 1.8
distribution: adopt
- name: Build and run tests
run: mvn -B verify

Copilot uses AI. Check for mistakes.
build-windows:
runs-on: windows-latest

steps:
- uses: actions/checkout@v1
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Build and run tests
run: mvn -B verify
- uses: actions/checkout@v2
- name: Set up JDK 1.8
uses: actions/setup-java@v2
with:
java-version: 8
distribution: adopt
- name: Set up JRE 1.8
uses: actions/setup-java@v2
with:
java-version: 8
distribution: adopt
java-package: jre
- name: Build and run tests
run: |
$env:JAVA_HOME = "C:\hostedtoolcache\windows\Java_Adopt_jdk\8.0.292-10\x64"
mvn -B package
$env:JAVA_HOME
Comment on lines +37 to +40
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This hard-codes a specific hostedtoolcache JDK path (including a particular patch version). Runner images change over time, so this is likely to break unexpectedly. Prefer capturing the JDK JAVA_HOME from the first setup-java step (before installing the JRE) and reusing that value here.

Copilot uses AI. Check for mistakes.
$env:PATH
dir $env:JAVA_HOME
dir $env:JAVA_HOME\bin
# dir $env:JAVA_HOME\jre\bin
which java
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

which is not a standard command in Windows PowerShell. This step may fail on windows-latest; use where.exe java or Get-Command java instead.

Suggested change
which java
where.exe java

Copilot uses AI. Check for mistakes.
java -version
java -jar target\extract-tls-secrets-4.1.0-SNAPSHOT.jar list
44 changes: 29 additions & 15 deletions src/main/java/name/neykov/secrets/AgentAttach.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package name.neykov.secrets;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
Expand Down Expand Up @@ -40,7 +41,7 @@ public static void main(String[] args) throws Exception {

try {
attach(jarUrl, jarFile, pid, logFile);
} catch (MessageException e) {
} catch (FailureMessageException e) {
for (String line : e.msg) {
System.err.println(line);
}
Expand All @@ -50,23 +51,45 @@ public static void main(String[] args) throws Exception {

public static void attach(URL jarUrl, File jarFile, String pid, String logFile) throws Exception {
if (isAttachApiAvailable()) {
// Either Java 9 or tools.jar already on classpath
// Either Java 9+ or tools.jar already on classpath
AttachHelper.handle(jarFile.getAbsolutePath(), pid, logFile);
} else {
File toolsFile = getToolsFile();
System.out.println("tools.jar: " + toolsFile.getAbsolutePath());
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Printing the tools.jar path to stdout changes CLI output (including list), which can break consumers expecting only the process list or attach status. Consider removing this, printing to stderr, or only emitting it under an explicit verbose/debug option.

Suggested change
System.out.println("tools.jar: " + toolsFile.getAbsolutePath());
System.err.println("tools.jar: " + toolsFile.getAbsolutePath());

Copilot uses AI. Check for mistakes.
URL toolsUrl = toolsFile.toURI().toURL();
URL[] cp = new URL[] {jarUrl, toolsUrl};
URLClassLoader classLoader = new URLClassLoader(cp, null);
Thread.currentThread().setContextClassLoader(classLoader);
Class<?> helper = classLoader.loadClass("name.neykov.secrets.AttachHelper");

Method handleMethod = helper.getMethod("handle", String.class, String.class, String.class);
handleMethod.invoke(null, jarFile.getAbsolutePath(), pid, logFile);
try {
handleMethod.invoke(null, jarFile.getAbsolutePath(), pid, logFile);
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();
// The cause class is loaded by "classLoader" and therefore a separate instance failing
// the equality test. It will not get caught by parent exception blocks.
if (cause.getClass().getName().equals(FailureMessageException.class.getName())) {
Field msgField = cause.getClass().getDeclaredField("msg");
msgField.setAccessible(true);
String[] msg = (String[])msgField.get(cause);
throw new FailureMessageException(msg);
} else {
throw e;
}
}
}
}
static File getJavaHome() throws FailureMessageException {
String javaHomeEnv = System.getenv("JAVA_HOME");
if (javaHomeEnv != null) {
return new File(javaHomeEnv);
}

throw new FailureMessageException("No JAVA_HOME environment variable found. Must point to a local JDK installation.");
}

private static File getToolsFile() throws MessageException {
private static File getToolsFile() throws FailureMessageException {
File javaHome = getJavaHome();

// javaHome is a JDK
Expand Down Expand Up @@ -98,27 +121,18 @@ private static File getToolsFile() throws MessageException {
// * Java 9 and higher: X.0.0 (e.x. 9.0.0, 11.0.0)
if (System.getProperty("java.version").startsWith("1.")) {
// JAVA_HOME required
throw new MessageException(
throw new FailureMessageException(
"Invalid JAVA_HOME environment variable '" + javaHome.getAbsolutePath() + "'.",
"Must point to a local JDK installation containing a 'lib/tools.jar' file."
);
} else {
// No need for JAVA_HOME. Not executed from a JDK java executable.
throw new MessageException(
throw new FailureMessageException(
"No access to JDK classes. Make sure to use the java executable from a JDK install."
);
}
}

private static File getJavaHome() throws MessageException {
String javaHomeEnv = System.getenv("JAVA_HOME");
if (javaHomeEnv != null) {
return new File(javaHomeEnv);
}

throw new MessageException("No JAVA_HOME environment variable found. Must point to a local JDK installation.");
}

private static boolean isAttachApiAvailable() {
try {
AgentAttach.class.getClassLoader().loadClass("com.sun.tools.attach.VirtualMachine");
Expand Down
88 changes: 86 additions & 2 deletions src/main/java/name/neykov/secrets/AttachHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;

import com.sun.tools.attach.AgentInitializationException;
import com.sun.tools.attach.AgentLoadException;
Expand All @@ -15,7 +16,11 @@
//Byte Buddy (https://github.com/raphw/byte-buddy) abstracts
//the API, including a fallback implementing the attach api.
public class AttachHelper {
public static void handle(String jarPath, String pid, String logFile) throws MessageException {
public static void handle(String jarPath, String pid, String logFile) throws FailureMessageException {
// if (isWindows()) {
// loadAttachLibrary();
// }

if (pid.equals("list")) {
System.out.print(AttachHelper.list());
} else {
Expand All @@ -24,11 +29,90 @@ public static void handle(String jarPath, String pid, String logFile) throws Mes
System.out.println("Successfully attached to process ID " + pid + ".");
} catch (IllegalStateException e) {
String msg = e.getMessage() != null ? e.getMessage() : "Failed attaching to java process " + pid;
throw new MessageException(msg);
throw new FailureMessageException(msg);
}
}

}

private static boolean isWindows() {
return System.getProperty("os.name").startsWith("Windows");
}

private static void loadAttachLibrary() throws FailureMessageException {
try {
System.loadLibrary("attach");
// All good - system is set up properly. Nothing to do.
} catch (UnsatisfiedLinkError e) {
// "attach.dll" not on the default search path, let's try some well known locations.
// Could happen if using the JRE with JAVA_HOME pointing to a JDK install.
if (!tryLoadLibrary("jre/bin/attach.dll")) {
throw new FailureMessageException(
"Failed loading attach provider. Make sure you are running with a JDK java executable. " +
"Alternatively locate 'attach.dll' on your system, typically found in " +
"'<jdk home>/jre/bin' folder for Oracle JDK installs, and pass the path at startup as: ",
" java -Djava.library.path=\"<jdk home>/jre/bin\" -jar extract-tls-secrets.jar"
);
}
}
}

private static File getJavaHome() throws FailureMessageException {
// Duplicated from AttachHelper, but can't be shared due to ClassLoader boundary
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says this method is “Duplicated from AttachHelper”, but it’s inside AttachHelper itself. This looks like a copy/paste mistake (likely meant “Duplicated from AgentAttach”) and makes the rationale unclear; please correct or remove the comment.

Suggested change
// Duplicated from AttachHelper, but can't be shared due to ClassLoader boundary
// Duplicated from AgentAttach, but can't be shared due to ClassLoader boundary

Copilot uses AI. Check for mistakes.
String javaHomeEnv = System.getenv("JAVA_HOME");
if (javaHomeEnv != null) {
return new File(javaHomeEnv);
}

throw new FailureMessageException("No JAVA_HOME environment variable found. Must point to a local JDK installation.");
}

private static boolean tryLoadLibrary(String attachPath) throws FailureMessageException {
File javaHome = getJavaHome();
File attachAbsolutePath = new File(javaHome, attachPath);
if (attachAbsolutePath.exists()) {
// Check the file path is a valid library
try {
System.load(attachAbsolutePath.getAbsolutePath());
} catch (UnsatisfiedLinkError ex) {
return false;
}

// Extend the path. On good installs the path is supposed to come from "sun.boot.library.path".
String initialPath = System.getProperty("java.library.path");
String extendedPath;
if (initialPath != null && initialPath.length() > 0) {
extendedPath = initialPath + File.pathSeparator + attachAbsolutePath.getParent();
} else {
extendedPath = attachAbsolutePath.getParent();
}
System.setProperty("java.library.path", extendedPath);

// Force reload of the java.library.path property
Field fieldSysPath = null;
try {
fieldSysPath = ClassLoader.class.getDeclaredField("sys_paths");
} catch (NoSuchFieldException ex) {
return false;
}
fieldSysPath.setAccessible(true);
try {
fieldSysPath.set(null, null);
} catch (IllegalAccessException ex) {
return false;
}

// Check patching was successful
try {
System.loadLibrary("attach");
System.out.println("Loaded attach " + attachAbsolutePath.getAbsolutePath());
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This writes a diagnostic message to stdout while loading attach.dll. For the list command, stdout is the machine-readable process list; extra lines like this can break scripts. Consider removing this print or sending it to stderr / gating behind a debug flag.

Suggested change
System.out.println("Loaded attach " + attachAbsolutePath.getAbsolutePath());
System.err.println("Loaded attach " + attachAbsolutePath.getAbsolutePath());

Copilot uses AI. Check for mistakes.
return true;
} catch (UnsatisfiedLinkError ex) {
}
}
return false;
}

private static void attach(String pid, String jarPath, String options) {
try {
VirtualMachine vm = VirtualMachine.attach(pid);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package name.neykov.secrets;

class FailureMessageException extends Exception {
String[] msg;

protected FailureMessageException(String... msg) {
this.msg = msg;
}
}
9 changes: 0 additions & 9 deletions src/main/java/name/neykov/secrets/MessageException.java

This file was deleted.

Loading