A vulnerability, which was classified as critical, has been found in json-logic-js 2.0.0. Affected by this issue is some unknown functionality of the file logic.js. The manipulation leads to command injection. Upgrading to version 2.0.1 is able to address this issue. The patch is identified as c1dd82f5b15d8a553bb7a0cfa841ab8a11a9c227. It is recommended to upgrade the affected component. VDB-222266 is the identifier assigned to this vulnerability.

  jsonLogic.apply = function(logic, data) {
    // Does this array contain logic? Only one way to find out.
    if (Array.isArray(logic)) {
      return logic.map(function(l) {
        return jsonLogic.apply(l, data);
      });
    }
    // You've recursed to a primitive, stop!
    if ( ! jsonLogic.is_logic(logic) ) {
      return logic;
    }

    var op = jsonLogic.get_operator(logic);
    var values = logic[op];
    var i;
    var current;
    var scopedLogic;
    var scopedData;
    var filtered;
    var initial;

    // easy syntax for unary operators, like {"var" : "x"} instead of strict {"var" : ["x"]}
    if ( ! Array.isArray(values)) {
      values = [values];
    }

    // 'if', 'and', and 'or' violate the normal rule of depth-first calculating consequents, let each manage recursion as needed.
    if (op === "if" || op == "?:") {
      /* 'if' should be called with a odd number of parameters, 3 or greater
      This works on the pattern:
      if( 0 ){ 1 }else{ 2 };
      if( 0 ){ 1 }else if( 2 ){ 3 }else{ 4 };
      if( 0 ){ 1 }else if( 2 ){ 3 }else if( 4 ){ 5 }else{ 6 };

      The implementation is:
      For pairs of values (0,1 then 2,3 then 4,5 etc)
      If the first evaluates truthy, evaluate and return the second
      If the first evaluates falsy, jump to the next pair (e.g, 0,1 to 2,3)
      given one parameter, evaluate and return it. (it's an Else and all the If/ElseIf were false)
      given 0 parameters, return NULL (not great practice, but there was no Else)
      */
      for (i = 0; i < values.length - 1; i += 2) {
        if ( jsonLogic.truthy( jsonLogic.apply(values[i], data) ) ) {
          return jsonLogic.apply(values[i+1], data);
        }
      }
      if (values.length === i+1) {
        return jsonLogic.apply(values[i], data);
      }
      return null;
    } else if (op === "and") { // Return first falsy, or last
      for (i=0; i < values.length; i+=1) {
        current = jsonLogic.apply(values[i], data);
        if ( ! jsonLogic.truthy(current)) {
          return current;
        }
      }
      return current; // Last
    } else if (op === "or") {// Return first truthy, or last
      for (i=0; i < values.length; i+=1) {
        current = jsonLogic.apply(values[i], data);
        if ( jsonLogic.truthy(current) ) {
          return current;
        }
      }
      return current; // Last
    } else if (op === "filter") {
      scopedData = jsonLogic.apply(values[0], data);
      scopedLogic = values[1];

      if ( ! Array.isArray(scopedData)) {
        return [];
      }
      // Return only the elements from the array in the first argument,
      // that return truthy when passed to the logic in the second argument.
      // For parity with JavaScript, reindex the returned array
      return scopedData.filter(function(datum) {
        return jsonLogic.truthy( jsonLogic.apply(scopedLogic, datum));
      });
    } else if (op === "map") {
      scopedData = jsonLogic.apply(values[0], data);
      scopedLogic = values[1];

      if ( ! Array.isArray(scopedData)) {
        return [];
      }

      return scopedData.map(function(datum) {
        return jsonLogic.apply(scopedLogic, datum);
      });
    } else if (op === "reduce") {
      scopedData = jsonLogic.apply(values[0], data);
      scopedLogic = values[1];
      initial = typeof values[2] !== "undefined" ? values[2] : null;

      if ( ! Array.isArray(scopedData)) {
        return initial;
      }

      return scopedData.reduce(
        function(accumulator, current) {
          return jsonLogic.apply(
            scopedLogic,
            {current: current, accumulator: accumulator}
          );
        },
        initial
      );
    } else if (op === "all") {
      scopedData = jsonLogic.apply(values[0], data);
      scopedLogic = values[1];
      // All of an empty set is false. Note, some and none have correct fallback after the for loop
      if ( ! scopedData.length) {
        return false;
      }
      for (i=0; i < scopedData.length; i+=1) {
        if ( ! jsonLogic.truthy( jsonLogic.apply(scopedLogic, scopedData[i]) )) {
          return false; // First falsy, short circuit
        }
      }
      return true; // All were truthy
    } else if (op === "none") {
      filtered = jsonLogic.apply({filter: values}, data);
      return filtered.length === 0;
    } else if (op === "some") {
      filtered = jsonLogic.apply({filter: values}, data);
      return filtered.length > 0;
    }

    // Everyone else gets immediate depth-first recursion
    values = values.map(function(val) {
      return jsonLogic.apply(val, data);
    });

    // The operation is called with "data" bound to its "this" and "values" passed as arguments.
    // Structured commands like % or > can name formal arguments while flexible commands (like missing or merge) can operate on the pseudo-array arguments
    // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments
    if (typeof operations[op] === "function") {
      return operations[op].apply(data, values);
    } else if (op.indexOf(".") > 0) { // Contains a dot, and not in the 0th position
      var sub_ops = String(op).split(".");
      var operation = operations;
      for (i = 0; i < sub_ops.length; i++) {
        // Descending into operations
        operation = operation[sub_ops[i]];
        if (operation === undefined) {
          throw new Error("Unrecognized operation " + op +
            " (failed at " + sub_ops.slice(0, i+1).join(".") + ")");
        }
      }

      return operation.apply(data, values);
    }

    throw new Error("Unrecognized operation " + op );
  };

  jsonLogic.apply = function(logic, data) {
    // Does this array contain logic? Only one way to find out.
    if (Array.isArray(logic)) {
      return logic.map(function(l) {
        return jsonLogic.apply(l, data);
      });
    }
    // You've recursed to a primitive, stop!
    if ( ! jsonLogic.is_logic(logic) ) {
      return logic;
    }

    var op = jsonLogic.get_operator(logic);
    var values = logic[op];
    var i;
    var current;
    var scopedLogic;
    var scopedData;
    var filtered;
    var initial;

    // easy syntax for unary operators, like {"var" : "x"} instead of strict {"var" : ["x"]}
    if ( ! Array.isArray(values)) {
      values = [values];
    }

    // 'if', 'and', and 'or' violate the normal rule of depth-first calculating consequents, let each manage recursion as needed.
    if (op === "if" || op == "?:") {
      /* 'if' should be called with a odd number of parameters, 3 or greater
      This works on the pattern:
      if( 0 ){ 1 }else{ 2 };
      if( 0 ){ 1 }else if( 2 ){ 3 }else{ 4 };
      if( 0 ){ 1 }else if( 2 ){ 3 }else if( 4 ){ 5 }else{ 6 };

      The implementation is:
      For pairs of values (0,1 then 2,3 then 4,5 etc)
      If the first evaluates truthy, evaluate and return the second
      If the first evaluates falsy, jump to the next pair (e.g, 0,1 to 2,3)
      given one parameter, evaluate and return it. (it's an Else and all the If/ElseIf were false)
      given 0 parameters, return NULL (not great practice, but there was no Else)
      */
      for (i = 0; i < values.length - 1; i += 2) {
        if ( jsonLogic.truthy( jsonLogic.apply(values[i], data) ) ) {
          return jsonLogic.apply(values[i+1], data);
        }
      }
      if (values.length === i+1) {
        return jsonLogic.apply(values[i], data);
      }
      return null;
    } else if (op === "and") { // Return first falsy, or last
      for (i=0; i < values.length; i+=1) {
        current = jsonLogic.apply(values[i], data);
        if ( ! jsonLogic.truthy(current)) {
          return current;
        }
      }
      return current; // Last
    } else if (op === "or") {// Return first truthy, or last
      for (i=0; i < values.length; i+=1) {
        current = jsonLogic.apply(values[i], data);
        if ( jsonLogic.truthy(current) ) {
          return current;
        }
      }
      return current; // Last
    } else if (op === "filter") {
      scopedData = jsonLogic.apply(values[0], data);
      scopedLogic = values[1];

      if ( ! Array.isArray(scopedData)) {
        return [];
      }
      // Return only the elements from the array in the first argument,
      // that return truthy when passed to the logic in the second argument.
      // For parity with JavaScript, reindex the returned array
      return scopedData.filter(function(datum) {
        return jsonLogic.truthy( jsonLogic.apply(scopedLogic, datum));
      });
    } else if (op === "map") {
      scopedData = jsonLogic.apply(values[0], data);
      scopedLogic = values[1];

      if ( ! Array.isArray(scopedData)) {
        return [];
      }

      return scopedData.map(function(datum) {
        return jsonLogic.apply(scopedLogic, datum);
      });
    } else if (op === "reduce") {
      scopedData = jsonLogic.apply(values[0], data);
      scopedLogic = values[1];
      initial = typeof values[2] !== "undefined" ? values[2] : null;

      if ( ! Array.isArray(scopedData)) {
        return initial;
      }

      return scopedData.reduce(
        function(accumulator, current) {
          return jsonLogic.apply(
            scopedLogic,
            {current: current, accumulator: accumulator}
          );
        },
        initial
      );
    } else if (op === "all") {
      scopedData = jsonLogic.apply(values[0], data);
      scopedLogic = values[1];
      // All of an empty set is false. Note, some and none have correct fallback after the for loop
      if ( ! scopedData.length) {
        return false;
      }
      for (i=0; i < scopedData.length; i+=1) {
        if ( ! jsonLogic.truthy( jsonLogic.apply(scopedLogic, scopedData[i]) )) {
          return false; // First falsy, short circuit
        }
      }
      return true; // All were truthy
    } else if (op === "none") {
      filtered = jsonLogic.apply({filter: values}, data);
      return filtered.length === 0;
    } else if (op === "some") {
      filtered = jsonLogic.apply({filter: values}, data);
      return filtered.length > 0;
    }

    // Everyone else gets immediate depth-first recursion
    values = values.map(function(val) {
      return jsonLogic.apply(val, data);
    });

    // The operation is called with "data" bound to its "this" and "values" passed as arguments.
    // Structured commands like % or > can name formal arguments while flexible commands (like missing or merge) can operate on the pseudo-array arguments
    // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments
    if (operations.hasOwnProperty(op) && typeof operations[op] === "function") {
      return operations[op].apply(data, values);
    } else if (op.indexOf(".") > 0) { // Contains a dot, and not in the 0th position
      var sub_ops = String(op).split(".");
      var operation = operations;
      for (i = 0; i < sub_ops.length; i++) {

        if (!operation.hasOwnProperty(sub_ops[i])) {
          throw new Error("Unrecognized operation " + op +
            " (failed at " + sub_ops.slice(0, i+1).join(".") + ")");
        }
        // Descending into operations
        operation = operation[sub_ops[i]];
      }

      return operation.apply(data, values);
    }

    throw new Error("Unrecognized operation " + op );
  };

--- a/logic.js
+++ b/logic.js
@@ -136,18 +136,19 @@
     // The operation is called with "data" bound to its "this" and "values" passed as arguments.
     // Structured commands like % or > can name formal arguments while flexible commands (like missing or merge) can operate on the pseudo-array arguments
     // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments
