Copilot commented on code in PR #4471:
URL: https://github.com/apache/solr/pull/4471#discussion_r3316213352


##########
solr/server/etc/agent-security-extra.policy:
##########
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Solr Security Agent — operator extension policy
+//
+// This file is loaded in addition to the bundled default policy 
(agent-security.policy).
+// Add entries here to permit additional file paths or network endpoints 
required by your
+// specific deployment, plugins, or custom components.
+//
+// This file's location can be overridden via:
+//   - Environment variable: 
SOLR_SECURITY_AGENT_EXTRA_POLICY=/path/to/custom.policy
+//   - System property:      
-Dsolr.security.agent.extra.policy=/path/to/custom.policy
+//
+// An absent file is silently skipped — it is not a startup error.
+//
+// Syntax: JDK-style .policy files (same as agent-security.policy).
+// Supported variables: ${solr.home}, ${solr.data.dir}, ${solr.log.dir}, 
${solr.install.dir},
+//                      ${java.io.tmpdir}, ${java.home}, ${solr.port}, 
${solr.zk.port}

Review Comment:
   The list of "Supported variables" here does not match what 
`PolicyPropertyExpander` actually resolves; the same incorrect names appear 
(`${solr.home}`, `${solr.data.dir}`, `${solr.log.dir}`, `${solr.port}`). The 
default policy file at `solr/server/etc/agent-security.policy` uses 
`${solr.solr.home}`, `${solr.data.home}`, `${solr.logs.dir}`, 
`${solr.port.listen}`. Please reconcile this comment with the actual supported 
variable names so operators copying these examples don't hit "Unresolved policy 
variable" errors.



