gnodet-bot commented on code in PR #27833:
URL: https://github.com/apache/camel/pull/27833#discussion_r4271395843


##########
dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ToolConfirms.java:
##########
@@ -48,27 +55,99 @@ record Call(String title, String target, String reason) {
     static final Set<String> CODE_LANGUAGES
             = Set.of("groovy", "java", "joor", "js", "javascript", "python", 
"mvel", "ognl", "spel");
 
+    private ToolConfirms() {
+    }
+
     /**
-     * The components that stay inside the integration: its routes and 
in-memory queues, its log and the test endpoints.
-     * Not every component the catalog marks remote=false: exec, class, bean, 
language and the components that load a
-     * template or a stylesheet by URI are not remote either, but run code, or 
read a file or a URL.
+     * Whether a component stays inside the integration, as the catalog marks 
it internal: its routes, in-memory state
+     * and log, and no file, network, process, or code or resource named in 
its URI, by any option. This is narrower
+     * than remote=false, which exec, class, bean, language and the components 
that load a template or a stylesheet by
+     * URI are too. The catalog is the TUI's own, so it has the flag; a 
component it does not know (a custom one, or one
+     * newer than the TUI) is not internal.
      */
-    static final Set<String> INTERNAL_SCHEMES = Set.of(
-            "direct", "seda", "disruptor", "disruptor-vm", "log", "mock", 
"stub", "browse", "dataset", "dataset-test");
+    static boolean isInternal(String scheme) {
+        if (!scheme.matches("[a-z0-9-]+")) {
+            return false;
+        }
+        try {
+            ComponentModel model = Catalog.INSTANCE.componentModel(scheme);
+            return model != null && model.isInternal();
+        } catch (RuntimeException e) {
+            return false;
+        }
+    }
 
-    private ToolConfirms() {
+    // loaded on the first send, not when a call only needs the SQL or 
language checks
+    private static final class Catalog {
+        static final CamelCatalog INSTANCE = new DefaultCamelCatalog(true);
     }
 
-    /** A message to an endpoint the integration does not have, or null when 
it stays inside the integration. */
-    static Call sendMessage(String endpoint, Collection<String> endpointUris) {
-        if (staysInside(endpoint, endpointUris)) {
+    /**
+     * A message to an endpoint the integration does not have, or with a body 
read from a file outside the project; null
+     * when it stays inside the integration and the project.
+     *
+     * @param fileOutside the file outside the project that the body makes the 
integration read (see
+     *                    {@link #fileOutsideProject}), or null
+     */
+    static Call sendMessage(String endpoint, Collection<String> endpointUris, 
String fileOutside) {
+        boolean inside = staysInside(endpoint, endpointUris);
+        if (fileOutside != null) {
+            return new Call(
+                    inside
+                            ? "send a file from outside the project"
+                            : "send a file from outside the project to an 
endpoint the integration does not have",
+                    endpoint + "\nbody: file:" + fileOutside,
+                    "The integration reads the file and sends its content, 
which may hold secrets");
+        }
+        if (inside) {
             return null;
         }
         return new Call(
                 "send a message to an endpoint the integration does not have", 
endpoint,
                 "The message may leave the integration: to a file, a broker or 
a service");
     }
 
+    /**
+     * The file a message body makes the integration read when it is outside 
the project folder, or null. The send
+     * console sends the content of the file that a body starting with file: 
names, relative to the working directory of
+     * the integration. The project folder is the integration's source 
directory, where the AI tools write. Symlinks and
+     * .. are resolved as the integration would; a file that cannot be placed 
counts as outside.
+     */
+    static String fileOutsideProject(String body, String workingDirectory, 
Path projectDirectory) {
+        if (body == null || !body.startsWith("file:")) {
+            return null;
+        }
+        String name = body.substring(5);
+        Path file;
+        try {
+            Path path = Path.of(name);
+            if (!path.isAbsolute()) {
+                if (workingDirectory == null) {

Review Comment:
   ⚠️ **Empty-string `workingDirectory` bypasses the null guard.**
   
   `Path.of("").resolve(relativePath)` returns a relative path, so when 
`realPath()` calls `toAbsolutePath()` it resolves against the JVM's working 
directory, not the integration's directory. An empty string passes `!= null` 
but behaves semantically the same as a missing directory, and this is a 
security-boundary method: it determines whether an AI agent is allowed to 
access a file without user confirmation.
   
   Consider:
   ```suggestion
                   if (workingDirectory == null || workingDirectory.isBlank()) {
   ```
   
   A matching test case for `workingDirectory = ""` would be worth adding 
alongside the existing ones.



-- 
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]

Reply via email to