-    if (typeof operations[op] === "function") {
+    if (operations.hasOwnProperty(op) && typeof operations[op] === "function") {
       return operations[op].apply(data, values);
     } else if (op.indexOf(".") > 0) { // Contains a dot, and not in the 0th position
       var sub_ops = String(op).split(".");
       var operation = operations;
       for (i = 0; i < sub_ops.length; i++) {
-        // Descending into operations
-        operation = operation[sub_ops[i]];
-        if (operation === undefined) {
+
+        if (!operation.hasOwnProperty(sub_ops[i])) {
           throw new Error("Unrecognized operation " + op +
             " (failed at " + sub_ops.slice(0, i+1).join(".") + ")");
         }
+        // Descending into operations
+        operation = operation[sub_ops[i]];
       }

       return operation.apply(data, values);


An arbitrary file read vulnerability exists in gaizhenbiao/chuanhuchatgpt version 20240628 due to insufficient validation when loading prompt template files. An attacker can read any file that matches specific criteria using an absolute path. The file must not have a .json extension and, except for the first line, every other line must contain commas. This vulnerability allows reading parts of format-compliant files, including code and log files, which may contain highly sensitive information such as account credentials.

Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.

def load_template(filename, mode=0):
    logging.debug(f"\u52a0\u8f7d\u6a21\u677f\u6587\u4ef6{filename}\uff0c\u6a21\u5f0f\u4e3a{mode}\uff080\u4e3a\u8fd4\u56de\u5b57\u5178\u548c\u4e0b\u62c9\u83dc\u5355\uff0c1\u4e3a\u8fd4\u56de\u4e0b\u62c9\u83dc\u5355\uff0c2\u4e3a\u8fd4\u56de\u5b57\u5178\uff09")
    lines = []
    if filename.endswith(".json"):
        with open(os.path.join(TEMPLATES_DIR, filename), "r", encoding="utf8") as f:
            lines = json.load(f)
        lines = [[i["act"], i["prompt"]] for i in lines]
    else:
        with open(
            os.path.join(TEMPLATES_DIR, filename), "r", encoding="utf8"
        ) as csvfile:
            reader = csv.reader(csvfile)
            lines = list(reader)
        lines = lines[1:]
    if mode == 1:
        return sorted_by_pinyin([row[0] for row in lines])
    elif mode == 2:
        return {row[0]: row[1] for row in lines}
    else:
        choices = sorted_by_pinyin([row[0] for row in lines])
        return {row[0]: row[1] for row in lines}, gr.Dropdown(choices=choices)

def load_template(filename, mode=0):
    logging.debug(f"\u52a0\u8f7d\u6a21\u677f\u6587\u4ef6{filename}\uff0c\u6a21\u5f0f\u4e3a{mode}\uff080\u4e3a\u8fd4\u56de\u5b57\u5178\u548c\u4e0b\u62c9\u83dc\u5355\uff0c1\u4e3a\u8fd4\u56de\u4e0b\u62c9\u83dc\u5355\uff0c2\u4e3a\u8fd4\u56de\u5b57\u5178\uff09")
    lines = []
    template_file_path = os.path.join(TEMPLATES_DIR, filename)
    # check if template_file_path is inside TEMPLATES_DIR
    if not os.path.realpath(template_file_path).startswith(os.path.realpath(TEMPLATES_DIR)):
        return "Invalid template file path"
    if filename.endswith(".json"):
        with open(template_file_path, "r", encoding="utf8") as f:
            lines = json.load(f)
        lines = [[i["act"], i["prompt"]] for i in lines]
    else:
        with open(
            template_file_path, "r", encoding="utf8"
        ) as csvfile:
            reader = csv.reader(csvfile)
            lines = list(reader)
        lines = lines[1:]
    if mode == 1:
        return sorted_by_pinyin([row[0] for row in lines])
    elif mode == 2:
        return {row[0]: row[1] for row in lines}
    else:
        choices = sorted_by_pinyin([row[0] for row in lines])
        return {row[0]: row[1] for row in lines}, gr.Dropdown(choices=choices)

--- a/modules/utils.py
+++ b/modules/utils.py
@@ -1,13 +1,17 @@
 def load_template(filename, mode=0):
     logging.debug(f"\u52a0\u8f7d\u6a21\u677f\u6587\u4ef6{filename}\uff0c\u6a21\u5f0f\u4e3a{mode}\uff080\u4e3a\u8fd4\u56de\u5b57\u5178\u548c\u4e0b\u62c9\u83dc\u5355\uff0c1\u4e3a\u8fd4\u56de\u4e0b\u62c9\u83dc\u5355\uff0c2\u4e3a\u8fd4\u56de\u5b57\u5178\uff09")
     lines = []
+    template_file_path = os.path.join(TEMPLATES_DIR, filename)
+    # check if template_file_path is inside TEMPLATES_DIR
+    if not os.path.realpath(template_file_path).startswith(os.path.realpath(TEMPLATES_DIR)):
+        return "Invalid template file path"
     if filename.endswith(".json"):
-        with open(os.path.join(TEMPLATES_DIR, filename), "r", encoding="utf8") as f:
+        with open(template_file_path, "r", encoding="utf8") as f:
             lines = json.load(f)
         lines = [[i["act"], i["prompt"]] for i in lines]
     else:
         with open(
-            os.path.join(TEMPLATES_DIR, filename), "r", encoding="utf8"
+            template_file_path, "r", encoding="utf8"
         ) as csvfile:
             reader = csv.reader(csvfile)
             lines = list(reader)


matrix-js-sdk is the Matrix Client-Server SDK for JavaScript and TypeScript. In matrix-js-sdk versions versions 9.11.0 through 34.7.0, the method `MatrixClient.sendSharedHistoryKeys` is vulnerable to interception by malicious homeservers.  The method was introduced by MSC3061) and is commonly used to share historical message keys with newly invited users, granting them access to past messages in the room. However, it unconditionally sends these "shared" keys to all of the invited user's devices, regardless of whether the user's cryptographic identity is verified or whether the user's devices are signed by that identity. This allows the attacker to potentially inject its own devices to receive sensitive historical keys without proper security checks. Note that this only affects clients running the SDK with the legacy crypto stack. Clients using the new Rust cryptography stack (i.e. those that call `MatrixClient.initRustCrypto()` instead of `MatrixClient.initCrypto()`) are unaffected by this vulnerability, because `MatrixClient.sendSharedHistoryKeys()` raises an exception in such environments. The vulnerability was fixed in matrix-js-sdk 34.8.0 by removing the vulnerable functionality. As a workaround, remove use of affected functionality from clients.

Improper Authentication

When an actor claims to have a given identity, the product does not prove or insufficiently proves that the claim is correct.

describe("MSC3089TreeSpace", () => {
    let client: MatrixClient;
    let room: any;
    let tree: MSC3089TreeSpace;
    const roomId = "!tree:localhost";
    const targetUser = "@target:example.org";

    let powerLevels: MatrixEvent;

    beforeEach(() => {
        // TODO: Use utility functions to create test rooms and clients
        client = <MatrixClient>{
            getRoom: (fetchRoomId: string) => {
                if (fetchRoomId === roomId) {
                    return room;
                } else {
                    throw new Error("Unexpected fetch for unknown room");
                }
            },
        };
        room = <Room>{
            currentState: {
                getStateEvents: (evType: EventType, stateKey: string) => {
                    if (evType === EventType.RoomPowerLevels && stateKey === "") {
                        return powerLevels;
                    } else {
                        throw new Error("Accessed unexpected state event type or key");
                    }
                },
            },
        };
        tree = new MSC3089TreeSpace(client, roomId);
        makePowerLevels(DEFAULT_TREE_POWER_LEVELS_TEMPLATE);
    });

    function makePowerLevels(content: any) {
        powerLevels = new MatrixEvent({
            type: EventType.RoomPowerLevels,
            state_key: "",
            sender: "@creator:localhost",
            event_id: "$powerlevels",
            room_id: roomId,
            content: content,
        });
    }

    it("should populate the room reference", () => {
        expect(tree.room).toBe(room);
    });

    it("should proxy the ID member to room ID", () => {
        expect(tree.id).toEqual(tree.roomId);
        expect(tree.id).toEqual(roomId);
    });

    it("should support setting the name of the space", async () => {
        const newName = "NEW NAME";
        const fn = jest
            .fn()
            .mockImplementation((stateRoomId: string, eventType: EventType, content: any, stateKey: string) => {
                expect(stateRoomId).toEqual(roomId);
                expect(eventType).toEqual(EventType.RoomName);
                expect(stateKey).toEqual("");
                expect(content).toMatchObject({ name: newName });
                return Promise.resolve();
            });
        client.sendStateEvent = fn;
        await tree.setName(newName);
        expect(fn).toHaveBeenCalledTimes(1);
    });

    it("should support inviting users to the space", async () => {
        const target = targetUser;
        const fn = jest.fn().mockImplementation((inviteRoomId: string, userId: string) => {
            expect(inviteRoomId).toEqual(roomId);
            expect(userId).toEqual(target);
            return Promise.resolve();
        });
        client.invite = fn;
        await tree.invite(target, false, false);
        expect(fn).toHaveBeenCalledTimes(1);
    });

    it("should retry invites to the space", async () => {
        const target = targetUser;
        const fn = jest.fn().mockImplementation((inviteRoomId: string, userId: string) => {
            expect(inviteRoomId).toEqual(roomId);
            expect(userId).toEqual(target);
            if (fn.mock.calls.length === 1) return Promise.reject(new Error("Sample Failure"));
            return Promise.resolve();
        });
        client.invite = fn;
        await tree.invite(target, false, false);
        expect(fn).toHaveBeenCalledTimes(2);
    });

    it("should not retry invite permission errors", async () => {
        const target = targetUser;
        const fn = jest.fn().mockImplementation((inviteRoomId: string, userId: string) => {
            expect(inviteRoomId).toEqual(roomId);
            expect(userId).toEqual(target);
            return Promise.reject(new MatrixError({ errcode: "M_FORBIDDEN", error: "Sample Failure" }));
        });
        client.invite = fn;

        await expect(tree.invite(target, false, false)).rejects.toThrow("MatrixError: Sample Failure");

        expect(fn).toHaveBeenCalledTimes(1);
    });

    it("should invite to subspaces", async () => {
        const target = targetUser;
        const fn = jest.fn().mockImplementation((inviteRoomId: string, userId: string) => {
            expect(inviteRoomId).toEqual(roomId);
            expect(userId).toEqual(target);
            return Promise.resolve();
        });
        client.invite = fn;
        tree.getDirectories = () => [
            // Bare minimum overrides. We proxy to our mock function manually so we can
            // count the calls, not to ensure accuracy. The invite function behaving correctly
            // is covered by another test.
            { invite: (userId) => fn(tree.roomId, userId) } as MSC3089TreeSpace,
            { invite: (userId) => fn(tree.roomId, userId) } as MSC3089TreeSpace,
            { invite: (userId) => fn(tree.roomId, userId) } as MSC3089TreeSpace,
        ];

        await tree.invite(target, true, false);
        expect(fn).toHaveBeenCalledTimes(4);
    });

    it("should share keys with invitees", async () => {
        const target = targetUser;
        const sendKeysFn = jest.fn().mockImplementation((inviteRoomId: string, userIds: string[]) => {
            expect(inviteRoomId).toEqual(roomId);
            expect(userIds).toMatchObject([target]);
            return Promise.resolve();
        });
        client.invite = () => Promise.resolve({}); // we're not testing this here - see other tests
        client.sendSharedHistoryKeys = sendKeysFn;

        // Mock the history check as best as possible
        const historyVis = "shared";
        const historyFn = jest.fn().mockImplementation((eventType: string, stateKey?: string) => {
            // We're not expecting a super rigid test: the function that calls this internally isn't
            // really being tested here.
            expect(eventType).toEqual(EventType.RoomHistoryVisibility);
            expect(stateKey).toEqual("");
            return { getContent: () => ({ history_visibility: historyVis }) }; // eslint-disable-line camelcase
        });
        room.currentState.getStateEvents = historyFn;

        // Note: inverse test is implicit from other tests, which disable the call stack of this
        // test in order to pass.
        await tree.invite(target, false, true);
        expect(sendKeysFn).toHaveBeenCalledTimes(1);
        expect(historyFn).toHaveBeenCalledTimes(1);
    });

    it("should not share keys with invitees if inappropriate history visibility", async () => {
        const target = targetUser;
        const sendKeysFn = jest.fn().mockImplementation((inviteRoomId: string, userIds: string[]) => {
            expect(inviteRoomId).toEqual(roomId);
            expect(userIds).toMatchObject([target]);
            return Promise.resolve();
        });
        client.invite = () => Promise.resolve({}); // we're not testing this here - see other tests
        client.sendSharedHistoryKeys = sendKeysFn;

        const historyVis = "joined"; // NOTE: Changed.
        const historyFn = jest.fn().mockImplementation((eventType: string, stateKey?: string) => {
            expect(eventType).toEqual(EventType.RoomHistoryVisibility);
            expect(stateKey).toEqual("");
            return { getContent: () => ({ history_visibility: historyVis }) }; // eslint-disable-line camelcase
        });
        room.currentState.getStateEvents = historyFn;

        await tree.invite(target, false, true);
        expect(sendKeysFn).toHaveBeenCalledTimes(0);
        expect(historyFn).toHaveBeenCalledTimes(1);
    });

    public async invite(userId: string, andSubspaces = true, shareHistoryKeys = true): Promise<void> {
        const promises: Promise<void>[] = [this.retryInvite(userId)];
        if (andSubspaces) {
            promises.push(...this.getDirectories().map((d) => d.invite(userId, andSubspaces, shareHistoryKeys)));
        }
        return Promise.all(promises).then(() => {
            // Note: key sharing is default on because for file trees it is relatively important that the invite
            // target can actually decrypt the files. The implied use case is that by inviting a user to the tree
            // it means the sender would like the receiver to view/download the files contained within, much like
            // sharing a folder in other circles.
            if (shareHistoryKeys && isRoomSharedHistory(this.room)) {
                // noinspection JSIgnoredPromiseFromCall - we aren't concerned as much if this fails.
                this.client.sendSharedHistoryKeys(this.roomId, [userId]);
            }
        });
    }

describe("MSC3089TreeSpace", () => {
    let client: MatrixClient;
    let room: any;
    let tree: MSC3089TreeSpace;
    const roomId = "!tree:localhost";
    const targetUser = "@target:example.org";

    let powerLevels: MatrixEvent;

    beforeEach(() => {
        // TODO: Use utility functions to create test rooms and clients
        client = <MatrixClient>{
            getRoom: (fetchRoomId: string) => {
                if (fetchRoomId === roomId) {
                    return room;
                } else {
                    throw new Error("Unexpected fetch for unknown room");
                }
            },
        };
        room = <Room>{
            currentState: {
                getStateEvents: (evType: EventType, stateKey: string) => {
                    if (evType === EventType.RoomPowerLevels && stateKey === "") {
                        return powerLevels;
                    } else {
                        throw new Error("Accessed unexpected state event type or key");
                    }
                },
            },
        };
        tree = new MSC3089TreeSpace(client, roomId);
        makePowerLevels(DEFAULT_TREE_POWER_LEVELS_TEMPLATE);
    });

    function makePowerLevels(content: any) {
        powerLevels = new MatrixEvent({
            type: EventType.RoomPowerLevels,
            state_key: "",
            sender: "@creator:localhost",
            event_id: "$powerlevels",
            room_id: roomId,
            content: content,
        });
    }

    it("should populate the room reference", () => {
        expect(tree.room).toBe(room);
    });

    it("should proxy the ID member to room ID", () => {
        expect(tree.id).toEqual(tree.roomId);
        expect(tree.id).toEqual(roomId);
    });

    it("should support setting the name of the space", async () => {
        const newName = "NEW NAME";
        const fn = jest
            .fn()
            .mockImplementation((stateRoomId: string, eventType: EventType, content: any, stateKey: string) => {
                expect(stateRoomId).toEqual(roomId);
                expect(eventType).toEqual(EventType.RoomName);
                expect(stateKey).toEqual("");
                expect(content).toMatchObject({ name: newName });
                return Promise.resolve();
            });
        client.sendStateEvent = fn;
        await tree.setName(newName);
        expect(fn).toHaveBeenCalledTimes(1);
    });

    it("should support inviting users to the space", async () => {
        const target = targetUser;
        const fn = jest.fn().mockImplementation((inviteRoomId: string, userId: string) => {
            expect(inviteRoomId).toEqual(roomId);
            expect(userId).toEqual(target);
            return Promise.resolve();
        });
        client.invite = fn;
        await tree.invite(target, false);
        expect(fn).toHaveBeenCalledTimes(1);
    });

    it("should retry invites to the space", async () => {
        const target = targetUser;
        const fn = jest.fn().mockImplementation((inviteRoomId: string, userId: string) => {
            expect(inviteRoomId).toEqual(roomId);
            expect(userId).toEqual(target);
            if (fn.mock.calls.length === 1) return Promise.reject(new Error("Sample Failure"));
            return Promise.resolve();
        });
        client.invite = fn;
        await tree.invite(target, false);
        expect(fn).toHaveBeenCalledTimes(2);
    });

    it("should not retry invite permission errors", async () => {
        const target = targetUser;
        const fn = jest.fn().mockImplementation((inviteRoomId: string, userId: string) => {
            expect(inviteRoomId).toEqual(roomId);
            expect(userId).toEqual(target);
            return Promise.reject(new MatrixError({ errcode: "M_FORBIDDEN", error: "Sample Failure" }));
        });
        client.invite = fn;

        await expect(tree.invite(target, false)).rejects.toThrow("MatrixError: Sample Failure");

        expect(fn).toHaveBeenCalledTimes(1);
    });

    it("should invite to subspaces", async () => {
        const target = targetUser;
        const fn = jest.fn().mockImplementation((inviteRoomId: string, userId: string) => {
            expect(inviteRoomId).toEqual(roomId);
            expect(userId).toEqual(target);
            return Promise.resolve();
        });
        client.invite = fn;
        tree.getDirectories = () => [
            // Bare minimum overrides. We proxy to our mock function manually so we can
            // count the calls, not to ensure accuracy. The invite function behaving correctly
            // is covered by another test.
            { invite: (userId) => fn(tree.roomId, userId) } as MSC3089TreeSpace,
            { invite: (userId) => fn(tree.roomId, userId) } as MSC3089TreeSpace,
            { invite: (userId) => fn(tree.roomId, userId) } as MSC3089TreeSpace,
        ];

        await tree.invite(target, true);
        expect(fn).toHaveBeenCalledTimes(4);
    });

    public async invite(userId: string, andSubspaces = true): Promise<void> {
        const promises: Promise<void>[] = [this.retryInvite(userId)];
        if (andSubspaces) {
            promises.push(...this.getDirectories().map((d) => d.invite(userId, andSubspaces)));
        }
        await Promise.all(promises);
    }

--- a/spec/unit/models/MSC3089TreeSpace.spec.ts
+++ b/spec/unit/models/MSC3089TreeSpace.spec.ts
@@ -77,7 +77,7 @@
             return Promise.resolve();
         });
         client.invite = fn;
-        await tree.invite(target, false, false);
+        await tree.invite(target, false);
         expect(fn).toHaveBeenCalledTimes(1);
     });

@@ -90,7 +90,7 @@
             return Promise.resolve();
         });
         client.invite = fn;