##########
solr/agent-sm/src/java/org/apache/solr/security/agent/FileInterceptor.java:
##########
@@ -0,0 +1,277 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.security.agent;
+
+import java.lang.reflect.Method;
+import java.nio.file.LinkOption;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.nio.file.spi.FileSystemProvider;
+import java.util.Collection;
+import java.util.Locale;
+import java.util.Set;
+import net.bytebuddy.asm.Advice;
+
+/**
+ * ByteBuddy {@link Advice} interceptor for file-system operations.
+ *
+ * <p>This file was derived from the OpenSearch project and modified. See 
{@code NOTICE.txt} for
+ * attribution.
+ */
+public class FileInterceptor {
+
+  /** FileInterceptor */
+  public FileInterceptor() {}
+
+  /**
+   * Intercepts file operations.
+   *
+   * @param args arguments
+   * @param method method
+   * @throws Exception exceptions
+   */
+  @Advice.OnMethodEnter
+  public static void intercept(@Advice.AllArguments Object[] args, 
@Advice.Origin Method method)
+      throws Exception {
+    if (!AgentPolicy.isInitialized()) return;
+    final AgentPolicy policy = AgentPolicy.getInstance();
+
+    FileSystemProvider provider = null;
+    String filePath = null;
+    if (args.length > 0 && args[0] instanceof String pathStr) {
+      filePath = Path.of(pathStr).toAbsolutePath().toString();
+    } else if (args.length > 0 && args[0] instanceof Path path) {
+      filePath = path.toAbsolutePath().toString();
+      provider = path.getFileSystem().provider();
+    }
+
+    if (filePath == null) {
+      return; // No valid file path found
+    }
+
+    if (provider != null && 
policy.trustedFileSystems().contains(provider.getScheme())) {
+      return;
+    }
+
+    final StackWalker walker = 
StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
+    final String caller = walker.getCallerClass().getName();
+
+    final String name = method.getName();
+    boolean isMutating = name.equals("move") || name.equals("write") || 
name.startsWith("create");
+    final boolean isDelete = isMutating == false ? name.startsWith("delete") : 
false;
+
+    // This is Windows implementation of UNIX Domain Sockets (close)
+    boolean isUnixSocketCaller = false;
+    if (isDelete == true) {
+      final Collection<Class<?>> chain = 
walker.walk(StackCallerClassChainExtractor.INSTANCE);
+      for (final Class<?> cls : chain) {
+        if 
(cls.getName().equalsIgnoreCase("sun.nio.ch.PipeImpl$Initializer$LoopbackConnector"))
 {
+          isUnixSocketCaller = true;
+          break;
+        }
+      }
+    }
+
+    if (isDelete == true && isUnixSocketCaller == true) {
+      // Unix domain socket cleanup — local IPC, always allow
+      return;
+    } else {
+      String targetFilePath = null;
+      if (isMutating == false && isDelete == false) {
+        if (name.equals("newByteChannel") == true || name.equals("open") == 
true) {
+          if (args.length > 1) {
+            if (args instanceof OpenOption[] opts) {

Review Comment:
   Bug: this `instanceof` check uses `args` (the full `Object[]` argument array 
from `@Advice.AllArguments`) instead of `args[1]`. An `Object[]` can never be 
an `OpenOption[]`, so this branch is effectively unreachable. The subsequent 
branches correctly test `args[1]`. This means callers passing a varargs 
`OpenOption[]` directly will fall through to the `Object[]` branch (which is 
fine since arrays are `Object[]`), but the dedicated typed branch never fires. 
The check should be `args[1] instanceof OpenOption[] opts`.
   



##########
changelog/unreleased/SOLR-15868-java-agent.yml:
##########
@@ -0,0 +1,9 @@
+title: >
+  Introduce a Java agent (solr-agent-sm) that enforces file access, outbound 
network,
+  System.exit(), and process-spawn controls via ByteBuddy instrumentation.
+type: added
+authors:
+  - name: Jan Høydahl
+links:
+  - name: SOLR-15868
+    url: https://issues.apache.org/jira/browse/SOLR-15868

Review Comment:
   JIRA ticket mismatch: this changelog file uses SOLR-15868 in its name and 
link, but the PR title, description, and `@Deprecated` documentation throughout 
the rest of the change reference SOLR-17767. One of the two is incorrect — 
please align the changelog filename and link with the actual JIRA issue.



##########
solr/solr-ref-guide/modules/deployment-guide/pages/security-agent.adoc:
##########
@@ -0,0 +1,166 @@
+= Security Agent
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+Solr includes a Java agent (`solr-agent-sm`) that aims to fill the gaps left 
by the removal of Java Security Manager (JSM) in JDK 24.
+The agent intercepts file access, network connections, `System.exit()`, and 
process spawning at the bytecode level, enforcing a configurable policy.
+
+== Protections Active by Default
+
+When the agent JAR is present, the startup scripts automatically activate it.
+The following protections apply to all code running inside the Solr JVM:
+
+* **File access** — reads and writes are restricted to permitted directories 
(Solr home, data dir, log dir, temp dir).
+* **Outbound network** — connections are restricted to loopback addresses and 
intra-cluster ports.
+* **`System.exit()` / `Runtime.halt()`** — only approved caller classes may 
shut down the JVM.
+* **Process spawning** — `ProcessBuilder.start()` and `Runtime.exec()` are 
blocked by default.
+
+== Enforcement Modes
+
+The agent operates in two modes, controlled by the `SOLR_SECURITY_AGENT_MODE` 
environment variable:
+
+`warn` (default)::
+Violations are logged at `WARN` level and counted in `/admin/metrics`, but the 
operation is allowed to continue.
+This is the safe default for existing deployments — operators can observe 
violations without risk.
+
+`enforce`::
+Violations throw a `SecurityException`, blocking the operation.
+Enable this mode once you have verified that your deployment produces no 
unexpected violations in `warn` mode.
+
+Set the mode in `solr.in.sh` or `solr.in.cmd`:
+
+[source,bash]
+----
+SOLR_SECURITY_AGENT_MODE=enforce
+----
+
+== Policy File Format
+
+Solr uses JDK-style `.policy` files with Solr-specific variable substitution.
+Two files are loaded:
+
+`server/etc/agent-security.policy` (default)::
+The bundled production policy.
+Do not edit this file — it will be overwritten on upgrade.
+
+`server/etc/agent-security-extra.policy` (operator extension)::
+Add your custom rules here.
+This file is silently skipped if absent.
+Its location can be overridden via `SOLR_SECURITY_AGENT_EXTRA_POLICY`.
+
+=== Variable Substitution
+
+Any java property will be expanded in policy entries, here are the most 
common. Environment variables are expanded if they adhere to the mapping 
convention `SOLR_FOO --> solr.foo`:

Review Comment:
   The env-var mapping convention is described backwards. 
`PolicyPropertyExpander.getPropertyOrEnv` derives the env var from the system 
property by upper-casing and replacing `.` with `_` (e.g. `solr.foo` → 
`SOLR_FOO`), not the other way around. The current phrasing ("if they adhere to 
the mapping convention `SOLR_FOO --> solr.foo`") is ambiguous about direction 
and inverts how a reader would expect to use it. Consider rephrasing as "system 
property `solr.foo.bar` is also looked up as environment variable 
`SOLR_FOO_BAR`".



##########
solr/core/src/java/org/apache/solr/security/AgentViolationBridge.java:
##########
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.security;
+
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Field;
+import java.util.function.Consumer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Wires the Solr security agent's violation reporter to SLF4J once Log4j2 is 
available. Uses
+ * reflection because {@code solr:agent-sm} and {@code solr:core} have no 
compile-time dependency on
+ * each other. Call {@link #wire()} from {@code CoreContainer} after Log4j2 is 
initialised; from
+ * that point violations appear in {@code solr.log} rather than {@code 
System.err}. Safe to call
+ * when the agent JAR is absent — {@link ClassNotFoundException} is silently 
ignored.
+ */
+public final class AgentViolationBridge {
+
+  private static final Logger log = 
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+  private AgentViolationBridge() {}
+
+  /**
+   * Wires the security agent violation reporter to SLF4J. No-op if the agent 
JAR is not present.
+   */
+  public static void wire() {
+    try {
+      // SecurityViolationLogger is in the bootstrap classloader via 
Boot-Class-Path.
+      Class<?> cls = 
Class.forName("org.apache.solr.security.agent.SecurityViolationLogger");
+      Field f = cls.getField("reporter");
+      Consumer<String> bridge = msg -> log.warn("SECURITY VIOLATION {}", msg);
+      f.set(null, bridge);

Review Comment:
   `AgentViolationBridge.wire()` uses `Field.set(null, bridge)` to assign to a 
`public volatile` static field via reflection. With the JPMS module system this 
works because both classes are in `org.apache.solr.security.agent` / 
`org.apache.solr.security`, but since the agent JAR is loaded into the 
bootstrap classloader (per `Boot-Class-Path` manifest), some JDKs enforce 
additional access checks. Consider adding `f.setAccessible(true)` before 
`f.set(...)` as a defensive measure to avoid `IllegalAccessException` in 
restricted environments. Alternatively, expose a 
`setReporter(Consumer<String>)` static method on `SecurityViolationLogger` and 
invoke that via reflection, which would also avoid relying on a public mutable 
field.



##########
solr/solr-ref-guide/modules/deployment-guide/pages/security-agent.adoc:
##########
@@ -0,0 +1,166 @@
+= Security Agent
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+Solr includes a Java agent (`solr-agent-sm`) that aims to fill the gaps left 
by the removal of Java Security Manager (JSM) in JDK 24.
+The agent intercepts file access, network connections, `System.exit()`, and 
process spawning at the bytecode level, enforcing a configurable policy.
+
+== Protections Active by Default
+
+When the agent JAR is present, the startup scripts automatically activate it.
+The following protections apply to all code running inside the Solr JVM:
+
+* **File access** — reads and writes are restricted to permitted directories 
(Solr home, data dir, log dir, temp dir).
+* **Outbound network** — connections are restricted to loopback addresses and 
intra-cluster ports.
+* **`System.exit()` / `Runtime.halt()`** — only approved caller classes may 
shut down the JVM.
+* **Process spawning** — `ProcessBuilder.start()` and `Runtime.exec()` are 
blocked by default.
+
+== Enforcement Modes
+
+The agent operates in two modes, controlled by the `SOLR_SECURITY_AGENT_MODE` 
environment variable:
+
+`warn` (default)::
+Violations are logged at `WARN` level and counted in `/admin/metrics`, but the 
operation is allowed to continue.
+This is the safe default for existing deployments — operators can observe 
violations without risk.
+
+`enforce`::
+Violations throw a `SecurityException`, blocking the operation.
+Enable this mode once you have verified that your deployment produces no 
unexpected violations in `warn` mode.
+
+Set the mode in `solr.in.sh` or `solr.in.cmd`:
+
+[source,bash]
+----
+SOLR_SECURITY_AGENT_MODE=enforce
+----
+
+== Policy File Format
+
+Solr uses JDK-style `.policy` files with Solr-specific variable substitution.
+Two files are loaded:
+
+`server/etc/agent-security.policy` (default)::
+The bundled production policy.
+Do not edit this file — it will be overwritten on upgrade.
+
+`server/etc/agent-security-extra.policy` (operator extension)::
+Add your custom rules here.
+This file is silently skipped if absent.
+Its location can be overridden via `SOLR_SECURITY_AGENT_EXTRA_POLICY`.
+
+=== Variable Substitution
+
+Any java property will be expanded in policy entries, here are the most 
common. Environment variables are expanded if they adhere to the mapping 
convention `SOLR_FOO --> solr.foo`:
+
+[cols="1,2",options="header"]
+|===
+|Variable|Resolved Value
+|`${solr.home}`|Solr home directory
+|`${solr.data.dir}`|Solr data directory
+|`${solr.log.dir}`|Solr log directory
+|`${solr.install.dir}`|Solr installation root (parent of `server/`)
+|`${java.io.tmpdir}`|JVM temporary directory
+|`${java.home}`|JDK installation directory
+|`${solr.port}`|Solr HTTP port
+|`${solr.zk.port}`|ZooKeeper port (defaults to `solr.port + 1000` for embedded 
ZK)
+|===
+
+=== Default Intra-Cluster Network Policy
+
+The bundled policy permits connections on `*:${solr.port}` and 
`*:${solr.zk.port}` from any host, so all cluster nodes can communicate without 
any operator action.
+If your ZooKeeper runs on a non-standard port, set `-Dsolr.zk.port=<port>` at 
startup or configure it explicitly in the policy.
+
+=== Adding Custom Policy Entries
+
+To permit additional paths or endpoints, add entries to 
`agent-security-extra.policy`:
+
+[source,text]
+----
+grant {
+  // permit a plugin that reads from an NFS mount
+  permission java.io.FilePermission "/mnt/nfs-data/-", "read";
+};
+
+grant {
+  // permit outbound connection to an internal analytics service
+  permission java.net.SocketPermission "analytics.internal:443", 
"connect,resolve";
+};
+----
+
+To override the extra-policy file location (useful for read-only installations 
or containers):
+
+[source,bash]
+----
+SOLR_SECURITY_AGENT_EXTRA_POLICY=/etc/solr/custom-security.policy
+----
+
+== Diagnosing Violations
+
+Violations appear in the Solr log in this format:
+
+[source,text]
+----
+SECURITY VIOLATION [FILE] target=/etc/passwd caller=com.example.Plugin 
mode=warn source=DEFAULT
+----
+
+Fields:
+
+* `[TYPE]` — `FILE`, `NETWORK`, `EXIT`, or `EXEC`
+* `target` — the resource that was accessed or attempted
+* `caller` — the class that triggered the access
+* `mode` — `warn` or `enforce`
+* `source` — `DEFAULT` (bundled policy) or `OPERATOR` (extra policy)
+
+Violation counts are also available in `/admin/metrics` under:
+
+* `solr.security.agent.violations.file`
+* `solr.security.agent.violations.network`
+* `solr.security.agent.violations.exit`
+* `solr.security.agent.violations.exec`
+
+== Disabling the Agent
+
+To temporarily disable the agent for troubleshooting, set in `solr.in.sh` or 
`solr.in.cmd`:
+
+[source,bash]
+----
+SOLR_SECURITY_AGENT_SKIP=true
+----
+
+[WARNING]
+====
+Disabling the agent removes all runtime security controls.
+Use this only for temporary troubleshooting of if you intend to provide 
similar protection on the operating system level.

Review Comment:
   Typo: "of if you intend" should be "or if you intend".



##########
solr/agent-sm/src/java/org/apache/solr/security/agent/SolrAgentEntryPoint.java:
##########
@@ -0,0 +1,221 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.security.agent;
+
+import java.lang.instrument.Instrumentation;
+import java.net.Socket;
+import java.nio.channels.FileChannel;
+import java.nio.channels.SocketChannel;
+import java.nio.file.Path;
+import java.nio.file.spi.FileSystemProvider;
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.agent.builder.AgentBuilder;
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.matcher.ElementMatchers;
+
+/**
+ * Java agent entry point for the Solr security agent.
+ *
+ * <p>The JVM invokes {@link #premain(String, Instrumentation)} before the 
application main method
+ * when the agent JAR is listed on the command line as {@code 
-javaagent:solr-agent-sm-*.jar}. The
+ * {@link #agentmain(String, Instrumentation)} method supports attach-based 
loading (not used by
+ * Solr's startup scripts but retained for tooling compatibility).
+ *
+ * <h2>Startup sequence</h2>
+ *
+ * <ol>
+ *   <li>Locate and parse {@code agent-security.policy} (and the optional 
{@code
+ *       agent-security-extra.policy}) via {@link PolicyLoader}.
+ *   <li>Initialize the {@link AgentPolicy} singleton.
+ *   <li>Register all four ByteBuddy interceptors with the JVM instrumentation 
API.
+ *   <li>If policy loading fails and enforcement mode is {@code ENFORCE}, halt 
the JVM; in {@code
+ *       WARN} mode, log the error and continue without protection.
+ * </ol>
+ *
+ * <h2>Bootstrap classloader</h2>
+ *
+ * The agent JAR is placed on {@code Boot-Class-Path} so all interceptor and 
policy classes are
+ * visible to the bootstrap classloader. SLF4J is intentionally absent from 
the fat JAR to avoid
+ * poisoning the SLF4J binding before Log4j2 is loaded.
+ */
+public final class SolrAgentEntryPoint {
+
+  private SolrAgentEntryPoint() {}
+
+  /**
+   * Called by the JVM before the application main class is loaded, when the 
agent JAR is specified
+   * via {@code -javaagent:}.
+   */
+  public static void premain(String agentArgs, Instrumentation inst) {
+    bootAgent(inst);
+  }
+
+  /** Called by the JVM when the agent is attached dynamically; delegates to 
{@link #premain}. */
+  public static void agentmain(String agentArgs, Instrumentation inst) {
+    premain(agentArgs, inst);
+  }
+
+  // 
---------------------------------------------------------------------------
+  // Internal bootstrap logic
+  // 
---------------------------------------------------------------------------
+
+  @SuppressForbidden(
+      reason =
+          "System.err is the only safe output during premain; System.exit(1) 
is required to halt "
+              + "the JVM on fatal policy-load failure in enforce mode.")
+  private static void bootAgent(Instrumentation inst) {
+    // Locate the default policy file next to the agent JAR.
+    Path defaultPolicyPath = resolveDefaultPolicyPath();
+
+    AgentPolicy policy = null;
+    try {
+      PolicyLoader loader = new PolicyLoader();
+      policy = loader.load(defaultPolicyPath);
+    } catch (IllegalStateException e) {
+      String modeStr = 
PolicyPropertyExpander.getPropertyOrEnv("solr.security.agent.mode");
+      if (modeStr == null) modeStr = "warn";
+      if ("enforce".equalsIgnoreCase(modeStr.trim())) {
+        System.err.println(
+            "[Solr SecurityAgent] FATAL: Cannot load security policy in 
enforce mode. "
+                + "Solr will not start. Error: "
+                + e.getMessage());
+        System.exit(1);
+      } else {
+        System.err.println(
+            "[Solr SecurityAgent] WARNING: Cannot load security policy ("
+                + e.getMessage()
+                + "). Security controls are inactive.");
+        return;
+      }
+    }
+
+    AgentPolicy.initialize(policy);
+
+    try {
+      installInterceptors(inst);
+    } catch (Exception e) {
+      System.err.println("[Solr SecurityAgent] Failed to install interceptors: 
" + e);
+    }
+
+    System.err.println(
+        "[Solr SecurityAgent] Security agent active — mode="
+            + policy.enforcementMode()
+            + ", permitted paths="
+            + policy.permittedPaths().size()
+            + ", permitted endpoints="
+            + policy.permittedEndpoints().size());
+  }
+
+  private static final String[] FILE_INTERCEPTED_METHODS = {
+    "write",
+    "createFile",
+    "createDirectories",
+    "createLink",
+    "copy",
+    "move",
+    "newByteChannel",
+    "delete",
+    "deleteIfExists",
+    "read",
+    "open"
+  };
+
+  private static void installInterceptors(Instrumentation inst) {
+    // AgentBuilder configuration for JDK class instrumentation:
+    //   - Implementation.Context.Disabled: required for REDEFINE so ByteBuddy 
does not try to
+    //     add auxiliary types (forbidden when redefining already-loaded JDK 
classes).
+    //   - InitializationStrategy.NoOp: skip static initializer injection 
(same reason).
+    //   - RedefinitionStrategy.REDEFINITION: redefine already-loaded 
bootstrap classes (Files,
+    // etc.)
+    //     in-place rather than scheduling a retransformation pass.
+    final ByteBuddy byteBuddy =
+        new ByteBuddy().with(Implementation.Context.Disabled.Factory.INSTANCE);
+
+    new AgentBuilder.Default(byteBuddy)
+        .with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE)
+        .with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
+        .with(AgentBuilder.TypeStrategy.Default.REDEFINE)
+        // Override the default ignore filter so we can instrument 
bootstrap-loaded JDK classes
+        // in named modules (java.lang, java.nio, etc.). Without this, 
ByteBuddy 1.18.x silently
+        // skips those classes due to module-visibility checks in its default 
ignore rules.
+        // Also exclude ByteBuddy's own classes to prevent circular 
instrumentation at startup.
+        
.ignore(ElementMatchers.isSynthetic().or(ElementMatchers.nameStartsWith("net.bytebuddy.")))
+        // Intercept FileSystemProvider subclasses, java.nio.file.Files, and 
FileChannel subtypes.
+        .type(
+            ElementMatchers.isSubTypeOf(FileSystemProvider.class)
+                .or(ElementMatchers.named("java.nio.file.Files"))
+                .or(ElementMatchers.isSubTypeOf(FileChannel.class)))
+        .transform(
+            (builder, type, classLoader, module, domain) ->
+                builder.visit(
+                    Advice.to(FileInterceptor.class)
+                        .on(
+                            
ElementMatchers.namedOneOf(FILE_INTERCEPTED_METHODS)
+                                
.and(ElementMatchers.not(ElementMatchers.isAbstract())))))
+        // Intercept SocketChannel / Socket outbound connections → 
SocketChannelInterceptor
+        .type(
+            ElementMatchers.isSubTypeOf(SocketChannel.class)
+                .or(ElementMatchers.isSubTypeOf(Socket.class)))
+        .transform(
+            (builder, type, classLoader, module, domain) ->
+                builder.visit(
+                    Advice.to(SocketChannelInterceptor.class)
+                        .on(
+                            ElementMatchers.named("connect")
+                                
.and(ElementMatchers.not(ElementMatchers.isAbstract())))))
+        // Intercept System.exit(int) → SystemExitInterceptor
+        .type(ElementMatchers.is(System.class))
+        .transform(
+            (builder, type, classLoader, module, domain) ->
+                builder.visit(
+                    
Advice.to(SystemExitInterceptor.class).on(ElementMatchers.named("exit"))))
+        // Intercept Runtime.halt(int) → RuntimeHaltInterceptor
+        .type(ElementMatchers.is(Runtime.class))
+        .transform(
+            (builder, type, classLoader, module, domain) ->
+                builder.visit(
+                    
Advice.to(RuntimeHaltInterceptor.class).on(ElementMatchers.named("halt"))))
+        // Intercept ProcessBuilder.start() → ProcessExecInterceptor
+        .type(ElementMatchers.is(ProcessBuilder.class))
+        .transform(
+            (builder, type, classLoader, module, domain) ->
+                builder.visit(
+                    
Advice.to(ProcessExecInterceptor.class).on(ElementMatchers.named("start"))))
+        // Intercept Runtime.exec(String[]) → ProcessExecInterceptor
+        .type(ElementMatchers.is(Runtime.class))
+        .transform(
+            (builder, type, classLoader, module, domain) ->
+                builder.visit(
+                    
Advice.to(ProcessExecInterceptor.class).on(ElementMatchers.named("exec"))))

Review Comment:
   `AgentBuilder` registers two separate 
`.type(ElementMatchers.is(Runtime.class))` chains — one for `halt` and one for 
`exec`. With ByteBuddy's fluent builder, the second `.type(...)` starts a fresh 
top-level transformation rather than adding to the previous one, but more 
importantly applying two redefinitions to the same JDK class 
(`java.lang.Runtime`) via separate transformation chains can yield surprising 
interaction (only one set of advice applied, or duplicate work depending on 
REDEFINITION semantics). Consider combining the `halt` and `exec` matchers into 
a single `.type(ElementMatchers.is(Runtime.class))` clause whose transform 
installs both advices, or verify with an integration test that both 
interceptors fire when both methods are called in the same JVM.
   



##########
solr/solr-ref-guide/modules/deployment-guide/pages/security-agent.adoc:
##########
@@ -0,0 +1,166 @@
+= Security Agent
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+Solr includes a Java agent (`solr-agent-sm`) that aims to fill the gaps left 
by the removal of Java Security Manager (JSM) in JDK 24.
+The agent intercepts file access, network connections, `System.exit()`, and 
process spawning at the bytecode level, enforcing a configurable policy.
+
+== Protections Active by Default
+
+When the agent JAR is present, the startup scripts automatically activate it.
+The following protections apply to all code running inside the Solr JVM:
+
+* **File access** — reads and writes are restricted to permitted directories 
(Solr home, data dir, log dir, temp dir).
+* **Outbound network** — connections are restricted to loopback addresses and 
intra-cluster ports.
+* **`System.exit()` / `Runtime.halt()`** — only approved caller classes may 
shut down the JVM.
+* **Process spawning** — `ProcessBuilder.start()` and `Runtime.exec()` are 
blocked by default.
+
+== Enforcement Modes
+
+The agent operates in two modes, controlled by the `SOLR_SECURITY_AGENT_MODE` 
environment variable:
+
+`warn` (default)::
+Violations are logged at `WARN` level and counted in `/admin/metrics`, but the 
operation is allowed to continue.
+This is the safe default for existing deployments — operators can observe 
violations without risk.
+
+`enforce`::
+Violations throw a `SecurityException`, blocking the operation.
+Enable this mode once you have verified that your deployment produces no 
unexpected violations in `warn` mode.
+
+Set the mode in `solr.in.sh` or `solr.in.cmd`:
+
+[source,bash]
+----
+SOLR_SECURITY_AGENT_MODE=enforce
+----
+
+== Policy File Format
+
+Solr uses JDK-style `.policy` files with Solr-specific variable substitution.
+Two files are loaded:
+
+`server/etc/agent-security.policy` (default)::
+The bundled production policy.
+Do not edit this file — it will be overwritten on upgrade.
+
+`server/etc/agent-security-extra.policy` (operator extension)::
+Add your custom rules here.
+This file is silently skipped if absent.
+Its location can be overridden via `SOLR_SECURITY_AGENT_EXTRA_POLICY`.
+
+=== Variable Substitution
+
+Any java property will be expanded in policy entries, here are the most 
common. Environment variables are expanded if they adhere to the mapping 
convention `SOLR_FOO --> solr.foo`:
+
+[cols="1,2",options="header"]
+|===
+|Variable|Resolved Value
+|`${solr.home}`|Solr home directory
+|`${solr.data.dir}`|Solr data directory
+|`${solr.log.dir}`|Solr log directory
+|`${solr.install.dir}`|Solr installation root (parent of `server/`)
+|`${java.io.tmpdir}`|JVM temporary directory
+|`${java.home}`|JDK installation directory
+|`${solr.port}`|Solr HTTP port
+|`${solr.zk.port}`|ZooKeeper port (defaults to `solr.port + 1000` for embedded 
ZK)
+|===
+
+=== Default Intra-Cluster Network Policy
+
+The bundled policy permits connections on `*:${solr.port}` and 
`*:${solr.zk.port}` from any host, so all cluster nodes can communicate without 
any operator action.
+If your ZooKeeper runs on a non-standard port, set `-Dsolr.zk.port=<port>` at 
startup or configure it explicitly in the policy.

Review Comment:
   The variable names documented here do not match the names actually used in 
the bundled `agent-security.policy` and recognised by `PolicyPropertyExpander`. 
The policy file uses `${solr.solr.home}`, `${solr.data.home}`, 
`${solr.logs.dir}`, `${solr.port.listen}` (with special handling for 
`solr.zk.port`, `solr.install.symDir`, and `solr.data.home`), not 
`${solr.home}`, `${solr.data.dir}`, `${solr.log.dir}`, `${solr.port}`. 
Operators following this table verbatim will see "Unresolved policy variable" 
startup failures. Please align the table (and the example `${solr.port}` 
reference in the "Default Intra-Cluster Network Policy" section) with the 
actual property names.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to