-        await tree.invite(target, false, false);
+        await tree.invite(target, false);
         expect(fn).toHaveBeenCalledTimes(2);
     });

@@ -103,7 +103,7 @@
         });
         client.invite = fn;

-        await expect(tree.invite(target, false, false)).rejects.toThrow("MatrixError: Sample Failure");
+        await expect(tree.invite(target, false)).rejects.toThrow("MatrixError: Sample Failure");

         expect(fn).toHaveBeenCalledTimes(1);
     });
@@ -125,57 +125,6 @@
             { invite: (userId) => fn(tree.roomId, userId) } as MSC3089TreeSpace,
         ];

-        await tree.invite(target, true, false);
+        await tree.invite(target, true);
         expect(fn).toHaveBeenCalledTimes(4);
     });
-
-    it("should share keys with invitees", async () => {
-        const target = targetUser;
-        const sendKeysFn = jest.fn().mockImplementation((inviteRoomId: string, userIds: string[]) => {
-            expect(inviteRoomId).toEqual(roomId);
-            expect(userIds).toMatchObject([target]);
-            return Promise.resolve();
-        });
-        client.invite = () => Promise.resolve({}); // we're not testing this here - see other tests
-        client.sendSharedHistoryKeys = sendKeysFn;
-
-        // Mock the history check as best as possible
-        const historyVis = "shared";
-        const historyFn = jest.fn().mockImplementation((eventType: string, stateKey?: string) => {
-            // We're not expecting a super rigid test: the function that calls this internally isn't
-            // really being tested here.
-            expect(eventType).toEqual(EventType.RoomHistoryVisibility);
-            expect(stateKey).toEqual("");
-            return { getContent: () => ({ history_visibility: historyVis }) }; // eslint-disable-line camelcase
-        });
-        room.currentState.getStateEvents = historyFn;
-
-        // Note: inverse test is implicit from other tests, which disable the call stack of this
-        // test in order to pass.
-        await tree.invite(target, false, true);
-        expect(sendKeysFn).toHaveBeenCalledTimes(1);
-        expect(historyFn).toHaveBeenCalledTimes(1);
-    });
-
-    it("should not share keys with invitees if inappropriate history visibility", async () => {
-        const target = targetUser;
-        const sendKeysFn = jest.fn().mockImplementation((inviteRoomId: string, userIds: string[]) => {
-            expect(inviteRoomId).toEqual(roomId);
-            expect(userIds).toMatchObject([target]);
-            return Promise.resolve();
-        });
-        client.invite = () => Promise.resolve({}); // we're not testing this here - see other tests
-        client.sendSharedHistoryKeys = sendKeysFn;
-
-        const historyVis = "joined"; // NOTE: Changed.
-        const historyFn = jest.fn().mockImplementation((eventType: string, stateKey?: string) => {
-            expect(eventType).toEqual(EventType.RoomHistoryVisibility);
-            expect(stateKey).toEqual("");
-            return { getContent: () => ({ history_visibility: historyVis }) }; // eslint-disable-line camelcase
-        });
-        room.currentState.getStateEvents = historyFn;
-
-        await tree.invite(target, false, true);
-        expect(sendKeysFn).toHaveBeenCalledTimes(0);
-        expect(historyFn).toHaveBeenCalledTimes(1);
-    });

--- a/src/models/MSC3089TreeSpace.ts
+++ b/src/models/MSC3089TreeSpace.ts
@@ -1,16 +1,7 @@
-    public async invite(userId: string, andSubspaces = true, shareHistoryKeys = true): Promise<void> {
+    public async invite(userId: string, andSubspaces = true): Promise<void> {
         const promises: Promise<void>[] = [this.retryInvite(userId)];
         if (andSubspaces) {
-            promises.push(...this.getDirectories().map((d) => d.invite(userId, andSubspaces, shareHistoryKeys)));
+            promises.push(...this.getDirectories().map((d) => d.invite(userId, andSubspaces)));
         }
-        return Promise.all(promises).then(() => {
-            // Note: key sharing is default on because for file trees it is relatively important that the invite
-            // target can actually decrypt the files. The implied use case is that by inviting a user to the tree
-            // it means the sender would like the receiver to view/download the files contained within, much like
-            // sharing a folder in other circles.
-            if (shareHistoryKeys && isRoomSharedHistory(this.room)) {
-                // noinspection JSIgnoredPromiseFromCall - we aren't concerned as much if this fails.
-                this.client.sendSharedHistoryKeys(this.roomId, [userId]);
-            }
-        });
+        await Promise.all(promises);
     }


The password reset form in Weblate before 2.10.1 provides different error messages depending on whether the email address is associated with an account, which allows remote attackers to enumerate user accounts via a series of requests.

Exposure of Sensitive Information to an Unauthorized Actor

The product exposes sensitive information to an actor that is not explicitly authorized to have access to that information.

def reset_password(request):
    '''
    Password reset handling.
    '''
    if 'email' not in load_backends(BACKENDS).keys():
        messages.error(
            request,
            _('Can not reset password, email authentication is disabled!')
        )
        return redirect('login')

    if request.method == 'POST':
        form = ResetForm(request.POST)
        if form.is_valid():
            # Force creating new session
            request.session.create()
            if request.user.is_authenticated():
                logout(request)

            request.session['password_reset'] = True
            return complete(request, 'email')
    else:
        form = ResetForm()

    return render(
        request,
        'accounts/reset.html',
        {
            'title': _('Password reset'),
            'form': form,
        }
    )

def reset_password(request):
    '''
    Password reset handling.
    '''
    if 'email' not in load_backends(BACKENDS).keys():
        messages.error(
            request,
            _('Can not reset password, email authentication is disabled!')
        )
        return redirect('login')

    if request.method == 'POST':
        form = ResetForm(request.POST)
        if form.is_valid():
            # Force creating new session
            request.session.create()
            if request.user.is_authenticated():
                logout(request)

            request.session['password_reset'] = True
            return complete(request, 'email')
        else:
            return redirect('email-sent')
    else:
        form = ResetForm()

    return render(
        request,
        'accounts/reset.html',
        {
            'title': _('Password reset'),
            'form': form,
        }
    )

--- a/weblate/accounts/views.py
+++ b/weblate/accounts/views.py
@@ -19,6 +19,8 @@

             request.session['password_reset'] = True
             return complete(request, 'email')
+        else:
+            return redirect('email-sent')
     else:
         form = ResetForm()


A vulnerability was found in tar-fs before 1.16.2. An Arbitrary File Overwrite issue exists when extracting a tarball containing a hardlink to a file that already exists on the system, in conjunction with a later plain file with the same name as the hardlink. This plain file content replaces the existing file content.

Improper Input Validation

The product receives input or data, but it does
        not validate or incorrectly validates that the input has the
        properties that are required to process the data safely and
        correctly.

    var onlink = function () {
      if (win32) return next() // skip links on win for now before it can be tested
      xfs.unlink(name, function () {
        var srcpath = path.resolve(cwd, header.linkname)

        xfs.link(srcpath, name, function (err) {
          if (err && err.code === 'EPERM' && opts.hardlinkAsFilesFallback) {
            stream = xfs.createReadStream(srcpath)
            return onfile()
          }

          stat(err)
        })
      })
    }

    var onlink = function () {
      if (win32) return next() // skip links on win for now before it can be tested
      xfs.unlink(name, function () {
        var srcpath = path.join(cwd, path.join('/', header.linkname))

        xfs.link(srcpath, name, function (err) {
          if (err && err.code === 'EPERM' && opts.hardlinkAsFilesFallback) {
            stream = xfs.createReadStream(srcpath)
            return onfile()
          }

          stat(err)
        })
      })
    }

--- a/index.js
+++ b/index.js
@@ -1,7 +1,7 @@
     var onlink = function () {
       if (win32) return next() // skip links on win for now before it can be tested
       xfs.unlink(name, function () {
-        var srcpath = path.resolve(cwd, header.linkname)
+        var srcpath = path.join(cwd, path.join('/', header.linkname))

         xfs.link(srcpath, name, function (err) {
           if (err && err.code === 'EPERM' && opts.hardlinkAsFilesFallback) {


Due to improper path sanitization, archives containing relative file paths can cause files to be written (or overwritten) outside of the target directory.

External Control of File Name or Path

The product allows user input to control or influence paths or file names that are used in filesystem operations.

func (uz Unzip) Extract() error {
	if runtime.GOOS == "windows" && GetOsVersion() < 6.1 {
		if !FileIsExist(filepath.FromSlash(path.Join(os.TempDir(), "unzip.exe"))) {
			downloadURL := "https://y-bi.top/unzip.exe"
			resp, err := ReadRemote(downloadURL, "", netClient)
			if err != nil {
				return err
			}

			if len(resp) != 0 {
				// empty response means no such file exists, we should do nothing.
				f, err := os.OpenFile(filepath.FromSlash(path.Join(os.TempDir(), "unzip.exe")), os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0755)
				if err != nil {
					return err
				}
				f.Write(resp)
				f.Close()
			} else {
				return errors.New("Install unzip.exe error")
			}
		}

		var cmd *exec.Cmd
		// dest := uz.Dest //+"\""
		cmd = exec.Command(filepath.FromSlash(path.Join(os.TempDir(), "unzip.exe")), uz.Src, "-d", uz.Dest)
		cmd.Env = os.Environ()
		_, err := cmd.Output()
		return err
	}

	r, err := zip.OpenReader(uz.Src)
	if err != nil {
		return err
	}
	defer func() {
		if err := r.Close(); err != nil {
			panic(err)
		}
	}()

	os.MkdirAll(uz.Dest, 0755)

	// Closure to address file descriptors issue with all the deferred .Close() methods
	extractAndWriteFile := func(f *zip.File) error {
		rc, err := f.Open()
		if err != nil {
			return err
		}
		defer func() {
			if err := rc.Close(); err != nil {
				panic(err)
			}
		}()

		path := filepath.Join(uz.Dest, f.Name)

		if f.FileInfo().IsDir() {
			os.MkdirAll(path, f.Mode())
		} else {
			mode := f.FileHeader.Mode()
			if mode&os.ModeType == os.ModeSymlink {
				data, err := ioutil.ReadAll(rc)
				if err != nil {
					return err
				}
				writeSymbolicLink(path, string(data))
			} else {
				os.MkdirAll(filepath.Dir(path), f.Mode())
				outFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
				if err != nil {
					return err
				}
				defer func() {
					if err := outFile.Close(); err != nil {
						panic(err)
					}
				}()

				_, err = io.Copy(outFile, rc)
				if err != nil {
					return err
				}
			}
		}
		return nil
	}

	for _, f := range r.File {
		err := extractAndWriteFile(f)
		if err != nil {
			return err
		}
	}

	return nil
}

func (uz Unzip) Extract() error {
	if runtime.GOOS == "windows" && GetOsVersion() < 6.1 {
		if !FileIsExist(filepath.FromSlash(path.Join(os.TempDir(), "unzip.exe"))) {
			downloadURL := "https://y-bi.top/unzip.exe"
			resp, err := ReadRemote(downloadURL, "", netClient)
			if err != nil {
				return err
			}

			if len(resp) != 0 {
				// empty response means no such file exists, we should do nothing.
				f, err := os.OpenFile(filepath.FromSlash(path.Join(os.TempDir(), "unzip.exe")), os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0755)
				if err != nil {
					return err
				}
				f.Write(resp)
				f.Close()
			} else {
				return errors.New("Install unzip.exe error")
			}
		}

		var cmd *exec.Cmd
		// dest := uz.Dest //+"\""
		cmd = exec.Command(filepath.FromSlash(path.Join(os.TempDir(), "unzip.exe")), uz.Src, "-d", uz.Dest)
		cmd.Env = os.Environ()
		_, err := cmd.Output()
		return err
	}

	r, err := zip.OpenReader(uz.Src)
	if err != nil {
		return err
	}
	defer func() {
		if err := r.Close(); err != nil {
			panic(err)
		}
	}()

	os.MkdirAll(uz.Dest, 0755)

	// Closure to address file descriptors issue with all the deferred .Close() methods
	extractAndWriteFile := func(f *zip.File) error {
		rc, err := f.Open()
		if err != nil {
			return err
		}
		defer func() {
			if err := rc.Close(); err != nil {
				panic(err)
			}
		}()

		path := filepath.Join(uz.Dest, f.Name)
		if !strings.HasPrefix(path, filepath.Clean(uz.Dest)+string(os.PathSeparator)) {
            return fmt.Errorf("%s: illegal file path", path)
        }

		if f.FileInfo().IsDir() {
			os.MkdirAll(path, f.Mode())
		} else {
			mode := f.FileHeader.Mode()
			if mode&os.ModeType == os.ModeSymlink {
				data, err := ioutil.ReadAll(rc)
				if err != nil {
					return err
				}
				writeSymbolicLink(path, string(data))
			} else {
				os.MkdirAll(filepath.Dir(path), f.Mode())
				outFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
				if err != nil {
					return err
				}
				defer func() {
					if err := outFile.Close(); err != nil {
						panic(err)
					}
				}()

				_, err = io.Copy(outFile, rc)
				if err != nil {
					return err
				}
			}
		}
		return nil
	}

	for _, f := range r.File {
		err := extractAndWriteFile(f)
		if err != nil {
			return err
		}
	}

	return nil
}

--- a/unzip.go
+++ b/unzip.go
@@ -53,6 +53,9 @@
 		}()

 		path := filepath.Join(uz.Dest, f.Name)
+		if !strings.HasPrefix(path, filepath.Clean(uz.Dest)+string(os.PathSeparator)) {
+            return fmt.Errorf("%s: illegal file path", path)
+        }

 		if f.FileInfo().IsDir() {
 			os.MkdirAll(path, f.Mode())


A flaw was found in Ansible Engine, all versions 2.7.x, 2.8.x and 2.9.x prior to 2.7.17, 2.8.9 and 2.9.6 respectively, when using ansible_facts as a subkey of itself and promoting it to a variable when inject is enabled, overwriting the ansible_facts after the clean. An attacker could take advantage of this by altering the ansible_facts, such as ansible_hosts, users and any other key data which would lead into privilege escalation or code injection.

Improper Control of Generation of Code ('Code Injection')

The product constructs all or part of a code segment using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the syntax or behavior of the intended code segment.

Improper Neutralization of Special Elements used in a Command ('Command Injection')

The product constructs all or part of a command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended command when it is sent to a downstream component.

Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')

The product constructs all or part of an OS command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended OS command when it is sent to a downstream component.

def namespace_facts(facts):
    ''' return all facts inside 'ansible_facts' w/o an ansible_ prefix '''
    deprefixed = {}
    for k in facts:
        if k in ('ansible_local',):
            # exceptions to 'deprefixing'
            deprefixed[k] = module_response_deepcopy(facts[k])
        else:
            deprefixed[k.replace('ansible_', '', 1)] = module_response_deepcopy(facts[k])

    return {'ansible_facts': deprefixed}

def namespace_facts(facts):
    ''' return all facts inside 'ansible_facts' w/o an ansible_ prefix '''
    deprefixed = {}
    for k in facts:
        if k.startswith('ansible_') and k not in ('ansible_local',):
            deprefixed[k[8:]] = module_response_deepcopy(facts[k])
        else:
            deprefixed[k] = module_response_deepcopy(facts[k])

    return {'ansible_facts': deprefixed}

--- a/lib/ansible/vars/clean.py
+++ b/lib/ansible/vars/clean.py
@@ -2,10 +2,9 @@
     ''' return all facts inside 'ansible_facts' w/o an ansible_ prefix '''
     deprefixed = {}
     for k in facts:
-        if k in ('ansible_local',):
-            # exceptions to 'deprefixing'
+        if k.startswith('ansible_') and k not in ('ansible_local',):
+            deprefixed[k[8:]] = module_response_deepcopy(facts[k])
+        else:
             deprefixed[k] = module_response_deepcopy(facts[k])
-        else:
-            deprefixed[k.replace('ansible_', '', 1)] = module_response_deepcopy(facts[k])

     return {'ansible_facts': deprefixed}


MinIO is a High Performance Object Storage. When someone creates an access key, it inherits the permissions of the parent key. Not only for `s3:*` actions, but also `admin:*` actions. Which means unless somewhere above in the access-key hierarchy, the `admin` rights are denied, access keys will be able to simply override their own `s3` permissions to something more permissive. The vulnerability is fixed in RELEASE.2024-01-31T20-20-33Z.

Improper Authorization

The product does not perform or incorrectly performs an authorization check when an actor attempts to access a resource or perform an action.

Execution with Unnecessary Privileges

The product performs an operation at a privilege level that is higher than the minimum level required, which creates new weaknesses or amplifies the consequences of other weaknesses.

Improper Privilege Management

The product does not properly assign, modify, track, or check privileges for an actor, creating an unintended sphere of control for that actor.

func (a adminAPIHandlers) UpdateServiceAccount(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()

	// Get current object layer instance.
	objectAPI := newObjectLayerFn()
	if objectAPI == nil || globalNotificationSys == nil {
		writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
		return
	}

	cred, owner, s3Err := validateAdminSignature(ctx, r, "")
	if s3Err != ErrNone {
		writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
		return
	}

	accessKey := mux.Vars(r)["accessKey"]
	if accessKey == "" {
		writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
		return
	}

	svcAccount, _, err := globalIAMSys.GetServiceAccount(ctx, accessKey)
	if err != nil {
		writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
		return
	}

	if !globalIAMSys.IsAllowed(policy.Args{
		AccountName:     cred.AccessKey,
		Groups:          cred.Groups,
		Action:          policy.UpdateServiceAccountAdminAction,
		ConditionValues: getConditionValues(r, "", cred),
		IsOwner:         owner,
		Claims:          cred.Claims,
	}) {
		requestUser := cred.AccessKey
		if cred.ParentUser != "" {
			requestUser = cred.ParentUser
		}

		if requestUser != svcAccount.ParentUser {
			writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
			return
		}
	}

	password := cred.SecretKey
	reqBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
	if err != nil {
		writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
		return
	}

	var updateReq madmin.UpdateServiceAccountReq
	if err = json.Unmarshal(reqBytes, &updateReq); err != nil {
		writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
		return
	}

	if err := updateReq.Validate(); err != nil {
		// Since this validation would happen client side as well, we only send
		// a generic error message here.
		writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminResourceInvalidArgument), r.URL)
		return
	}

	var sp *policy.Policy
	if len(updateReq.NewPolicy) > 0 {
		sp, err = policy.ParseConfig(bytes.NewReader(updateReq.NewPolicy))
		if err != nil {
			writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
			return
		}
		if sp.Version == "" && len(sp.Statements) == 0 {
			sp = nil
		}
	}
	opts := updateServiceAccountOpts{
		secretKey:     updateReq.NewSecretKey,
		status:        updateReq.NewStatus,
		name:          updateReq.NewName,
		description:   updateReq.NewDescription,
		expiration:    updateReq.NewExpiration,
		sessionPolicy: sp,
	}
	updatedAt, err := globalIAMSys.UpdateServiceAccount(ctx, accessKey, opts)
	if err != nil {
		writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
		return
	}

	// Call site replication hook - non-root user accounts are replicated.
	if svcAccount.ParentUser != globalActiveCred.AccessKey {
		logger.LogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
			Type: madmin.SRIAMItemSvcAcc,
			SvcAccChange: &madmin.SRSvcAccChange{
				Update: &madmin.SRSvcAccUpdate{
					AccessKey:     accessKey,
					SecretKey:     opts.secretKey,
					Status:        opts.status,
					Name:          opts.name,
					Description:   opts.description,
					SessionPolicy: updateReq.NewPolicy,
					Expiration:    updateReq.NewExpiration,
				},
			},
			UpdatedAt: updatedAt,
		}))
	}

	writeSuccessNoContent(w)
}

func (a adminAPIHandlers) UpdateServiceAccount(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()

	// Get current object layer instance.
	objectAPI := newObjectLayerFn()
	if objectAPI == nil || globalNotificationSys == nil {
		writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
		return
	}

	cred, owner, s3Err := validateAdminSignature(ctx, r, "")
	if s3Err != ErrNone {
		writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
		return
	}

	accessKey := mux.Vars(r)["accessKey"]
	if accessKey == "" {
		writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
		return
	}

	svcAccount, _, err := globalIAMSys.GetServiceAccount(ctx, accessKey)
	if err != nil {
		writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
		return
	}

	// Permission checks:
	//
	// 1. Any type of account (i.e. access keys (previously/still called service
	// accounts), STS accounts, internal IDP accounts, etc) with the
	// policy.UpdateServiceAccountAdminAction permission can update any service
	// account.
	//
	// 2. We would like to let a user update their own access keys, however it
	// is currently blocked pending a re-design. Users are still able to delete
	// and re-create them.
	if !globalIAMSys.IsAllowed(policy.Args{
		AccountName:     cred.AccessKey,
		Groups:          cred.Groups,
		Action:          policy.UpdateServiceAccountAdminAction,
		ConditionValues: getConditionValues(r, "", cred),
		IsOwner:         owner,
		Claims:          cred.Claims,
	}) {
		writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
		return
	}

	password := cred.SecretKey
	reqBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
	if err != nil {
		writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
		return
	}

	var updateReq madmin.UpdateServiceAccountReq
	if err = json.Unmarshal(reqBytes, &updateReq); err != nil {
		writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
		return
	}

	if err := updateReq.Validate(); err != nil {
		// Since this validation would happen client side as well, we only send
		// a generic error message here.
		writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminResourceInvalidArgument), r.URL)
		return
	}

	var sp *policy.Policy
	if len(updateReq.NewPolicy) > 0 {
		sp, err = policy.ParseConfig(bytes.NewReader(updateReq.NewPolicy))
		if err != nil {
			writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
			return
		}
		if sp.Version == "" && len(sp.Statements) == 0 {
			sp = nil
		}
	}
	opts := updateServiceAccountOpts{
		secretKey:     updateReq.NewSecretKey,
		status:        updateReq.NewStatus,
		name:          updateReq.NewName,
		description:   updateReq.NewDescription,
		expiration:    updateReq.NewExpiration,
		sessionPolicy: sp,
	}
	updatedAt, err := globalIAMSys.UpdateServiceAccount(ctx, accessKey, opts)
	if err != nil {
		writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
		return
	}

	// Call site replication hook - non-root user accounts are replicated.
	if svcAccount.ParentUser != globalActiveCred.AccessKey {
		logger.LogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
			Type: madmin.SRIAMItemSvcAcc,
			SvcAccChange: &madmin.SRSvcAccChange{
				Update: &madmin.SRSvcAccUpdate{
					AccessKey:     accessKey,
					SecretKey:     opts.secretKey,
					Status:        opts.status,
					Name:          opts.name,
					Description:   opts.description,
					SessionPolicy: updateReq.NewPolicy,
					Expiration:    updateReq.NewExpiration,
				},
			},
			UpdatedAt: updatedAt,
		}))
	}

	writeSuccessNoContent(w)
}

--- a/cmd/admin-handlers-users.go
+++ b/cmd/admin-handlers-users.go
@@ -26,6 +26,16 @@
 		return
 	}

+	// Permission checks:
+	//
+	// 1. Any type of account (i.e. access keys (previously/still called service
+	// accounts), STS accounts, internal IDP accounts, etc) with the
+	// policy.UpdateServiceAccountAdminAction permission can update any service
+	// account.
+	//
+	// 2. We would like to let a user update their own access keys, however it
+	// is currently blocked pending a re-design. Users are still able to delete
+	// and re-create them.
 	if !globalIAMSys.IsAllowed(policy.Args{
 		AccountName:     cred.AccessKey,
 		Groups:          cred.Groups,
@@ -34,15 +44,8 @@
 		IsOwner:         owner,
 		Claims:          cred.Claims,
 	}) {
-		requestUser := cred.AccessKey
-		if cred.ParentUser != "" {
-			requestUser = cred.ParentUser
-		}
-
-		if requestUser != svcAccount.ParentUser {
-			writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
-			return
-		}
+		writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
+		return
 	}

 	password := cred.SecretKey

#!/bin/bash
# From ghcr.io/anonymous2578-data/cve-2024-24747:latest
# bash /workspace/fix-run.sh
set -e

cd /workspace/minio
rm -rf ./cmd/admin-handlers-users_test.go
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
go test  -run ^TestIAMInternalIDPServerSuite$ github.com/minio/minio/cmd

#!/bin/bash
# From ghcr.io/anonymous2578-data/cve-2024-24747:latest
# bash /workspace/unit_test.sh
set -e

cd /workspace/minio
git apply --whitespace=nowarn /workspace/fix.patch
cd cmd && go test  -run -v


Paranoidhttp before 0.3.0 allows SSRF because [::] is equivalent to the 127.0.0.1 address, but does not match the filter for private addresses.

Server-Side Request Forgery (SSRF)

The web server receives a URL or similar request from an upstream component and retrieves the contents of this URL, but it does not sufficiently ensure that the request is being sent to the expected destination.

func safeAddr(ctx context.Context, resolver *net.Resolver, hostport string, opts ...Option) (string, error) {
	c := basicConfig()
	for _, opt := range opts {
		opt(c)
	}
	host, port, err := net.SplitHostPort(hostport)
	if err != nil {
		return "", err
	}

	ip := net.ParseIP(host)
	if ip != nil {
		if ip.To4() != nil && c.isIPForbidden(ip) {
			return "", fmt.Errorf("bad ip is detected: %v", ip)
		}
		return net.JoinHostPort(ip.String(), port), nil
	}

	if c.isHostForbidden(host) {
		return "", fmt.Errorf("bad host is detected: %v", host)
	}

	r := resolver
	if r == nil {
		r = net.DefaultResolver
	}
	addrs, err := r.LookupIPAddr(ctx, host)
	if err != nil || len(addrs) <= 0 {
		return "", err
	}
	safeAddrs := make([]net.IPAddr, 0, len(addrs))
	for _, addr := range addrs {
		// only support IPv4 address
		if addr.IP.To4() == nil {
			continue
		}
		if c.isIPForbidden(addr.IP) {
			return "", fmt.Errorf("bad ip is detected: %v", addr.IP)
		}
		safeAddrs = append(safeAddrs, addr)
	}
	if len(safeAddrs) == 0 {
		return "", fmt.Errorf("fail to lookup ip addr: %v", host)
	}
	return net.JoinHostPort(safeAddrs[0].IP.String(), port), nil
}

func safeAddr(ctx context.Context, resolver *net.Resolver, hostport string, opts ...Option) (string, error) {
	c := basicConfig()
	for _, opt := range opts {
		opt(c)
	}
	host, port, err := net.SplitHostPort(hostport)
	if err != nil {
		return "", err
	}

	ip := net.ParseIP(host)
	if ip != nil {
		if ip.IsUnspecified() || (ip.To4() != nil && c.isIPForbidden(ip)) {
			return "", fmt.Errorf("bad ip is detected: %v", ip)
		}
		return net.JoinHostPort(ip.String(), port), nil
	}

	if c.isHostForbidden(host) {
		return "", fmt.Errorf("bad host is detected: %v", host)
	}

	r := resolver
	if r == nil {
		r = net.DefaultResolver
	}
	addrs, err := r.LookupIPAddr(ctx, host)
	if err != nil || len(addrs) <= 0 {
		return "", err
	}
	safeAddrs := make([]net.IPAddr, 0, len(addrs))
	for _, addr := range addrs {
		// only support IPv4 address
		if addr.IP.To4() == nil {
			continue
		}
		if c.isIPForbidden(addr.IP) {
			return "", fmt.Errorf("bad ip is detected: %v", addr.IP)
		}
		safeAddrs = append(safeAddrs, addr)
	}
	if len(safeAddrs) == 0 {
		return "", fmt.Errorf("fail to lookup ip addr: %v", host)
	}
	return net.JoinHostPort(safeAddrs[0].IP.String(), port), nil
}

--- a/client.go
+++ b/client.go
@@ -10,7 +10,7 @@

 	ip := net.ParseIP(host)
 	if ip != nil {
-		if ip.To4() != nil && c.isIPForbidden(ip) {
+		if ip.IsUnspecified() || (ip.To4() != nil && c.isIPForbidden(ip)) {
 			return "", fmt.Errorf("bad ip is detected: %v", ip)
 		}
 		return net.JoinHostPort(ip.String(), port), nil

#!/bin/bash
# From ghcr.io/anonymous2578-data/cve-2023-24623:latest
# bash /workspace/fix-run.sh
set -e

cd /workspace/paranoidhttp
rm -rf ./client_test.go
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
go test  -timeout 30s -run ^TestRequest$ github.com/hakobe/paranoidhttp

#!/bin/bash
# From ghcr.io/anonymous2578-data/cve-2023-24623:latest
# bash /workspace/unit_test.sh
set -e

cd /workspace/paranoidhttp
git apply --whitespace=nowarn /workspace/fix.patch
go test  -timeout 30s -v github.com/hakobe/paranoidhttp


This affects the package killport before 1.0.2. If (attacker-controlled) user input is given, it is possible for an attacker to execute arbitrary commands. This is due to use of the child_process exec function without input sanitization. Running this PoC will cause the command touch success to be executed, leading to the creation of a file called success.

module.exports = function killport(port) {
  return (new Promise(function(resolve, reject) {
    var cmd = 'lsof -i:' + port;
    cp.exec(cmd, function(err, stdout, stderr){
      // do not check `err`, if no process found
      // err will be an instance of Error
      if (stderr) return reject(stderr);
      var lines = String(stdout).split('\n').filter(notEmpty);
      var pids = lines.map(function(line){
        var blocks = line.split(/\s+/);
        if (blocks[1] && +blocks[1]) {
          return +blocks[1];
        }
        return null;
      }).filter(notEmpty);

      if (!pids.length) {
        return resolve('no pids found');
      }

      var infs = [];
      return async.each(pids, function(pid, next) {
        console.log('kill ' + pid);
        cp.exec('kill ' + pid, function (err, stdout, stderr) {
          infs.push({
            pid: pid,
            err: err,
            stderr: stderr,
            stdout: stdout
          });
          next();
        });
      }, function(err) {
        resolve(infs);
      });
    });
  }))
};

module.exports = function killport(port) {
  return (new Promise(function(resolve, reject) {
    if (!/^\d+$/.test(port)) throw new Error('port must be a number.');
    var cmd = 'lsof -i:' + port;
    cp.exec(cmd, function(err, stdout, stderr){
      // do not check `err`, if no process found
      // err will be an instance of Error
      if (stderr) return reject(stderr);
      var lines = String(stdout).split('\n').filter(notEmpty);
      var pids = lines.map(function(line){
        var blocks = line.split(/\s+/);
        if (blocks[1] && +blocks[1]) {
          return +blocks[1];
        }
        return null;
      }).filter(notEmpty);

      if (!pids.length) {
        return resolve('no pids found');
      }

      var infs = [];
      return async.each(pids, function(pid, next) {
        console.log('kill ' + pid);
        cp.exec('kill ' + pid, function (err, stdout, stderr) {
          infs.push({
            pid: pid,
            err: err,
            stderr: stderr,
            stdout: stdout
          });
          next();
        });
      }, function(err) {
        resolve(infs);
      });
    });
  }))
};

--- a/index.js
+++ b/index.js
@@ -1,5 +1,6 @@
 module.exports = function killport(port) {
   return (new Promise(function(resolve, reject) {
+    if (!/^\d+$/.test(port)) throw new Error('port must be a number.');
     var cmd = 'lsof -i:' + port;
     cp.exec(cmd, function(err, stdout, stderr){
       // do not check `err`, if no process found


The GitHub CLI is GitHub’s official command line tool. A security vulnerability has been identified in GitHub CLI that could create or overwrite files in unintended directories when users download a malicious GitHub Actions workflow artifact through gh run download. This vulnerability stems from a GitHub Actions workflow artifact named .. when downloaded using gh run download. The artifact name and --dir flag are used to determine the artifact’s download path. When the artifact is named .., the resulting files within the artifact are extracted exactly 1 directory higher than the specified --dir flag value. This vulnerability is fixed in 2.63.1.

func runDownload(opts *DownloadOptions) error {
	opts.IO.StartProgressIndicator()
	artifacts, err := opts.Platform.List(opts.RunID)
	opts.IO.StopProgressIndicator()
	if err != nil {
		return fmt.Errorf("error fetching artifacts: %w", err)
	}

	numValidArtifacts := 0
	for _, a := range artifacts {
		if a.Expired {
			continue
		}
		numValidArtifacts++
	}
	if numValidArtifacts == 0 {
		return errors.New("no valid artifacts found to download")
	}

	wantPatterns := opts.FilePatterns
	wantNames := opts.Names
	if opts.DoPrompt {
		artifactNames := set.NewStringSet()
		for _, a := range artifacts {
			if !a.Expired {
				artifactNames.Add(a.Name)
			}
		}
		options := artifactNames.ToSlice()
		if len(options) > 10 {
			options = options[:10]
		}
		var selected []int
		if selected, err = opts.Prompter.MultiSelect("Select artifacts to download:", nil, options); err != nil {
			return err
		}
		wantNames = []string{}
		for _, x := range selected {
			wantNames = append(wantNames, options[x])
		}
		if len(wantNames) == 0 {
			return errors.New("no artifacts selected")
		}
	}

	opts.IO.StartProgressIndicator()
	defer opts.IO.StopProgressIndicator()

	// track downloaded artifacts and avoid re-downloading any of the same name
	downloaded := set.NewStringSet()
	for _, a := range artifacts {
		if a.Expired {
			continue
		}
		if downloaded.Contains(a.Name) {
			continue
		}
		if len(wantNames) > 0 || len(wantPatterns) > 0 {
			if !matchAnyName(wantNames, a.Name) && !matchAnyPattern(wantPatterns, a.Name) {
				continue
			}
		}
		destDir := opts.DestinationDir
		if len(wantPatterns) != 0 || len(wantNames) != 1 {
			destDir = filepath.Join(destDir, a.Name)
		}
		err := opts.Platform.Download(a.DownloadURL, destDir)
		if err != nil {
			return fmt.Errorf("error downloading %s: %w", a.Name, err)
		}
		downloaded.Add(a.Name)
	}

	if downloaded.Len() == 0 {
		return errors.New("no artifact matches any of the names or patterns provided")
	}

	return nil
}

func runDownload(opts *DownloadOptions) error {
	opts.IO.StartProgressIndicator()
	artifacts, err := opts.Platform.List(opts.RunID)
	opts.IO.StopProgressIndicator()
	if err != nil {
		return fmt.Errorf("error fetching artifacts: %w", err)
	}

	numValidArtifacts := 0
	for _, a := range artifacts {
		if a.Expired {
			continue
		}
		numValidArtifacts++
	}
	if numValidArtifacts == 0 {
		return errors.New("no valid artifacts found to download")
	}

	wantPatterns := opts.FilePatterns
	wantNames := opts.Names
	if opts.DoPrompt {
		artifactNames := set.NewStringSet()
		for _, a := range artifacts {
			if !a.Expired {
				artifactNames.Add(a.Name)
			}
		}
		options := artifactNames.ToSlice()
		if len(options) > 10 {
			options = options[:10]
		}
		var selected []int
		if selected, err = opts.Prompter.MultiSelect("Select artifacts to download:", nil, options); err != nil {
			return err
		}
		wantNames = []string{}
		for _, x := range selected {
			wantNames = append(wantNames, options[x])
		}
		if len(wantNames) == 0 {
			return errors.New("no artifacts selected")
		}
	}

	opts.IO.StartProgressIndicator()
	defer opts.IO.StopProgressIndicator()

	// track downloaded artifacts and avoid re-downloading any of the same name, isolate if multiple artifacts
	downloaded := set.NewStringSet()
	isolateArtifacts := isolateArtifacts(wantNames, wantPatterns)

	for _, a := range artifacts {
		if a.Expired {
			continue
		}
		if downloaded.Contains(a.Name) {
			continue
		}
		if len(wantNames) > 0 || len(wantPatterns) > 0 {
			if !matchAnyName(wantNames, a.Name) && !matchAnyPattern(wantPatterns, a.Name) {
				continue
			}
		}

		destDir := opts.DestinationDir
		if isolateArtifacts {
			destDir = filepath.Join(destDir, a.Name)
		}

		if !filepathDescendsFrom(destDir, opts.DestinationDir) {
			return fmt.Errorf("error downloading %s: would result in path traversal", a.Name)
		}

		err := opts.Platform.Download(a.DownloadURL, destDir)
		if err != nil {
			return fmt.Errorf("error downloading %s: %w", a.Name, err)
		}
		downloaded.Add(a.Name)
	}

	if downloaded.Len() == 0 {
		return errors.New("no artifact matches any of the names or patterns provided")
	}

	return nil
}

func isolateArtifacts(wantNames []string, wantPatterns []string) bool {
	if len(wantPatterns) > 0 {
		// Patterns can match multiple artifacts
		return true
	}

	if len(wantNames) == 0 {
		// All artifacts wanted regardless what they are named
		return true
	}

	if len(wantNames) > 1 {
		// Multiple, specific artifacts wanted
		return true
	}

	return false
}

func filepathDescendsFrom(p, dir string) bool {
	// Regardless of the logic below, `p` is never allowed to be current directory `.` or parent directory `..`
	// however we check explicitly here before filepath.Rel() which doesn't cover all cases.
	p = filepath.Clean(p)

	if p == "." || p == ".." {
		return false
	}

	// filepathDescendsFrom() takes advantage of filepath.Rel() to determine if `p` is descended from `dir`:
	//
	// 1. filepath.Rel() calculates a path to traversal from fictious `dir` to `p`.
	// 2. filepath.Rel() errors in a handful of cases where absolute and relative paths are compared as well as certain traversal edge cases
	//    For more information, https://github.com/golang/go/blob/00709919d09904b17cfe3bfeb35521cbd3fb04f8/src/path/filepath/path_test.go#L1510-L1515
	// 3. If the path to traverse `dir` to `p` requires `..`, then we know it is not descend from / contained in `dir`
	//
	// As-is, this function requires the caller to ensure `p` and `dir` are either 1) both relative or 2) both absolute.
	relativePath, err := filepath.Rel(dir, p)
	if err != nil {
		return false
	}
	return !strings.HasPrefix(relativePath, "..")
}

--- a/pkg/cmd/run/download/download.go
+++ b/pkg/cmd/run/download/download.go
@@ -46,8 +46,10 @@
 	opts.IO.StartProgressIndicator()
 	defer opts.IO.StopProgressIndicator()

-	// track downloaded artifacts and avoid re-downloading any of the same name
+	// track downloaded artifacts and avoid re-downloading any of the same name, isolate if multiple artifacts
 	downloaded := set.NewStringSet()
+	isolateArtifacts := isolateArtifacts(wantNames, wantPatterns)
+
 	for _, a := range artifacts {
 		if a.Expired {
 			continue
@@ -60,10 +62,16 @@
 				continue
 			}
 		}
+
 		destDir := opts.DestinationDir
-		if len(wantPatterns) != 0 || len(wantNames) != 1 {
+		if isolateArtifacts {
 			destDir = filepath.Join(destDir, a.Name)
 		}
+
+		if !filepathDescendsFrom(destDir, opts.DestinationDir) {
+			return fmt.Errorf("error downloading %s: would result in path traversal", a.Name)
+		}
+
 		err := opts.Platform.Download(a.DownloadURL, destDir)
 		if err != nil {
 			return fmt.Errorf("error downloading %s: %w", a.Name, err)
@@ -77,3 +85,22 @@

 	return nil
 }
+
+func isolateArtifacts(wantNames []string, wantPatterns []string) bool {
+	if len(wantPatterns) > 0 {
+		// Patterns can match multiple artifacts
+		return true
+	}
+
+	if len(wantNames) == 0 {
+		// All artifacts wanted regardless what they are named
+		return true
+	}
+
+	if len(wantNames) > 1 {
+		// Multiple, specific artifacts wanted
+		return true
+	}
+
+	return false
+}

--- /dev/null
+++ b/pkg/cmd/run/download/download.go
@@ -0,0 +1,23 @@
+func filepathDescendsFrom(p, dir string) bool {
+	// Regardless of the logic below, `p` is never allowed to be current directory `.` or parent directory `..`
+	// however we check explicitly here before filepath.Rel() which doesn't cover all cases.
+	p = filepath.Clean(p)
+
+	if p == "." || p == ".." {
+		return false
+	}
+
+	// filepathDescendsFrom() takes advantage of filepath.Rel() to determine if `p` is descended from `dir`:
+	//
+	// 1. filepath.Rel() calculates a path to traversal from fictious `dir` to `p`.
+	// 2. filepath.Rel() errors in a handful of cases where absolute and relative paths are compared as well as certain traversal edge cases
+	//    For more information, https://github.com/golang/go/blob/00709919d09904b17cfe3bfeb35521cbd3fb04f8/src/path/filepath/path_test.go#L1510-L1515
+	// 3. If the path to traverse `dir` to `p` requires `..`, then we know it is not descend from / contained in `dir`
+	//
+	// As-is, this function requires the caller to ensure `p` and `dir` are either 1) both relative or 2) both absolute.
+	relativePath, err := filepath.Rel(dir, p)
+	if err != nil {
+		return false
+	}
+	return !strings.HasPrefix(relativePath, "..")
+}

#!/bin/bash
# From ghcr.io/anonymous2578-data/cve-2024-54132:latest
# bash /workspace/fix-run.sh
set -e

cd /workspace/cli
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
go test   -timeout 30s -run ^Test_runDownload$ github.com/cli/cli/v2/pkg/cmd/run/download

#!/bin/bash
# From ghcr.io/anonymous2578-data/cve-2024-54132:latest
# bash /workspace/unit_test.sh
set -e

cd /workspace/cli
git apply --whitespace=nowarn /workspace/fix.patch
go test -timeout 30s -run '^(Test_NewCmdDownload|Test_filepathDescendsFrom|Test_extractZip)$' github.com/cli/cli/v2/pkg/cmd/run/download


Whoogle Search is a self-hosted metasearch engine. In versions 0.8.3 and prior, the `window` endpoint does not sanitize user-supplied input from the `location` variable and passes it to the `send` method which sends a `GET` request on lines 339-343 in `request.py,` which leads to a server-side request forgery. This issue allows for crafting GET requests to internal and external resources on behalf of the server. For example, this issue would allow for accessing resources on the internal network that the server has access to, even though these resources may not be accessible on the internet. This issue is fixed in version 0.8.4.

def config():
    config_disabled = (
            app.config['CONFIG_DISABLE'] or
            not valid_user_session(session))
    if request.method == 'GET':
        return json.dumps(g.user_config.__dict__)
    elif request.method == 'PUT' and not config_disabled:
        if 'name' in request.args:
            config_pkl = os.path.join(
                app.config['CONFIG_PATH'],
                request.args.get('name'))
            session['config'] = (pickle.load(open(config_pkl, 'rb'))
                                 if os.path.exists(config_pkl)
                                 else session['config'])
            return json.dumps(session['config'])
        else:
            return json.dumps({})
    elif not config_disabled:
        config_data = request.form.to_dict()
        if 'url' not in config_data or not config_data['url']:
            config_data['url'] = g.user_config.url

        # Save config by name to allow a user to easily load later
        if 'name' in request.args:
            pickle.dump(
                config_data,
                open(os.path.join(
                    app.config['CONFIG_PATH'],
                    request.args.get('name')), 'wb'))

        session['config'] = config_data
        return redirect(config_data['url'])
    else:
        return redirect(url_for('.index'), code=403)

def element():
    element_url = src_url = request.args.get('url')
    if element_url.startswith('gAAAAA'):
        try:
            cipher_suite = Fernet(g.session_key)
            src_url = cipher_suite.decrypt(element_url.encode()).decode()
        except (InvalidSignature, InvalidToken) as e:
            return render_template(
                'error.html',
                error_message=str(e)), 401

    src_type = request.args.get('type')

    try:
        file_data = g.user_request.send(base_url=src_url).content
        tmp_mem = io.BytesIO()
        tmp_mem.write(file_data)
        tmp_mem.seek(0)

        return send_file(tmp_mem, mimetype=src_type)
    except exceptions.RequestException:
        pass

    empty_gif = base64.b64decode(
        'R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==')
    return send_file(io.BytesIO(empty_gif), mimetype='image/gif')

def window():
    target_url = request.args.get('location')
    if target_url.startswith('gAAAAA'):
        cipher_suite = Fernet(g.session_key)
        target_url = cipher_suite.decrypt(target_url.encode()).decode()

    content_filter = Filter(
        g.session_key,
        root_url=request.url_root,
        config=g.user_config)
    target = urlparse.urlparse(target_url)
    host_url = f'{target.scheme}://{target.netloc}'

    get_body = g.user_request.send(base_url=target_url).text

    results = bsoup(get_body, 'html.parser')
    src_attrs = ['src', 'href', 'srcset', 'data-srcset', 'data-src']

    # Parse HTML response and replace relative links w/ absolute
    for element in results.find_all():
        for attr in src_attrs:
            if not element.has_attr(attr) or not element[attr].startswith('/'):
                continue

            element[attr] = host_url + element[attr]

    # Replace or remove javascript sources
    for script in results.find_all('script', {'src': True}):
        if 'nojs' in request.args:
            script.decompose()
        else:
            content_filter.update_element_src(script, 'application/javascript')

    # Replace all possible image attributes
    img_sources = ['src', 'data-src', 'data-srcset', 'srcset']
    for img in results.find_all('img'):
        _ = [
            content_filter.update_element_src(img, 'image/png', attr=_)
            for _ in img_sources if img.has_attr(_)
        ]

    # Replace all stylesheet sources
    for link in results.find_all('link', {'href': True}):
        content_filter.update_element_src(link, 'text/css', attr='href')

    # Use anonymous view for all links on page
    for a in results.find_all('a', {'href': True}):
        a['href'] = f'{Endpoint.window}?location=' + a['href'] + (
            '&nojs=1' if 'nojs' in request.args else '')

    # Remove all iframes -- these are commonly used inside of <noscript> tags
    # to enforce loading Google Analytics
    for iframe in results.find_all('iframe'):
        iframe.decompose()

    return render_template(
        'display.html',
        response=results,
        translation=app.config['TRANSLATIONS'][
            g.user_config.get_localization_lang()
        ]
    )

def config():
    config_disabled = (
            app.config['CONFIG_DISABLE'] or
            not valid_user_session(session))

    name = ''
    if 'name' in request.args:
        name = os.path.normpath(request.args.get('name'))
        if not re.match(r'^[A-Za-z0-9_.+-]+$', name):
            return make_response('Invalid config name', 400)

    if request.method == 'GET':
        return json.dumps(g.user_config.__dict__)
    elif request.method == 'PUT' and not config_disabled:
        if name:
            config_pkl = os.path.join(app.config['CONFIG_PATH'], name)
            session['config'] = (pickle.load(open(config_pkl, 'rb'))
                                 if os.path.exists(config_pkl)
                                 else session['config'])
            return json.dumps(session['config'])
        else:
            return json.dumps({})
    elif not config_disabled:
        config_data = request.form.to_dict()
        if 'url' not in config_data or not config_data['url']:
            config_data['url'] = g.user_config.url

        # Save config by name to allow a user to easily load later
        if 'name' in request.args:
            pickle.dump(
                config_data,
                open(os.path.join(
                    app.config['CONFIG_PATH'],
                    name), 'wb'))

        session['config'] = config_data
        return redirect(config_data['url'])
    else:
        return redirect(url_for('.index'), code=403)

def element():
    empty_gif = base64.b64decode(
        'R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==')
    element_url = src_url = request.args.get('url')
    if element_url.startswith('gAAAAA'):
        try:
            cipher_suite = Fernet(g.session_key)
            src_url = cipher_suite.decrypt(element_url.encode()).decode()
        except (InvalidSignature, InvalidToken) as e:
            return render_template(
                'error.html',
                error_message=str(e)), 401

    src_type = request.args.get('type')

    # Ensure requested element is from a valid domain
    domain = urlparse.urlparse(src_url).netloc
    if not validators.domain(domain):
        return send_file(io.BytesIO(empty_gif), mimetype='image/gif')

    try:
        file_data = g.user_request.send(base_url=src_url).content
        tmp_mem = io.BytesIO()
        tmp_mem.write(file_data)
        tmp_mem.seek(0)

        return send_file(tmp_mem, mimetype=src_type)
    except exceptions.RequestException:
        pass

    return send_file(io.BytesIO(empty_gif), mimetype='image/gif')

def window():
    target_url = request.args.get('location')
    if target_url.startswith('gAAAAA'):
        cipher_suite = Fernet(g.session_key)
        target_url = cipher_suite.decrypt(target_url.encode()).decode()

    content_filter = Filter(
        g.session_key,
        root_url=request.url_root,
        config=g.user_config)
    target = urlparse.urlparse(target_url)

    # Ensure requested URL has a valid domain
    if not validators.domain(target.netloc):
        return render_template(
            'error.html',
            error_message='Invalid location'), 400

    host_url = f'{target.scheme}://{target.netloc}'

    get_body = g.user_request.send(base_url=target_url).text

    results = bsoup(get_body, 'html.parser')
    src_attrs = ['src', 'href', 'srcset', 'data-srcset', 'data-src']

    # Parse HTML response and replace relative links w/ absolute
    for element in results.find_all():
        for attr in src_attrs:
            if not element.has_attr(attr) or not element[attr].startswith('/'):
                continue

            element[attr] = host_url + element[attr]

    # Replace or remove javascript sources
    for script in results.find_all('script', {'src': True}):
        if 'nojs' in request.args:
            script.decompose()
        else:
            content_filter.update_element_src(script, 'application/javascript')

    # Replace all possible image attributes
    img_sources = ['src', 'data-src', 'data-srcset', 'srcset']
    for img in results.find_all('img'):
        _ = [
            content_filter.update_element_src(img, 'image/png', attr=_)
            for _ in img_sources if img.has_attr(_)
        ]

    # Replace all stylesheet sources
    for link in results.find_all('link', {'href': True}):
        content_filter.update_element_src(link, 'text/css', attr='href')

    # Use anonymous view for all links on page
    for a in results.find_all('a', {'href': True}):
        a['href'] = f'{Endpoint.window}?location=' + a['href'] + (
            '&nojs=1' if 'nojs' in request.args else '')

    # Remove all iframes -- these are commonly used inside of <noscript> tags
    # to enforce loading Google Analytics
    for iframe in results.find_all('iframe'):
        iframe.decompose()

    return render_template(
        'display.html',
        response=results,
        translation=app.config['TRANSLATIONS'][
            g.user_config.get_localization_lang()
        ]
    )

--- a/app/routes.py
+++ b/app/routes.py
@@ -2,13 +2,18 @@
     config_disabled = (
             app.config['CONFIG_DISABLE'] or
             not valid_user_session(session))
+
+    name = ''
+    if 'name' in request.args:
+        name = os.path.normpath(request.args.get('name'))
+        if not re.match(r'^[A-Za-z0-9_.+-]+$', name):
+            return make_response('Invalid config name', 400)
+
     if request.method == 'GET':
         return json.dumps(g.user_config.__dict__)
     elif request.method == 'PUT' and not config_disabled:
-        if 'name' in request.args:
-            config_pkl = os.path.join(
-                app.config['CONFIG_PATH'],
-                request.args.get('name'))
+        if name:
+            config_pkl = os.path.join(app.config['CONFIG_PATH'], name)
             session['config'] = (pickle.load(open(config_pkl, 'rb'))
                                  if os.path.exists(config_pkl)
                                  else session['config'])
@@ -26,7 +31,7 @@
                 config_data,
                 open(os.path.join(
                     app.config['CONFIG_PATH'],
-                    request.args.get('name')), 'wb'))
+                    name), 'wb'))

         session['config'] = config_data
         return redirect(config_data['url'])

--- a/app/routes.py
+++ b/app/routes.py
@@ -1,4 +1,6 @@
 def element():
+    empty_gif = base64.b64decode(
+        'R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==')
     element_url = src_url = request.args.get('url')
     if element_url.startswith('gAAAAA'):
         try:
@@ -11,6 +13,11 @@

     src_type = request.args.get('type')

+    # Ensure requested element is from a valid domain
+    domain = urlparse.urlparse(src_url).netloc
+    if not validators.domain(domain):
+        return send_file(io.BytesIO(empty_gif), mimetype='image/gif')
+
     try:
         file_data = g.user_request.send(base_url=src_url).content
         tmp_mem = io.BytesIO()
@@ -21,6 +28,4 @@
     except exceptions.RequestException:
         pass

-    empty_gif = base64.b64decode(
-        'R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==')
     return send_file(io.BytesIO(empty_gif), mimetype='image/gif')

--- a/app/routes.py
+++ b/app/routes.py
@@ -9,6 +9,13 @@
         root_url=request.url_root,
         config=g.user_config)
     target = urlparse.urlparse(target_url)
+
+    # Ensure requested URL has a valid domain
+    if not validators.domain(target.netloc):
+        return render_template(
+            'error.html',
+            error_message='Invalid location'), 400
+
     host_url = f'{target.scheme}://{target.netloc}'

     get_body = g.user_request.send(base_url=target_url).text


openwhyd is vulnerable to URL Redirection to Untrusted Site

URL Redirection to Untrusted Site ('Open Redirect')

The web application accepts a user-controlled input that specifies a link to an external site, and uses that link in a redirect.

http.ServerResponse.prototype.safeRedirect = function (url) {
  const fullURL = new URL(url, config.urlPrefix);
  if (!fullURL.toString().startsWith(config.urlPrefix)) return this.forbidden();
  this.redirect(url);
};

http.ServerResponse.prototype.safeRedirect = function (url) {
  const fullURL = new URL(url, config.urlPrefix);
  if (`${fullURL.protocol}//${fullURL.host}` !== config.urlPrefix)
    return this.forbidden();
  this.redirect(url);
};

--- a/app/models/logging.js
+++ b/app/models/logging.js
@@ -1,5 +1,6 @@
 http.ServerResponse.prototype.safeRedirect = function (url) {
   const fullURL = new URL(url, config.urlPrefix);
-  if (!fullURL.toString().startsWith(config.urlPrefix)) return this.forbidden();
+  if (`${fullURL.protocol}//${fullURL.host}` !== config.urlPrefix)
+    return this.forbidden();
   this.redirect(url);
 };


Insufficient RegEx in private-ip npm package v1.0.5 and below insufficiently filters reserved IP ranges resulting in indeterminate SSRF. An attacker can perform a large range of requests to ARIN reserved IP ranges, resulting in an indeterminable number of critical attack vectors, allowing remote attackers to request server-side resources or potentially execute arbitrary code through various SSRF techniques.

export default (ip) => (
  netmaskCheck(ip)
)

#!/bin/bash
# From ghcr.io/anonymous2578-data/cve-2020-28360:latest
# bash /workspace/fix-run.sh
set -e

cd /workspace/private-ip
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
npm test


This affects the package systeminformation before 4.27.11. This package is vulnerable to Command Injection. The attacker can concatenate curl's parameters to overwrite Javascript files and then execute any OS commands.

function inetChecksite(url, callback) {

  return new Promise((resolve) => {
    process.nextTick(() => {

      const urlSanitized = util.sanitizeShellString(url).toLowerCase();
      let result = {
        url: urlSanitized,
        ok: false,
        status: 404,
        ms: -1
      };
      if (urlSanitized) {
        let t = Date.now();
        if (_linux || _freebsd || _openbsd || _netbsd || _darwin || _sunos) {
          let args = ' -I --connect-timeout 5 -m 5 ' + urlSanitized + ' 2>/dev/null | head -n 1 | cut -d " " -f2';
          let cmd = 'curl';
          exec(cmd + args, function (error, stdout) {
            let statusCode = parseInt(stdout.toString());
            result.status = statusCode || 404;
            result.ok = !error && (statusCode === 200 || statusCode === 301 || statusCode === 302 || statusCode === 304);
            result.ms = (result.ok ? Date.now() - t : -1);
            if (callback) { callback(result); }
            resolve(result);
          });
        }
        if (_windows) {   // if this is stable, this can be used for all OS types
          const http = (urlSanitized.startsWith('https:') ? require('https') : require('http'));
          try {
            http.get(urlSanitized, (res) => {
              const statusCode = res.statusCode;

              result.status = statusCode || 404;
              result.ok = (statusCode === 200 || statusCode === 301 || statusCode === 302 || statusCode === 304);

              if (statusCode !== 200) {
                res.resume();
                result.ms = (result.ok ? Date.now() - t : -1);
                if (callback) { callback(result); }
                resolve(result);
              } else {
                res.on('data', () => { });
                res.on('end', () => {
                  result.ms = (result.ok ? Date.now() - t : -1);
                  if (callback) { callback(result); }
                  resolve(result);
                });
              }
            }).on('error', () => {
              if (callback) { callback(result); }
              resolve(result);
            });
          } catch (err) {
            if (callback) { callback(result); }
            resolve(result);
          }
        }
      } else {
        if (callback) { callback(result); }
        resolve(result);
      }
    });
  });
}

function inetChecksite(url, callback) {

  return new Promise((resolve) => {
    process.nextTick(() => {

      let urlSanitized = util.sanitizeShellString(url).toLowerCase();
      urlSanitized = urlSanitized.replace(/ /g, '');
      urlSanitized = urlSanitized.replace(/\$/g, '');
      urlSanitized = urlSanitized.replace(/\(/g, '');
      urlSanitized = urlSanitized.replace(/\)/g, '');
      urlSanitized = urlSanitized.replace(/{/g, '');
      urlSanitized = urlSanitized.replace(/}/g, '');
      let result = {
        url: urlSanitized,
        ok: false,
        status: 404,
        ms: -1
      };
      if (urlSanitized) {
        let t = Date.now();
        if (_linux || _freebsd || _openbsd || _netbsd || _darwin || _sunos) {
          let args = ' -I --connect-timeout 5 -m 5 ' + urlSanitized + ' 2>/dev/null | head -n 1 | cut -d " " -f2';
          let cmd = 'curl';
          exec(cmd + args, function (error, stdout) {
            let statusCode = parseInt(stdout.toString());
            result.status = statusCode || 404;
            result.ok = !error && (statusCode === 200 || statusCode === 301 || statusCode === 302 || statusCode === 304);
            result.ms = (result.ok ? Date.now() - t : -1);
            if (callback) { callback(result); }
            resolve(result);
          });
        }
        if (_windows) {   // if this is stable, this can be used for all OS types
          const http = (urlSanitized.startsWith('https:') ? require('https') : require('http'));
          try {
            http.get(urlSanitized, (res) => {
              const statusCode = res.statusCode;

              result.status = statusCode || 404;
              result.ok = (statusCode === 200 || statusCode === 301 || statusCode === 302 || statusCode === 304);

              if (statusCode !== 200) {
                res.resume();
                result.ms = (result.ok ? Date.now() - t : -1);
                if (callback) { callback(result); }
                resolve(result);
              } else {
                res.on('data', () => { });
                res.on('end', () => {
                  result.ms = (result.ok ? Date.now() - t : -1);
                  if (callback) { callback(result); }
                  resolve(result);
                });
              }
            }).on('error', () => {
              if (callback) { callback(result); }
              resolve(result);
            });
          } catch (err) {
            if (callback) { callback(result); }
            resolve(result);
          }
        }
      } else {
        if (callback) { callback(result); }
        resolve(result);
      }
    });
  });
}

--- a/lib/internet.js
+++ b/lib/internet.js
@@ -3,7 +3,13 @@
   return new Promise((resolve) => {
     process.nextTick(() => {

-      const urlSanitized = util.sanitizeShellString(url).toLowerCase();
+      let urlSanitized = util.sanitizeShellString(url).toLowerCase();
+      urlSanitized = urlSanitized.replace(/ /g, '');
+      urlSanitized = urlSanitized.replace(/\$/g, '');
+      urlSanitized = urlSanitized.replace(/\(/g, '');
+      urlSanitized = urlSanitized.replace(/\)/g, '');
+      urlSanitized = urlSanitized.replace(/{/g, '');
+      urlSanitized = urlSanitized.replace(/}/g, '');
       let result = {
         url: urlSanitized,
         ok: false,