diff --git a/analysis/kernel/dashboard/Data/Field_Information/OLD/field_information_json.sh b/analysis/kernel/dashboard/Data/Field_Information/OLD/field_information_json.sh new file mode 100644 index 00000000..0aadd09a --- /dev/null +++ b/analysis/kernel/dashboard/Data/Field_Information/OLD/field_information_json.sh @@ -0,0 +1,56 @@ +#!/bin/bash + +if [ -z "$1" ] +then + echo -e '[ERROR] No vmlinux binarry supplied as an argument to a script!' + exit 1 +fi + +if ! command -v pahole 2>&1 >/dev/null +then + echo -e 'pahole could not be found' + exit 1 +elif ! command -v bpftool 2>&1 >/dev/null +then + echo -e 'bpftool could not be found' + exit 1 +elif ! command -v jq 2>&1 >/dev/null +then + echo -e 'jq could not be found' + exit 1 +elif ! command -v readelf 2>&1 >/dev/null +then + echo -e 'readelf could not be found' + exit 1 +fi + +(set -x; readelf -S $1 | grep -q debug > /dev/null) +if [ $? -eq 0 ] +then + echo -e '[OK] readelf. DWARF information is in kernel' +else + echo -e '[ERROR] no DWARF information found in kernel' + exit 1 +fi + +(set -x; pahole --btf_encode_detached btf-$1 $1) +if [ $? -eq 0 ] +then + echo -e '[OK] pahole. BTF dump' +else + exit 1 +fi + +(set -x; bpftool btf dump -j file btf-$1 > btf-$1.json) +if [ $? -eq 0 ] +then + echo -e '[OK] bpftool. BTF converted into json' +else + echo -e '[ERROR] bpftool. Something went wrong' + exit 1 +fi + +# Test querry +echo -e 'Test query results:' +(set -x; jq -c '.types[] | select(.kind=="STRUCT" and .size>64 and .size<=65)' btf-$1.json) + diff --git a/analysis/kernel/dashboard/Data/Field_Information/OLD/process-btf.py b/analysis/kernel/dashboard/Data/Field_Information/OLD/process-btf.py new file mode 100644 index 00000000..198a51b4 --- /dev/null +++ b/analysis/kernel/dashboard/Data/Field_Information/OLD/process-btf.py @@ -0,0 +1,96 @@ +#!/usr/bin/python3 + +import json +import math + +import sys + +def eprint(*args, **kwargs): + print(*args, file=sys.stderr, **kwargs) + +btf = json.load(open("btf-vmlinux.json")) + +types = {} +PTR_SIZE = 64 + +for type in btf['types']: + types[type['id']] = type + +def get_shallow(struct_name, struct_size, object, parent_type, prefix="", bits_offset=0): + shallow_types = [] + type = types[object['type_id']] + + while type['kind'] in ["TYPEDEF", "CONST", "VOLATILE"]: + type = types[type['type_id']] + + if type['kind'] == 'ARRAY' and type['nr_elems'] > 0: + object_bits_offset = object["bits_offset"] + bits_offset + for index in range(type['nr_elems']): + expanded_object = object.copy() + expanded_object['type_id'] = type['type_id'] + expanded_object['name'] += f"[{index}]" + deeper_types = get_shallow(struct_name, struct_size, expanded_object, parent_type, prefix, object_bits_offset) + sorted_deepest = sorted(deeper_types, key=lambda x: x['bits_end'], reverse=True) + deepest_bits_end = sorted_deepest[0]['bits_end'] if len(sorted_deepest) > 0 else 0 + object_bits_offset = 8 * math.ceil(deepest_bits_end / 8) + shallow_types += deeper_types + elif type['kind'] == 'STRUCT' or type['kind'] == 'UNION': + for idx, member in enumerate(type['members']): + if type['kind'] == 'UNION': + new_prefix = prefix + "/*" + str(idx) + ":" + object['name'] + "*/" + else: + new_prefix = prefix + object['name'] + "." + deeper_types = get_shallow(struct_name, struct_size, member, type['name'], new_prefix, bits_offset + object["bits_offset"]) + shallow_types += deeper_types + else: + kind = type['kind'] + is_flex = False + if type['kind'] == "PTR": + nr_bits = PTR_SIZE + bits_end = bits_offset + object["bits_offset"] + nr_bits + elif type['kind'] == "ENUM" or type['kind'] == "ENUM64": + nr_bits = type['size'] * 8 + bits_end = bits_offset + object["bits_offset"] + nr_bits + elif type['kind'] == "ARRAY": + expanded_object = object.copy() + expanded_object['type_id'] = type['type_id'] + expanded_object['bits_offset'] = 0 + deeper_types = get_shallow(struct_name, struct_size, expanded_object, type['name'], prefix, 0) + sorted_deepest = sorted(deeper_types, key=lambda x: x['bits_end'], reverse=True) + nr_bits = sorted_deepest[0]['bits_end'] + while type['kind'] in ["TYPEDEF", "CONST", "VOLATILE", "ARRAY"]: + type = types[type['type_id']] + kind = "ARRAY<" + type['kind'] + ">" + is_flex = True + bits_end = bits_offset + object["bits_offset"] + else: + if "nr_bits" not in type: + eprint(type) + nr_bits = type["nr_bits"] + bits_end = bits_offset + object["bits_offset"] + nr_bits + + shallow_types.append({ + "struct_name": struct_name, + "struct_size": struct_size, + "parent_type": parent_type, + "kind": kind, + "type": type['name'], + "name": prefix + object['name'], + "bits_offset": bits_offset + object["bits_offset"], + "nr_bits": nr_bits, + "bits_end": bits_end, + "is_flex": is_flex, + }) + + return shallow_types + +all_structs = [] +for type in btf['types']: + types[type['id']] = type + if type['kind'] == 'STRUCT': + for member in type['members']: + all_structs += get_shallow(type["name"], type["size"], member, type["name"]) + +print(len(all_structs)) + +print(json.dumps(all_structs, indent="\t")) diff --git a/analysis/kernel/dashboard/Data/Field_Information/extract-btf.py b/analysis/kernel/dashboard/Data/Field_Information/extract-btf.py new file mode 100644 index 00000000..28086d84 --- /dev/null +++ b/analysis/kernel/dashboard/Data/Field_Information/extract-btf.py @@ -0,0 +1,579 @@ +#!/usr/bin/python3 + +import logging +import sqlite3 +import argparse +import json +import subprocess +import os +import tempfile +import math +import sys + +from contextlib import closing + + +PAHOLE = "/usr/bin/pahole" +BPFTOOL = "/usr/sbin/bpftool" +READELF = "/usr/bin/readelf" + + +def eprint(*args, **kwargs): + print(*args, file=sys.stderr, **kwargs) + + +def vmlinux(filename: str) -> str: + base_dir, file_name = os.path.split(filename) + if not base_dir: + base_dir = os.getcwd() + + if (not os.path.isfile(filename)) or (not os.access(filename, os.R_OK)): + logging.critical("Not a file or can't read the file: %s" % filename) + raise ValueError + + result = subprocess.check_output([READELF, "-h", filename]) + if "ELF64" not in result.decode("utf-8"): + logging.critical("Ooops! Not an ELF64 file: %s" % filename) + raise ValueError + + result = subprocess.check_output([READELF, "-S", filename]) + if "debug" not in result.decode("utf-8"): + logging.critical( + "The binary provided isn't compiled with debug data (DWARF): %s" + % filename + ) + raise ValueError + + return os.path.join(base_dir, file_name) + + +def can_create_file(filename: str) -> str: + base_dir, file_name = os.path.split(filename) + if not base_dir: + base_dir = os.getcwd() + + if os.path.isdir(base_dir) and os.access(base_dir, os.W_OK): + return os.path.join(base_dir, file_name) + else: + logging.critical("Wrong path provided: %s" % filename) + raise ValueError + + +def dump_btf_json(vmlinux: str) -> bytes: + with tempfile.NamedTemporaryFile() as tmp: + logging.info("TMP file created: %s" % tmp.name) + + subprocess.run( + [PAHOLE, "--btf_encode_detached=%s" % tmp.name, vmlinux], check=True + ) + + if not os.path.getsize(tmp.name): + logging.critical( + "The tmp file doesn't contain valid BTF encoded data: %s" + % tmp.name + ) + raise ValueError + + logging.info("Data size in TMP file: %d" % os.path.getsize(tmp.name)) + raw_json_data = subprocess.check_output( + [BPFTOOL, "btf", "dump", "--json", "file", tmp.name] + ) + + if not raw_json_data: + logging.critical( + "The JSON formated BTF data could not be extracted from BTF file: %s" + % tmp.name + ) + raise ValueError + + json_data = "" + try: + json_data = json.loads(raw_json_data) + logging.info("Length of parsed BTF JSON: %d" % len(json_data)) + except ValueError: + logging.critical("Can't parse BTF data in JSON format") + + return json_data + + +def get_shallow( + types, + struct_name, + struct_size, + object, + parent_type, + prefix="", + bits_offset=0, +): + shallow_types = [] + type = types[object["type_id"]] + + while type["kind"] in ["TYPEDEF", "CONST", "VOLATILE", "RESTRICT"]: + if (type["kind"] == "TYPEDEF") and ( + types[type["type_id"]]["name"] == "(anon)" + ): + # Sometimes we have inline structs and enumes defined via typedef. + # They are considered as anonymous. So we want to put typedef name instead. + # + # Before this fix example: + # {'id': 2184, 'kind': 'TYPEDEF', 'name': 'efi_memory_desc_t', 'type_id': 2183} + # {'id': 2183, 'kind': 'STRUCT', 'name': '(anon)', 'size': 40, 'vlen': 6, 'members': [{'name': 'type', 'type_id': 33, 'bits_offset': 0}, {'name': 'pad', 'type_id': 33, 'bits_offset': 32}, {'name': 'phys_addr', 'type_id': 35, 'bits_offset': 64}, {'name': 'virt_addr', 'type_id': 35, 'bits_offset': 128}, {'name': 'num_pages', 'type_id': 35, 'bits_offset': 192}, {'name': 'attribute', 'type_id': 35, 'bits_offset': 256}]} + # + # After this fix example: + # {'id': 2184, 'kind': 'TYPEDEF', 'name': 'efi_memory_desc_t', 'type_id': 2183} + # {'id': 2183, 'kind': 'STRUCT', 'name': 'efi_memory_desc_t', 'size': 40, 'vlen': 6, 'members': [{'name': 'type', 'type_id': 33, 'bits_offset': 0}, {'name': 'pad', 'type_id': 33, 'bits_offset': 32}, {'name': 'phys_addr', 'type_id': 35, 'bits_offset': 64}, {'name': 'virt_addr', 'type_id': 35, 'bits_offset': 128}, {'name': 'num_pages', 'type_id': 35, 'bits_offset': 192}, {'name': 'attribute', 'type_id': 35, 'bits_offset': 256}]} + name = type["name"] + type = types[type["type_id"]] + type["name"] = name + else: + type = types[type["type_id"]] + + if type["kind"] == "ARRAY" and type["nr_elems"] > 0: + object_bits_offset = object["bits_offset"] + bits_offset + for index in range(type["nr_elems"]): + expanded_object = object.copy() + expanded_object["type_id"] = type["type_id"] + expanded_object["name"] += f"[{index}]" + deeper_types = get_shallow( + types, + struct_name, + struct_size, + expanded_object, + parent_type, + prefix, + object_bits_offset, + ) + + sorted_deepest = sorted( + deeper_types, key=lambda x: x["bits_end"], reverse=True + ) + + for element in reversed(sorted_deepest): + element["bits_offset"] = object_bits_offset + element["bits_end"] -= object["bits_offset"] + object_bits_offset = element["bits_end"] + + deepest_bits_end = ( + sorted_deepest[0]["bits_end"] if len(sorted_deepest) > 0 else 0 + ) + object_bits_offset = 8 * math.ceil(deepest_bits_end / 8) + shallow_types += deeper_types + elif type["kind"] == "STRUCT" or type["kind"] == "UNION": + members = [(idx, member) for idx, member in enumerate(type["members"])] + bitfield_id = -1 + for idx, member in members: + if type["kind"] == "UNION": + new_prefix = ( + prefix + "/*" + str(idx) + ":" + object["name"] + "*/" + ) + else: + new_prefix = prefix + object["name"] + "." + + if "bitfield_size" in member: + # Handle situations with bit masks like this: + # + # struct nft_table { + # struct list_head list; + # struct rhltable chains_ht; + # struct list_head chains; + # struct list_head sets; + # struct list_head objects; + # struct list_head flowtables; + # u64 hgenerator; + # u64 handle; + # u32 use; + # u16 family:6, + # flags:8, + # genmask:2; + # u32 nlpid; + # char *name; + # u16 udlen; + # u8 *udata; + # }; + if [ + True + for id, mem in members + if (id == idx - 1) and ("bitfield_size" not in mem) + ]: + bitfield_id = idx + elif idx == 0: + bitfield_id = 0 + + member["bits_offset"] = [ + mem["bits_offset"] + for id, mem in members + if (id == bitfield_id) + ][0] + + if (":" + str(member["bitfield_size"])) not in member["name"]: + member["name"] = ( + member["name"] + ":" + str(member["bitfield_size"]) + ) + + deeper_types = get_shallow( + types, + struct_name, + struct_size, + member, + type["name"], + new_prefix, + bits_offset + object["bits_offset"], + ) + + shallow_types += deeper_types + else: + kind = type["kind"] + out_type = type["name"] + is_flex = False + + if type["kind"] == "PTR": + if "type_id" in type: + pointer_type = type + + if pointer_type["type_id"] != 0: + pointer_type = types[pointer_type["type_id"]] + + while "(anon)" in pointer_type["name"]: + if pointer_type["kind"] == "STRUCT": + struct_members = [] + for member in pointer_type["members"]: + if member["type_id"] != 0: + expanded_object = object.copy() + expanded_object["type_id"] = member[ + "type_id" + ] + deeper_types = get_shallow( + types, + struct_name, + struct_size, + expanded_object, + pointer_type["name"], + prefix, + ) + + member_type = "" + if member["name"] != "(anon)": + member_type = ( + deeper_types[0]["type"] + + member["name"] + if deeper_types[0]["type"][-1] + == "*" + else deeper_types[0]["type"] + + " " + + member["name"] + ) + else: + member_type = ( + deeper_types[0]["type"] + "?" + if deeper_types[0]["type"][-1] + == "*" + else deeper_types[0]["type"] + " ?" + ) + struct_members.append(member_type) + + elif member["type_id"] == 0: + struct_members.append( + "void *" + member["name"] + if param["name"] != "(anon)" + else "void *?" + ) + + pointer_type["name"] = "struct {%s} *" % ( + ", ".join(struct_members) + ) + + elif pointer_type["kind"] == "FUNC_PROTO": + # Example of function structure that we parse here: + # + # {'id': 86, 'kind': 'FUNC_PROTO', 'name': '(anon)', 'ret_type_id': 0, 'vlen': 1, 'params': [{'name': '(anon)', 'type_id': 85}]} + ret_type = {} + if pointer_type["ret_type_id"] != 0: + expanded_object = object.copy() + expanded_object["type_id"] = pointer_type[ + "ret_type_id" + ] + deeper_types = get_shallow( + types, + struct_name, + struct_size, + expanded_object, + pointer_type["name"], + prefix, + ) + + ret_type["name"] = deeper_types[0]["type"] + + else: + ret_type["name"] = "void" + + fun_params = [] + for param in pointer_type["params"]: + if param["type_id"] != 0: + expanded_object = object.copy() + expanded_object["type_id"] = param[ + "type_id" + ] + expanded_object["bits_offset"] = 0 + deeper_types = get_shallow( + types, + struct_name, + struct_size, + expanded_object, + pointer_type["name"], + prefix, + ) + + param_type = "" + if param["name"] != "(anon)": + param_type = ( + deeper_types[0]["type"] + + param["name"] + if deeper_types[0]["type"][-1] + == "*" + else deeper_types[0]["type"] + + " " + + param["name"] + ) + else: + param_type = ( + deeper_types[0]["type"] + "?" + if deeper_types[0]["type"][-1] + == "*" + else deeper_types[0]["type"] + " ?" + ) + fun_params.append(param_type) + + elif param["type_id"] == 0: + param_type = ( + "void *" + param["name"] + if param["name"] != "(anon)" + else "void *?" + ) + fun_params.append(param_type) + + pointer_type["name"] = "%s (*) (%s)" % ( + ret_type["name"], + ", ".join(fun_params), + ) + + elif pointer_type["type_id"] != 0: + pointer_type = types[pointer_type["type_id"]] + else: + pointer_type["name"] = "void" + else: + pointer_type["name"] = "void" + + if pointer_type["kind"] == "STRUCT": + out_type = ( + "struct " + pointer_type["name"] + " *" + if pointer_type["name"][-1] != ")" + else "struct " + pointer_type["name"] + ) + elif pointer_type["kind"] == "CONST": + out_type = ( + "const " + pointer_type["name"] + " *" + if pointer_type["name"][-1] != ")" + else "const " + pointer_type["name"] + ) + else: + out_type = ( + pointer_type["name"] + " *" + if pointer_type["name"][-1] != ")" + else pointer_type["name"] + ) + + nr_bits = 64 # PTR_SIZE + bits_end = bits_offset + object["bits_offset"] + nr_bits + elif type["kind"] == "ENUM" or type["kind"] == "ENUM64": + nr_bits = type["size"] * 8 + bits_end = bits_offset + object["bits_offset"] + nr_bits + elif type["kind"] == "ARRAY": + expanded_object = object.copy() + expanded_object["type_id"] = type["type_id"] + expanded_object["bits_offset"] = 0 + deeper_types = get_shallow( + types, + struct_name, + struct_size, + expanded_object, + type["name"], + prefix, + 0, + ) + + sorted_deepest = sorted( + deeper_types, key=lambda x: x["bits_end"], reverse=True + ) + nr_bits = sorted_deepest[0]["bits_end"] + + while type["kind"] in [ + "TYPEDEF", + "CONST", + "VOLATILE", + "ARRAY", + "RESTRICT", + ]: + if (type["kind"] == "TYPEDEF") and ( + types[type["type_id"]]["name"] == "(anon)" + ): + # Sometimes we have inline structs and enumes defined via typedef. + # They are considered as anonymous. So we want to put typedef name instead. + # + # Before this fix example: + # {'id': 2184, 'kind': 'TYPEDEF', 'name': 'efi_memory_desc_t', 'type_id': 2183} + # {'id': 2183, 'kind': 'STRUCT', 'name': '(anon)', 'size': 40, 'vlen': 6, 'members': [{'name': 'type', 'type_id': 33, 'bits_offset': 0}, {'name': 'pad', 'type_id': 33, 'bits_offset': 32}, {'name': 'phys_addr', 'type_id': 35, 'bits_offset': 64}, {'name': 'virt_addr', 'type_id': 35, 'bits_offset': 128}, {'name': 'num_pages', 'type_id': 35, 'bits_offset': 192}, {'name': 'attribute', 'type_id': 35, 'bits_offset': 256}]} + # + # After this fix example: + # {'id': 2184, 'kind': 'TYPEDEF', 'name': 'efi_memory_desc_t', 'type_id': 2183} + # {'id': 2183, 'kind': 'STRUCT', 'name': 'efi_memory_desc_t', 'size': 40, 'vlen': 6, 'members': [{'name': 'type', 'type_id': 33, 'bits_offset': 0}, {'name': 'pad', 'type_id': 33, 'bits_offset': 32}, {'name': 'phys_addr', 'type_id': 35, 'bits_offset': 64}, {'name': 'virt_addr', 'type_id': 35, 'bits_offset': 128}, {'name': 'num_pages', 'type_id': 35, 'bits_offset': 192}, {'name': 'attribute', 'type_id': 35, 'bits_offset': 256}]} + name = type["name"] + type = types[type["type_id"]] + type["name"] = name + else: + type = types[type["type_id"]] + + kind = "ARRAY<" + type["kind"] + ">" + is_flex = True + bits_end = bits_offset + object["bits_offset"] + else: + if "nr_bits" not in type: + eprint(type) + + nr_bits = type["nr_bits"] + bits_end = bits_offset + object["bits_offset"] + nr_bits + + shallow_types.append( + { + "struct_name": struct_name, + "struct_size": struct_size, + "parent_type": parent_type, + "kind": kind, + "type": out_type, + "name": prefix + object["name"], + "bits_offset": bits_offset + object["bits_offset"], + "nr_bits": nr_bits, + "bits_end": bits_end, + "is_flex": is_flex, + } + ) + + return shallow_types + + +def create_types_table(json_data: dict, con: sqlite3.Connection) -> int: + con.execute("DROP TABLE IF EXISTS types;") + + con.execute( + """CREATE TABLE types ( + struct_name TEXT NOT NULL, + struct_size UNSIGNED BIG INT NOT NULL, + parent_type TEXT NOT NULL, + kind VARCHAR(15) NOT NULL, + type TEXT NOT NULL, + name TEXT NOT NULL, + bits_offset UNSIGNED BIG INT, + nr_bits UNSIGNED BIG INT, + bits_end UNSIGNED BIG INT, + is_flex BOOLEAN NOT NULL + );""" + ) + logging.info("Types table created in DB.") + + data = [] + + types = {} + for type in json_data["types"]: + types[type["id"]] = type + + for type in json_data["types"]: + if type["kind"] == "STRUCT": + for member in type["members"]: + data += get_shallow( + types, type["name"], type["size"], member, type["name"] + ) + + if not data: + logging.critical( + "Looks SUS no vars structures found in the whole JSON BTF dump!" + ) + raise ValueError + + con.executemany( + """INSERT INTO types + VALUES(:struct_name, :struct_size, :parent_type, :kind, :type, + :name, :bits_offset, :nr_bits, :bits_end, :is_flex)""", + data, + ) + + return len(data) + + +def create_sql_db(db_file: str, json_data: dict) -> None: + with closing(sqlite3.connect(db_file)) as conn: + sqlite3.register_adapter(bool, int) + sqlite3.register_converter("BOOLEAN", lambda v: bool(int(v))) + + with conn as con: + res = create_types_table(json_data, con) + print("BTF data saved into Sqlite DB. Number of lines: %d" % res) + + +def check_tools() -> None: + subprocess.run( + [PAHOLE, "--version"], + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.STDOUT, + ) + logging.info("Pahole found: %s" % PAHOLE) + subprocess.run( + [BPFTOOL, "--version"], + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.STDOUT, + ) + logging.info("Bpftool found: %s" % BPFTOOL) + subprocess.run( + [READELF, "-v"], + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.STDOUT, + ) + logging.info("Readelf found: %s" % READELF) + + +def main(): + logging.basicConfig(level=logging.INFO, format="%(levelname)s:%(message)s") + + parser = argparse.ArgumentParser() + parser.add_argument( + "vmlinux", + help="Kernel binary (vmlinux) with debug data (DWARF).", + type=vmlinux, + nargs=1, + ) + parser.add_argument( + "--json_file", + nargs="?", + help="Path where to store JSON file with BTF data extracted from vmlinux.", + type=can_create_file, + default=None, + ) + parser.add_argument( + "--db_file", + nargs="?", + help="Path where to store Sqlite3 DB with BTF data.", + type=can_create_file, + default="btf.db", + ) + args = parser.parse_args() + + check_tools() + + json_data = dump_btf_json(args.vmlinux[0]) + + create_sql_db(args.db_file, json_data) + + +if __name__ == "__main__": + main() diff --git a/analysis/kernel/dashboard/Data/Git_Blame/git_blame_dump.py b/analysis/kernel/dashboard/Data/Git_Blame/git_blame_dump.py new file mode 100644 index 00000000..1397d2cf --- /dev/null +++ b/analysis/kernel/dashboard/Data/Git_Blame/git_blame_dump.py @@ -0,0 +1,213 @@ +#!/usr/bin/python3 + +import logging +import sqlite3 +import argparse +import subprocess +import os +import git +import tempfile + +from urllib.request import urlopen +from urllib.parse import urlparse +from urllib.parse import urlsplit +from contextlib import closing +from tqdm import tqdm + + +GIT = "/usr/bin/git" +FIND = "/usr/bin/find" +PARALLEL = "/usr/bin/parallel" + + +class CloneProgress(git.RemoteProgress): + def update(self, op_code, cur_count, max_count=None, message=""): + pbar = tqdm(total=max_count) + pbar.update(cur_count) + + +def repo_url(repo_url: str) -> str: + logging.info("Validating URL provided") + result = urlparse(repo_url) + if result.scheme and result.netloc: + return repo_url + else: + logging.critical("Wrong URL provided: %s" % repo_url) + raise ValueError + + +def can_create_file(filename: str) -> str: + base_dir, file_name = os.path.split(filename) + if not base_dir: + base_dir = os.getcwd() + + if os.path.isdir(base_dir) and os.access(base_dir, os.W_OK): + return os.path.join(base_dir, file_name) + else: + logging.critical("Wrong path provided: %s" % filename) + raise ValueError + + +def create_blame_table( + repo: git.repo.base.Repo, no_cpu: int, con: sqlite3.Connection +) -> int: + con.execute("DROP TABLE IF EXISTS git_blame;") + + con.execute( + """CREATE TABLE git_blame ( + file_path TEXT NOT NULL, + `commit` VARCHAR(40) NOT NULL, + line_no UNSIGNED BIG INT NOT NULL, + data TEXT NOT NULL + );""" + ) + logging.info("Git Blame table created in DB.") + + data = [] + tmp = tempfile.NamedTemporaryFile() + + repo_folder = repo.git.rev_parse("--show-toplevel") + command = ( + "cd " + + repo_folder + + "; " + + FIND + + " . -path ./tools -prune -o -path ./Documentation -prune -o -type f -print0 | " + + PARALLEL + + " --roundrobin --group -0 -P " + + str(no_cpu) + + " -I % " + + GIT + + " --no-pager blame -b -s -f -l -w --no-progress --root % >> " + + tmp.name + ) + + logging.info("Command that we're running: %s" % command) + + os.system(command) + + logging.info("Command execution complete!") + + blame_data = "" + with open(tmp.name, "r", encoding="utf-8", errors="ignore") as f: + blame_data = f.readlines() + + logging.info("TMP file contained: %d lines" % len(blame_data)) + + if not blame_data: + logging.critical( + "Can't get blame data for the file: %s" % entry.abspath + ) + raise ValueError + + for blame_entry in blame_data: + blame_entry_chunks = blame_entry.split() + + if (not blame_entry_chunks) or (len(blame_entry_chunks) < 2): + logging.critical("Can't get blame entry parsed: %s" % blame_entry) + raise ValueError + + commit = blame_entry_chunks[0].strip() + line_number = blame_entry_chunks[2].strip()[:-1] + code_line = blame_entry.split(blame_entry_chunks[2])[1] + + data.append((blame_entry_chunks[1], commit, line_number, code_line)) + + logging.info("Git data processing for specified commit is complete") + + if not data: + logging.critical( + "Looks SUS no commit information has been dumped from GIT repository!" + ) + raise ValueError + + con.executemany( + """INSERT INTO git_blame + VALUES(:file_path, :commit, :line_no, :data)""", + data, + ) + + return len(data) + + +def create_sql_db(db_file: str, no_cpu: int, repo: git.repo.base.Repo) -> None: + with closing(sqlite3.connect(db_file)) as conn: + with conn as con: + res = create_blame_table(repo, no_cpu, con) + print( + "GIT Blame data saved into SQLite DB. Number of lines: %d" % res + ) + + +def check_tools() -> None: + subprocess.run( + [GIT, "--version"], + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.STDOUT, + ) + subprocess.run( + [PARALLEL, "--version"], + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.STDOUT, + ) + + +def main(): + logging.basicConfig(level=logging.INFO, format="%(levelname)s:%(message)s") + + parser = argparse.ArgumentParser() + parser.add_argument( + "repo_url", + help="Linux Kernel Repository URL.", + type=repo_url, + nargs=1, + ) + parser.add_argument( + "commit", + help="Commit to which we should hard reset the repo.", + type=str, + nargs=1, + ) + parser.add_argument( + "--db_file", + nargs="?", + help="Path where to store Sqlite3 DB with Git Blame data.", + type=can_create_file, + default="git_blame.db", + ) + parser.add_argument( + "--no_cpu", + nargs="?", + help="No of CPU to use for Git Blame data parsing.", + type=int, + default=5, + ) + args = parser.parse_args() + + check_tools() + + repo = "" + if not os.path.exists("linux"): + logging.info( + "Clonning the Git repo as linux folder is empty: %s" + % args.repo_url[0] + ) + repo = git.Repo.clone_from( + args.repo_url[0], "linux", branch="master", progress=CloneProgress() + ) + else: + logging.info("Reusing source code in Linux folder") + repo = git.Repo("linux") + repo.git.reset("--hard", "origin") + repo.remotes.origin.pull() + + logging.info("Making hard reset to commit: %s" % args.commit[0]) + repo.git.reset("--hard", args.commit[0]) + + create_sql_db(args.db_file, args.no_cpu, repo) + + +if __name__ == "__main__": + main() diff --git a/analysis/kernel/dashboard/Data/Syzkaller_Coverage/merge_db.sql b/analysis/kernel/dashboard/Data/Syzkaller_Coverage/merge_db.sql new file mode 100644 index 00000000..8e8691a9 --- /dev/null +++ b/analysis/kernel/dashboard/Data/Syzkaller_Coverage/merge_db.sql @@ -0,0 +1,70 @@ +CREATE TABLE IF NOT EXISTS syzk_prog ( + prog_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, -- Make prog_id autoincrement + prog_code TEXT NOT NULL, + UNIQUE (prog_code) +); + +CREATE TABLE IF NOT EXISTS syzk_cov ( + file_id UNSIGNED BIG INT NOT NULL, + code_line_no UNSIGNED BIG INT NOT NULL, + prog_id UNSIGNED BIG INT NOT NULL, + PRIMARY KEY (file_id, code_line_no, prog_id), + UNIQUE (file_id, code_line_no, prog_id) -- Add unique constraint for all columns +); + +CREATE TABLE IF NOT EXISTS file_path ( + file_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, -- Make prog_id autoincrement + file_path TEXT NOT NULL, + UNIQUE (file_path) +); + +SELECT "attaching database"; +ATTACH DATABASE 'new.db' AS db1; + +SELECT "creating prog_id index"; +CREATE INDEX IF NOT EXISTS db1.syzk_prog_id ON syzk_prog (prog_id); + +SELECT "creating prog_code index"; +CREATE INDEX IF NOT EXISTS db1.syzk_prog_code ON syzk_prog (prog_code); + +SELECT "creating file_id index"; +CREATE INDEX IF NOT EXISTS db1.file_path_id ON file_path (file_id); + +SELECT "creating file_path index"; +CREATE INDEX IF NOT EXISTS db1.file_path_path ON file_path (file_path); + +SELECT "adding file paths"; +INSERT OR IGNORE INTO file_path (file_path) SELECT file_path FROM db1.file_path; +SELECT ' added rows: ' || CAST(changes() AS TEXT); + +SELECT "adding program codes"; +INSERT OR IGNORE INTO syzk_prog (prog_code) SELECT prog_code FROM db1.syzk_prog; +SELECT ' added rows: ' || CAST(changes() AS TEXT); + +SELECT "creating prog_id index"; +CREATE INDEX IF NOT EXISTS syzk_prog_id ON syzk_prog (prog_id); + +SELECT "creating prog_code index"; +CREATE INDEX IF NOT EXISTS syzk_prog_code ON syzk_prog (prog_code); + +SELECT "altering old table"; +ALTER TABLE db1.syzk_prog ADD COLUMN new_prog_id INTEGER; + +SELECT "altering old table"; +ALTER TABLE db1.file_path ADD COLUMN new_file_id INTEGER; + +SELECT "getting new prog ids"; +UPDATE db1.syzk_prog SET new_prog_id = (SELECT prog_id FROM syzk_prog WHERE prog_code = db1.syzk_prog.prog_code); + +SELECT "getting new file ids"; +UPDATE db1.file_path SET new_file_id = (SELECT file_id FROM file_path WHERE file_path = db1.file_path.file_path); + +SELECT "adding coverage"; +INSERT OR IGNORE INTO syzk_cov (file_id, code_line_no, prog_id) + SELECT + (SELECT new_file_id FROM db1.file_path WHERE file_id = db1.file_path.file_id), + code_line_no, + (SELECT new_prog_id FROM db1.syzk_prog WHERE prog_id = db1.syzk_cov.prog_id) + FROM db1.syzk_cov; +SELECT ' added rows: ' || CAST(changes() AS TEXT); + diff --git a/analysis/kernel/dashboard/Data/Syzkaller_Coverage/syzkaller_coverage.py b/analysis/kernel/dashboard/Data/Syzkaller_Coverage/syzkaller_coverage.py new file mode 100644 index 00000000..e3b47ade --- /dev/null +++ b/analysis/kernel/dashboard/Data/Syzkaller_Coverage/syzkaller_coverage.py @@ -0,0 +1,404 @@ +#!/usr/bin/python3 + +import logging +import sqlite3 +import html +import os +import argparse +import re +import operator + +from urllib.request import urlopen +from urllib.parse import urlparse +from urllib.parse import urlsplit +from contextlib import closing + +from io import StringIO +from lxml import etree + + +def sk_cov_url(sk_cov_url: str) -> str: + logging.info("Validating URL provided") + result = urlparse(sk_cov_url) + if ( + result.scheme + and result.netloc + and (("syzbot" or "syzkaller") in result.path) + ): + return sk_cov_url + else: + logging.critical("Wrong URL provided: %s" % sk_cov_url) + raise ValueError + + +def can_create_file(filename: str) -> str: + base_dir, file_name = os.path.split(filename) + if not base_dir: + base_dir = os.getcwd() + + if os.path.isdir(base_dir) and os.access(base_dir, os.W_OK): + return os.path.join(base_dir, file_name) + else: + logging.critical("Wrong path provided: %s" % filename) + raise ValueError + + +def get_sk_cov_data(html_file: str, sk_cov_url: str) -> dict: + out = {} + file_idx = 0 + + for url in sk_cov_url: + with urlopen(url) as response: + logging.info("Getting HTML data from %s" % url) + data = response.read() + + if html_file: + logging.info("Saving coverage data in HTML file") + with open( + os.path.join(html_file, "." + str(file_idx)), mode="wb" + ) as file: + file.write(data) + file_idx += 1 + + out[url] = data.decode() + return out + + +def get_prog(html_data: str) -> list: + pos = -1 + data = [] + logging.info('Looking for
')
+        pos = html_data.find("
", prog_code_pos) + prog_code = html.unescape(html_data[prog_code_pos:pos].strip()) + logging.debug(prog_code) + data.append((prog_id, prog_code.strip())) + + if not data: + loggin.critical('No
[^$(]+)(?:[$]\w+)?.+)\n?", prog[1]
+        )
+
+        if results:
+            for syscall in results:
+                data.append((prog[0], syscall[1]))
+
+    if not data:
+        loggin.critical(
+            "Something went wrong. No syscalls found in syzkaller programs."
+        )
+        raise ValueError
+
+    logging.info("Total number of syscalls: %d" % len(data))
+
+    return data
+
+
+def get_syzk_cov(html_data: str) -> list:
+    pos = -1
+    data = []
+    logging.info('Looking for class="file" id="contents patterns')
+    while True:
+        file_section = 'class="file" id="contents_'
+        pos = html_data.find(file_section, pos + 1)
+        if pos == -1:
+            break
+        file_id_pos = pos + len(file_section)
+        pos = html_data.find('"', file_id_pos + 1)
+        if pos == -1:
+            break
+        file_id = html_data[file_id_pos:pos]
+        prefix_pos = pos + len('"')
+        prefix = ">", coverage_pos)
+        coverage = html_data[coverage_pos:pos].splitlines()
+        for code_line_no, line in enumerate(coverage):
+            code_line_no += 1  # 0 - indexed
+            program_event = "onProgClick("
+            if program_event in line:
+                comma_pos = line.find(",", len(program_event))
+                prog_id = line[
+                    line.find(program_event) + len(program_event) : comma_pos
+                ]
+                data.append((file_id, code_line_no, prog_id * 1))
+                logging.debug("%s,%s,%s" % (file_id, code_line_no, prog_id * 1))
+
+    if not data:
+        loggin.critical('No class="file" id="contents has been found')
+        raise ValueError
+
+    return data
+
+
+def get_path(html_data: str) -> list:
+    # grep '/\1,\2/' > ${COVERAGE_HTML}.files.csv
+    html_parser = etree.HTMLParser()
+    tree = etree.parse(StringIO(html_data), html_parser)
+
+    a_tag_list = tree.xpath(
+        './/a[@id and @href and contains(@onclick,"onFileClick")]'
+    )
+
+    if not a_tag_list:
+        loggin.critical("No  tags with href, id, onclick attributes")
+        raise ValueError
+
+    logging.info(
+        "Found some  tags to process. Number of lines: %d" % len(a_tag_list)
+    )
+
+    data = []
+    for tag in a_tag_list:
+        # Remove redundant JavaScript and get clean integer for file_id
+        file_id = re.search(r"\(\s*(\d*)\s*\)", tag.get("onclick")).group(1)
+
+        if not file_id:
+            loggin.critical(
+                "OOOPS! Something has changed in syzkaller HTML! Can't parse onclick attribute data"
+            )
+            raise ValueError
+
+        #  We also want to cut away "path/" from beginning of the path string supplied by syzkaller
+        if not tag.get("id").startswith("path/"):
+            loggin.critical(
+                "OOOPS! Something has changed in syzkaller HTML! Can't parse id attribute data"
+            )
+            raise ValueError
+
+        file_path = tag.get("id")[5:]
+
+        logging.debug("%s,%s" % (file_id, file_path))
+        data.append((file_id, file_path))
+
+    logging.info(
+        "Amount of data entries extracted from tags and cleaned from duplicats. Number of lines: %d"
+        % len(data)
+    )
+
+    # Syzkaller HTML contains duplicates of the  tags with file_ids. Removing these lines from final data.
+    seen = set()
+    uniq_data = [
+        (file_id, file_path.strip())
+        for file_id, file_path in data
+        if file_id not in seen and not seen.add(file_id)
+    ]
+
+    return uniq_data
+
+
+def get_all_data(html_dict: dict) -> (dict, dict, dict):
+    all_path = {}
+    all_syzk_cov = {}
+    all_prog = {}
+
+    for html_name, html_data in html_dict.items():
+        all_path[html_name] = get_path(html_data)
+        print("Path data obtained from file: %s" % html_name)
+        logging.info("Amount of path data: %d" % len(all_path[html_name]))
+
+        all_syzk_cov[html_name] = get_syzk_cov(html_data)
+        print("Syzk_cov data obtained from %s" % html_name)
+        logging.info(
+            "Amount of syz_cov data: %d" % len(all_syzk_cov[html_name])
+        )
+
+        all_prog[html_name] = get_prog(html_data)
+        print("Prog data obtained from: %s" % html_name)
+        logging.info("Amount of programs data: %d" % len(all_prog[html_name]))
+
+    all_path, all_syzk_cov, all_prog = merge_dicts(
+        all_path, all_syzk_cov, all_prog
+    )
+    all_syscalls = get_syscalls(all_prog)
+
+    return (all_path, all_syzk_cov, all_prog, all_syscalls)
+
+
+def merge_dicts(
+    all_path: dict, all_syzk_cov: dict, all_prog: dict
+) -> (list, list, list):
+    if (len(all_path) != len(all_syzk_cov)) or (len(all_path) != len(all_prog)):
+        loggin.critical(
+            "OOOPS! Input dicts have different size. This is toally wrong!"
+        )
+        raise ValueError
+
+    # Join all the table data to form file_name, line_no, program_code tuples
+    tmp_all_cov = []
+    for html_name in all_path:
+        # [(file_id, file_path), ...]
+        tmp_path = {element[0]: element[1] for element in all_path[html_name]}
+        tmp_prog = {element[0]: element[1] for element in all_prog[html_name]}
+        tmp_all_cov += [
+            (tmp_path[element[0]], element[1], tmp_prog[element[2]])
+            for element in all_syzk_cov[html_name]
+        ]
+
+    # Remove duplicates
+    tmp_all_cov = list(set(tmp_all_cov))
+
+    # Get unique file paths and unique program codes
+    unique_file_path = set()
+    unique_prog_code = set()
+    for file_name, _, prog_code in tmp_all_cov:
+        unique_file_path.add(file_name)
+        unique_prog_code.add(prog_code)
+
+    # Enumeration of unique file paths and programs
+    file_path_dict = {
+        file_path: id for id, file_path in enumerate(list(unique_file_path))
+    }
+    prog_code_dict = {
+        prog_code: id for id, prog_code in enumerate(list(unique_prog_code))
+    }
+
+    # Swapping file paths and prog code with ids in syzk_cov
+    agreg_syzk_cov = [
+        (file_path_dict[file_path], code_line_no, prog_code_dict[prog_code])
+        for file_path, code_line_no, prog_code in tmp_all_cov
+    ]
+
+    # Converting file paths and prog code dicts to list of tuples
+    agreg_file_path = [
+        (id, file_path) for file_path, id in file_path_dict.items()
+    ]
+    agreg_prog_code = [
+        (id, prog_code) for prog_code, id in prog_code_dict.items()
+    ]
+
+    return (agreg_file_path, agreg_syzk_cov, agreg_prog_code)
+
+
+def create_sql_db(db_file: str, html_dict: dict) -> None:
+    all_path, all_syzk_cov, all_prog, all_syscalls = get_all_data(html_dict)
+
+    with closing(sqlite3.connect(db_file)) as conn:
+        # Process path
+        with conn as con:
+            con.execute("DROP TABLE IF EXISTS file_path;")
+
+            logging.info("Creating kernel file path table in syzkaller DB")
+            con.execute(
+                "CREATE TABLE file_path (file_id UNSIGNED BIG INT PRIMARY KEY NOT NULL, file_path TEXT NOT NULL);"
+            )
+
+            logging.info(
+                "Inserting (file_id, file_path) data into Sqlite DB (file_path table). Number of lines: %d"
+                % len(all_path)
+            )
+
+            con.executemany("INSERT INTO file_path VALUES(?, ?);", all_path)
+
+        # Process syzk_cov
+        with conn as con:
+            con.execute("DROP TABLE IF EXISTS syzk_cov;")
+
+            logging.info("Creating syzk_cov table in syzkaller DB")
+            con.execute(
+                "CREATE TABLE syzk_cov (file_id UNSIGNED BIG INT NOT NULL, code_line_no UNSIGNED BIG INT NOT NULL, prog_id UNSIGNED BIG INT NOT NULL, PRIMARY KEY (file_id, code_line_no, prog_id));"
+            )
+
+            logging.info(
+                "Inserting (file_id, code_line_no, prog_id) data into Sqlite DB (syzk_cov table). Number of lines: %d"
+                % len(all_syzk_cov)
+            )
+
+            con.executemany(
+                "INSERT INTO syzk_cov VALUES(:file_id, :codeLine_no, :prog_id);",
+                all_syzk_cov,
+            )
+
+        # Process prog
+        with conn as con:
+            con.execute("DROP TABLE IF EXISTS syzk_prog;")
+
+            logging.info("Creating prog table in syzkaller DB")
+            con.execute(
+                "CREATE TABLE syzk_prog (prog_id UNSIGNED BIG INT PRIMARY KEY NOT NULL, prog_code TEXT NOT NULL);"
+            )
+
+            logging.info(
+                "Inserting (prog_id, prog_code) data into Sqlite DB (syzk_prog table). Number of lines: %d"
+                % len(all_prog)
+            )
+
+            con.executemany("INSERT INTO syzk_prog VALUES(?, ?);", all_prog)
+
+        # Process syscalls
+        with conn as con:
+            con.execute("DROP TABLE IF EXISTS syscalls;")
+
+            logging.info("Creating syscalls table in syzkaller DB")
+            con.execute(
+                "CREATE TABLE syscalls (prog_id UNSIGNED BIG INT NOT NULL, syscall TEXT NOT NULL);"
+            )
+
+            logging.info(
+                "Inserting (prog_id, syscall) data into Sqlite DB (syzk_prog table). Number of lines: %d"
+                % len(all_syscalls)
+            )
+
+            con.executemany("INSERT INTO syscalls VALUES(?, ?);", all_syscalls)
+
+
+def main():
+    logging.basicConfig(level=logging.INFO, format="%(levelname)s:%(message)s")
+
+    parser = argparse.ArgumentParser()
+    parser.add_argument(
+        "sk_cov_url",
+        help="Syzkaller Coverage URL like https://storage.googleapis.com/syzbot-assets/0422343bda5a/ci2-linux-6-1-kasan-aa4cd140.html, Get it here: https://syzkaller.appspot.com/upstream",
+        type=sk_cov_url,
+        nargs="+",
+    )
+    parser.add_argument(
+        "--html_file",
+        nargs="?",
+        help="Path where to store syzkaller HTML file.",
+        type=can_create_file,
+        default=None,
+    )
+    parser.add_argument(
+        "--db_file",
+        nargs="?",
+        help="Path where to store resulting Sqlite3 DB file.",
+        type=can_create_file,
+        default="syzkaller.db",
+    )
+    args = parser.parse_args()
+
+    html_dict = get_sk_cov_data(args.html_file, args.sk_cov_url)
+
+    if args.html_file:
+        print("Coverage data in HTML format saved in: %s" % args.html_file)
+
+    create_sql_db(args.db_file, html_dict)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/analysis/kernel/dashboard/Data/Test_Field_Information/full_btf_extraction.py b/analysis/kernel/dashboard/Data/Test_Field_Information/full_btf_extraction.py
new file mode 100644
index 00000000..8005731d
--- /dev/null
+++ b/analysis/kernel/dashboard/Data/Test_Field_Information/full_btf_extraction.py
@@ -0,0 +1,413 @@
+#!/usr/bin/python3
+import logging
+import sqlite3
+import argparse
+import json
+import subprocess
+import os
+import tempfile
+from contextlib import closing
+
+PAHOLE = "/usr/bin/pahole"
+BPFTOOL = "/usr/sbin/bpftool"
+READELF = "/usr/bin/readelf"
+
+
+def vmlinux(filename: str) -> str:
+    base_dir, file_name = os.path.split(filename)
+    if not base_dir:
+        base_dir = os.getcwd()
+    if (not os.path.isfile(filename)) or (not os.access(filename, os.R_OK)):
+        logging.critical("Not a file or can't read the file: %s" % filename)
+        raise ValueError
+    result = subprocess.check_output([READELF, "-h", filename])
+    if "ELF64" not in result.decode("utf-8"):
+        logging.critical("Ooops! Not an ELF64 file: %s" % filename)
+        raise ValueError
+    result = subprocess.check_output([READELF, "-S", filename])
+    if "debug" not in result.decode("utf-8"):
+        logging.critical(
+            "The binary provided isn't compiled with debug data (DWARF): %s"
+            % filename
+        )
+        raise ValueError
+    return os.path.join(base_dir, filename)
+
+
+def can_create_file(filename: str) -> str:
+    base_dir, file_name = os.path.split(filename)
+    if not base_dir:
+        base_dir = os.getcwd()
+    if os.path.isdir(base_dir) and os.access(base_dir, os.W_OK):
+        return os.path.join(base_dir, file_name)
+    else:
+        logging.critical("Wrong path provided: %s" % filename)
+        raise ValueError
+
+
+def dump_btf_json(vmlinux: str) -> bytes:
+    with tempfile.NamedTemporaryFile() as tmp:
+        logging.info("TMP file created: %s" % tmp.name)
+        subprocess.run(
+            [PAHOLE, "--btf_encode_detached=%s" % tmp.name, vmlinux], check=True
+        )
+        if not os.path.getsize(tmp.name):
+            logging.critical(
+                "The tmp file doesn't contain valid BTF encoded data: %s"
+                % tmp.name
+            )
+            raise ValueError
+        logging.info("Data size in TMP file: %d" % os.path.getsize(tmp.name))
+        raw_json_data = subprocess.check_output(
+            [BPFTOOL, "btf", "dump", "--json", "file", tmp.name]
+        )
+        if not raw_json_data:
+            logging.critical(
+                "The JSON formated BTF data could not be extracted from BTF file: %s"
+                % tmp.name
+            )
+            raise ValueError
+        json_data = ""
+        try:
+            json_data = json.loads(raw_json_data)
+            logging.info("Length of parsed BTF JSON: %d" % len(json_data))
+        except ValueError:
+            logging.critical("Can't parse BTF data in JSON format")
+        return json_data
+
+
+def create_members_table(json_data: dict, con: sqlite3.Connection) -> int:
+    con.execute("DROP TABLE IF EXISTS members;")
+    # types_id - ID from types table to run unions
+    con.execute(
+        """CREATE TABLE members (
+                types_id UNSIGNED BIG INT NOT NULL,
+                name TEXT NOT NULL,
+                type_id UNSIGNED BIG INT NOT NULL,
+                bits_offset UNSIGNED BIG INT NOT NULL,
+                bitfield_size UNSIGNED BIG INT
+                );"""
+    )
+    logging.info("Members table created in DB.")
+    # {'name': 'state', 'type_id': 25, 'bits_offset': 0}
+    data = []
+    for types_dict in json_data["types"]:
+        if "members" in types_dict:
+            for member in types_dict["members"]:
+                if len(member) == 3:
+                    data.append(
+                        (
+                            types_dict["id"],
+                            member["name"],
+                            member["type_id"],
+                            member["bits_offset"],
+                            None,
+                        )
+                    )
+                elif len(member) == 4:
+                    data.append(
+                        (
+                            types_dict["id"],
+                            member["name"],
+                            member["type_id"],
+                            member["bits_offset"],
+                            member["bitfield_size"],
+                        )
+                    )
+                else:
+                    logging.critical(
+                        "Oooooops! Something wrong happening with members data. Please debug: %s"
+                        % str(types_dict)
+                    )
+                    raise ValueError
+    if not data:
+        loggin.critical(
+            "Looks SUS no members structures found in the whole JSON BTF dump!"
+        )
+        raise ValueError
+    logging.info(
+        "Inserting (types_id, name, type_id, bits_offset, bitfield_size) data into Sqlite DB (members table). Number of lines: %d"
+        % len(data)
+    )
+    con.executemany(
+        "INSERT INTO members(types_id, name, type_id, bits_offset, bitfield_size) VALUES(?, ?, ?, ?, ?);",
+        data,
+    )
+    return len(data)
+
+
+def create_values_table(json_data: dict, con: sqlite3.Connection) -> int:
+    con.execute("DROP TABLE IF EXISTS `values`;")
+    # types_id - ID from types table to run unions
+    # val - could be quite large or negative so don't fit into INTEGER
+    con.execute(
+        """CREATE TABLE `values` (
+                types_id UNSIGNED BIG INT NOT NULL,
+                name TEXT NOT NULL,
+                val BLOB NOT NULL
+                );"""
+    )
+    logging.info("Values table created in DB.")
+    # {'name': 'MODULE_STATE_LIVE', 'val': 0}
+    data = []
+    for types_dict in json_data["types"]:
+        if "values" in types_dict:
+            for value in types_dict["values"]:
+                if len(value) == 2:
+                    data.append(
+                        (types_dict["id"], value["name"], str(value["val"]))
+                    )
+                else:
+                    logging.critical(
+                        "Oooooops! Something wrong happening with values data. Please debug: %s"
+                        % str(types_dict)
+                    )
+                    raise ValueError
+    if not data:
+        loggin.critical(
+            "Looks SUS no values structures found in the whole JSON BTF dump!"
+        )
+        raise ValueError
+    logging.info(
+        "Inserting (types_id, name, val) data into Sqlite DB (values table). Number of lines: %d"
+        % len(data)
+    )
+    con.executemany(
+        "INSERT INTO `values`(types_id, name, val) VALUES(?, ?, ?);", data
+    )
+    return len(data)
+
+
+def create_params_table(json_data: dict, con: sqlite3.Connection) -> int:
+    con.execute("DROP TABLE IF EXISTS params;")
+    # types_id - ID from types table to run unions
+    con.execute(
+        """CREATE TABLE params (
+                types_id UNSIGNED BIG INT NOT NULL,
+                name TEXT NOT NULL,
+                type_id UNSIGNED BIG INT NOT NULL
+                );"""
+    )
+    logging.info("Params table created in DB.")
+    # {'name': '(anon)', 'type_id': 57}
+    data = []
+    for types_dict in json_data["types"]:
+        if "params" in types_dict:
+            for param in types_dict["params"]:
+                if len(param) == 2:
+                    data.append(
+                        (types_dict["id"], param["name"], param["type_id"])
+                    )
+                else:
+                    logging.critical(
+                        "Oooooops! Something wrong happening with params data. Please debug: %s"
+                        % str(types_dict)
+                    )
+                    raise ValueError
+    if not data:
+        logging.critical(
+            "Looks SUS no params structures found in the whole JSON BTF dump!"
+        )
+        raise ValueError
+    logging.info(
+        "Inserting (types_id, name, type_id) data into Sqlite DB (params table). Number of lines: %d"
+        % len(data)
+    )
+    con.executemany(
+        "INSERT INTO params(types_id, name, type_id) VALUES(?, ?, ?);", data
+    )
+    return len(data)
+
+
+def create_vars_table(json_data: dict, con: sqlite3.Connection) -> int:
+    con.execute("DROP TABLE IF EXISTS vars;")
+    # types_id - ID from types table to run unions
+    con.execute(
+        """CREATE TABLE vars (
+                types_id UNSIGNED BIG INT NOT NULL,
+                type_id UNSIGNED BIG INT NOT NULL,
+                offset UNSIGNED BIG INT NOT NULL,
+                size UNSIGNED BIG INT NOT NULL
+                );"""
+    )
+    logging.info("Vars table created in DB.")
+    # {'type_id': 8651, 'offset': 0, 'size': 48}
+    data = []
+    for types_dict in json_data["types"]:
+        if "vars" in types_dict:
+            for var in types_dict["vars"]:
+                if len(var) == 3:
+                    data.append(
+                        (
+                            types_dict["id"],
+                            var["type_id"],
+                            var["offset"],
+                            var["size"],
+                        )
+                    )
+                else:
+                    logging.critical(
+                        "Oooooops! Something wrong happening with vars data. Please debug: %s"
+                        % str(types_dict)
+                    )
+                    raise ValueError
+    if not data:
+        logging.critical(
+            "Looks SUS no vars structures found in the whole JSON BTF dump!"
+        )
+        raise ValueError
+    logging.info(
+        "Inserting (types_id, type_id, offset, size) data into Sqlite DB (vars table). Number of lines: %d"
+        % len(data)
+    )
+    con.executemany(
+        "INSERT INTO vars(types_id, type_id, offset, size) VALUES(?, ?, ?, ?);",
+        data,
+    )
+    return len(data)
+
+
+def create_types_table(json_data: dict, con: sqlite3.Connection) -> int:
+    con.execute("DROP TABLE IF EXISTS types;")
+    columns_identified = set(
+        key
+        for elem in json_data["types"]
+        for key in elem
+        if key not in ["vars", "params", "values", "members"]
+    )
+    if len(columns_identified) != 14:
+        logging.critical(
+            "Looks SUS! Unique columns found: %s. This doesn't match our parsing"
+            % str(columns_identified)
+        )
+        raise ValueError
+    con.execute(
+        """CREATE TABLE types (
+                id UNSIGNED BIG INT PRIMARY KEY NOT NULL,
+                kind VARCHAR(15) NOT NULL,
+                name TEXT NOT NULL,
+                size UNSIGNED BIG INT,
+                vlen UNSIGNED BIG INT,
+                bits_offset UNSIGNED BIG INT,
+                nr_bits UNSIGNED BIG INT, 
+                encoding TEXT,
+                linkage TEXT,
+                nr_elems UNSIGNED BIG INT,
+                fwd_kind TEXT,
+                index_type_id UNSIGNED BIG INT,
+                type_id UNSIGNED BIG INT,
+                ret_type_id UNSIGNED BIG INT
+                );"""
+    )
+    logging.info("Types table created in DB.")
+    data = []
+    entry = lambda defined: {
+        key: (defined[key] if key in defined else None)
+        for key in columns_identified
+    }
+    for types_dict in json_data["types"]:
+        data.append(entry(types_dict))
+    if not data:
+        logging.critical(
+            "Looks SUS no vars structures found in the whole JSON BTF dump!"
+        )
+        raise ValueError
+    con.executemany(
+        """INSERT INTO types 
+                    VALUES(:id, :kind, :name, :size, :vlen, :bits_offset, :nr_bits,
+                    :encoding, :linkage, :nr_elems, :fwd_kind, :index_type_id,
+                    :type_id, :ret_type_id);""",
+        data,
+    )
+    return len(data)
+
+
+def create_sql_db(db_file: str, json_data: dict) -> None:
+    with closing(sqlite3.connect(db_file)) as conn:
+        with conn as con:
+            res = create_members_table(json_data, con)
+            print(
+                "Members data saved in members table of BTF DB. Number of lines: %d"
+                % res
+            )
+        with conn as con:
+            res = create_values_table(json_data, con)
+            print(
+                "Values data saved in values table of BTF DB. Number of lines: %d"
+                % res
+            )
+        with conn as con:
+            res = create_params_table(json_data, con)
+            print(
+                "Params data saved in params table of BTF DB. Number of lines: %d"
+                % res
+            )
+        with conn as con:
+            res = create_vars_table(json_data, con)
+            print(
+                "Vars data saved in vars table of BTF DB. Number of lines: %d"
+                % res
+            )
+        with conn as con:
+            res = create_types_table(json_data, con)
+            print(
+                "Vars data saved in vars table of BTF DB. Number of lines: %d"
+                % res
+            )
+
+
+def check_tools() -> None:
+    subprocess.run(
+        [PAHOLE, "--version"],
+        check=True,
+        stdout=subprocess.DEVNULL,
+        stderr=subprocess.STDOUT,
+    )
+    logging.info("Pahole found: %s" % PAHOLE)
+    subprocess.run(
+        [BPFTOOL, "--version"],
+        check=True,
+        stdout=subprocess.DEVNULL,
+        stderr=subprocess.STDOUT,
+    )
+    logging.info("Bpftool found: %s" % BPFTOOL)
+    subprocess.run(
+        [READELF, "-v"],
+        check=True,
+        stdout=subprocess.DEVNULL,
+        stderr=subprocess.STDOUT,
+    )
+    logging.info("Readelf found: %s" % READELF)
+
+
+def main():
+    logging.basicConfig(level=logging.INFO, format="%(levelname)s:%(message)s")
+    parser = argparse.ArgumentParser()
+    parser.add_argument(
+        "vmlinux",
+        help="Kernel binary (vmlinux) with debug data (DWARF).",
+        type=vmlinux,
+        nargs=1,
+    )
+    parser.add_argument(
+        "--json_file",
+        nargs="?",
+        help="Path where to store JSON file with BTF data extracted from vmlinux.",
+        type=can_create_file,
+        default=None,
+    )
+    parser.add_argument(
+        "--db_file",
+        nargs="?",
+        help="Path where to store Sqlite3 DB with BTF data.",
+        type=can_create_file,
+        default="btf.db",
+    )
+    args = parser.parse_args()
+    check_tools()
+    json_data = dump_btf_json(args.vmlinux[0])
+    create_sql_db(args.db_file, json_data)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/analysis/kernel/dashboard/Data/Test_Field_Information/test_field_information.py b/analysis/kernel/dashboard/Data/Test_Field_Information/test_field_information.py
new file mode 100644
index 00000000..9baa6e0f
--- /dev/null
+++ b/analysis/kernel/dashboard/Data/Test_Field_Information/test_field_information.py
@@ -0,0 +1,247 @@
+#!/usr/bin/python3
+
+import logging
+import sqlite3
+import argparse
+
+from contextlib import closing
+
+
+def find_type_by_id(
+    type_id: int, cur: sqlite3.Cursor, indent: int, rec_dep: int
+) -> None:
+    res = cur.execute(
+        "SELECT id, kind, name, size, index_type_id, type_id, ret_type_id FROM types WHERE id = ?",
+        (type_id,),
+    ).fetchall()
+
+    if not res:
+        return
+
+    if len(res) != 1:
+        logging.critical("Hm. More than one types entry found: %s", str(res))
+        raise ValueError
+
+    print(
+        " " * indent
+        + "-- TYPE --\n"
+        + " " * indent
+        + "id: "
+        + str(res[0][0])
+        + "\n"
+        + " " * indent
+        + "kind: "
+        + str(res[0][1])
+        + "\n"
+        + " " * indent
+        + "name: "
+        + str(res[0][2])
+        + "\n"
+        + " " * indent
+        + "size: "
+        + str(res[0][3])
+        + "\n"
+        + " " * indent
+        + "index_type_id: "
+        + str(res[0][4])
+        + "\n"
+        + " " * indent
+        + "ret_type_id: "
+        + str(res[0][6])
+        + "\n"
+    )
+    find_params(res[0][0], cur, indent, rec_dep)
+    find_members(res[0][0], cur, indent, rec_dep)
+    find_vars(res[0][0], cur, indent, rec_dep)
+    find_values(res[0][0], cur, indent)
+
+    if res[0][5] and (indent <= rec_dep * 2):
+        find_type_by_id(res[0][5], cur, indent + 2, rec_dep)
+
+
+def find_type_by_name(
+    struct_name: str, cur: sqlite3.Cursor, rec_dep: int
+) -> None:
+    res = cur.execute(
+        "SELECT id FROM types WHERE name = ?",
+        (struct_name,),
+    ).fetchall()
+
+    if not res:
+        logging.critical("Can't find provided type name in kernel's BTF data.")
+        raise ValueError
+
+    if len(res) != 1:
+        logging.critical("Hm. More than one types entry found: %s", str(res))
+        raise ValueError
+
+    find_type_by_id(res[0][0], cur, 0, rec_dep)
+
+
+def find_params(
+    types_id: int, cur: sqlite3.Cursor, indent: int, rec_dep: int
+) -> None:
+    params = cur.execute(
+        "SELECT types_id, name, type_id FROM params WHERE types_id = ?",
+        (types_id,),
+    ).fetchall()
+
+    if not params:
+        return
+
+    print(" " * indent + "-- PARAMS --")
+
+    for param in params:
+        print(
+            " " * indent
+            + "id: "
+            + str(param[0])
+            + "\n"
+            + " " * indent
+            + "name: "
+            + str(param[1])
+            + "\n"
+        )
+
+        (
+            find_type_by_id(param[2], cur, indent + 2, rec_dep)
+            if param[2]
+            else None
+        )
+
+
+def find_members(
+    types_id: int, cur: sqlite3.Cursor, indent: int, rec_dep: int
+) -> None:
+    members = cur.execute(
+        "SELECT types_id, name, type_id, bits_offset, bitfield_size FROM members WHERE types_id = ?",
+        (types_id,),
+    ).fetchall()
+
+    if not members:
+        return
+
+    print(" " * indent + "-- MEMBERS --")
+
+    for member in members:
+        print(
+            " " * indent
+            + "id: "
+            + str(member[0])
+            + "\n"
+            + " " * indent
+            + "name: "
+            + str(member[1])
+            + "\n"
+            + " " * indent
+            + "bits_offset: "
+            + str(member[3])
+            + "\n"
+            + " " * indent
+            + "bitfield_size: "
+            + str(member[4])
+            + "\n"
+        )
+
+        (
+            find_type_by_id(member[2], cur, indent + 2, rec_dep)
+            if member[2]
+            else None
+        )
+
+
+def find_vars(
+    types_id: int, cur: sqlite3.Cursor, indent: int, rec_dep: int
+) -> None:
+    vars = cur.execute(
+        "SELECT types_id, type_id, offset, size FROM vars WHERE types_id = ?",
+        (types_id,),
+    ).fetchall()
+
+    if not vars:
+        return
+
+    print(" " * indent + "-- VARS --")
+
+    for var in vars:
+        print(
+            " " * indent
+            + "id: "
+            + str(var[0])
+            + "\n"
+            + " " * indent
+            + "offset: "
+            + str(var[2])
+            + "\n"
+            + " " * indent
+            + "size: "
+            + str(var[3])
+            + "\n"
+        )
+
+        find_type_by_id(var[1], cur, indent + 2, rec_dep) if var[1] else None
+
+
+def find_values(types_id: int, cur: sqlite3.Cursor, indent: int) -> None:
+    values = cur.execute(
+        "SELECT types_id, val, name FROM `values` WHERE types_id = ?",
+        (types_id,),
+    ).fetchall()
+
+    if not values:
+        return
+
+    print(" " * indent + "-- VALUES --")
+
+    for value in values:
+        print(
+            " " * indent
+            + "id: "
+            + str(value[0])
+            + "\n"
+            + " " * indent
+            + "val: "
+            + str(value[1])
+            + "\n"
+            + " " * indent
+            + "name: "
+            + str(value[2])
+            + "\n"
+        )
+
+
+def open_sql_db(db_file: str, struct_name: list, rec_dep: int) -> None:
+    with closing(sqlite3.connect(db_file)) as conn:
+        with closing(conn.cursor()) as cur:
+            for struct in struct_name:
+                print("----------------> %s <---------------------" % struct)
+
+                find_type_by_name(struct, cur, rec_dep)
+
+
+def main():
+    logging.basicConfig(level=logging.INFO, format="%(levelname)s:%(message)s")
+
+    parser = argparse.ArgumentParser()
+    parser.add_argument("btf_db", help="Sqlite3 DB with BTF data.", nargs=1)
+    parser.add_argument(
+        "struct_name",
+        help="Structure to look for in BTF database.",
+        nargs="*",
+        default=["msg_msg"],
+    )
+    parser.add_argument(
+        "recursion_depth",
+        nargs="?",
+        type=int,
+        help="Depth of recursive type querying.",
+        default="5",
+    )
+
+    args = parser.parse_args()
+
+    open_sql_db(args.btf_db[0], args.struct_name, args.recursion_depth)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/analysis/kernel/dashboard/Data/codeql/find_paths.py b/analysis/kernel/dashboard/Data/codeql/find_paths.py
new file mode 100644
index 00000000..6ea5b182
--- /dev/null
+++ b/analysis/kernel/dashboard/Data/codeql/find_paths.py
@@ -0,0 +1,106 @@
+import sqlite3
+from collections import defaultdict
+
+
+def find_paths_to_function_recursive(db_file, function, max_depth=100):
+    """
+    Finds paths to $function recursively, finding edges
+    by target message and filtering by rule_id.
+    """
+
+    conn = sqlite3.connect(db_file)
+    cursor = conn.cursor()
+
+    cursor.execute("SELECT id, message, uri, startLine FROM locations")
+    locations = cursor.fetchall()
+
+    # Create a dictionary to map location ID to (message, uri, startLine)
+    location_map = defaultdict(set)
+    for id, message, uri, startLine in locations:
+        location_map[(message, uri, startLine)].add(id)
+
+    # Find IDs corresponding to $function
+    function_keys = [
+        key for key, value in location_map.items() if key[0] == function
+    ][0:1000]
+
+    def preprocess_edges(cursor):
+        cursor.execute(
+            """
+            SELECT e.source_location_id, e.target_location_id
+            FROM edges AS e
+            WHERE e.rule_id = 'callgraph-all'
+            """
+        )
+        edges = {}
+        for source_id, target_id in cursor.fetchall():
+            if target_id not in edges:
+                edges[target_id] = []
+            edges[target_id].append(source_id)
+        return edges
+
+    edges = preprocess_edges(cursor)
+
+    def find_edge_source(target_key):
+        target_ids = location_map.get(target_key)
+        if target_ids:
+            for target_id in target_ids:
+                source_ids = edges.get(target_id)
+                if source_ids:
+                    return source_ids
+        return None
+
+    paths = []  # To store all unique paths
+    seen_paths = set()  # To keep track of visited paths
+
+    def dfs_recursive(node_key, path, visited, depth):
+        visited.add(node_key)
+        path.append(node_key)
+
+        neighbors = find_edge_source(node_key)
+
+        if neighbors is not None:
+            for neighbor_id in neighbors:
+                for key, ids in location_map.items():
+                    if neighbor_id in ids:
+                        neighbor_key = key
+                        break
+                if (
+                    neighbor_key
+                    and depth < max_depth
+                    and neighbor_key not in visited
+                ):
+                    dfs_recursive(
+                        neighbor_key, path.copy(), visited.copy(), depth + 1
+                    )
+        else:
+            path_tuple = tuple(path)
+            if path_tuple not in seen_paths:
+                seen_paths.add(path_tuple)
+                paths.append(path.copy())
+
+        path.pop()
+        visited.remove(node_key)
+
+    for start_key in function_keys:
+        dfs_recursive(start_key, [], set(), 0)
+
+    named_paths = []
+    for path in paths:
+        named_paths.append(path.copy())
+
+    conn.close()
+    return named_paths
+
+
+if __name__ == "__main__":
+    db_file = "codeql_data-6.1.db"
+    paths = find_paths_to_function_recursive(db_file, "___sys_recvmsg")
+
+    for path in paths:
+        # Format the path
+        formatted_path = " -> ".join(
+            [f"{node[0]} ({node[1]}:{node[2]})" for node in path]
+        )
+        print(formatted_path)
+        print()
diff --git a/analysis/kernel/dashboard/Data/codeql/import_other.py b/analysis/kernel/dashboard/Data/codeql/import_other.py
new file mode 100644
index 00000000..6c2b36c2
--- /dev/null
+++ b/analysis/kernel/dashboard/Data/codeql/import_other.py
@@ -0,0 +1,157 @@
+import csv
+import sqlite3
+
+
+def trim_filename(string):
+    try:
+        start_index = string.index("linux/") + len("linux/")
+        return string[start_index:]
+    except ValueError:
+        return string
+
+
+def import_allocations_to_db(csv_filename, db_name="codeql_data.db"):
+    conn = sqlite3.connect(db_name)
+    cursor = conn.cursor()
+
+    # Create the table if it doesn't exist
+    cursor.execute(
+        """
+        CREATE TABLE IF NOT EXISTS kmalloc_calls (
+            id INTEGER PRIMARY KEY AUTOINCREMENT,
+            call_site TEXT,
+            call_expr TEXT,
+            struct_type TEXT,
+            struct_def TEXT,
+            struct_size INTEGER,
+            flags TEXT,
+            alloc_size INTEGER,
+            sizeof_expr TEXT,
+            is_flexible TEXT
+        )
+    """
+    )
+
+    with open(csv_filename, "r") as csvfile:
+        reader = csv.reader(csvfile, delimiter=",", quotechar='"')
+        next(reader)
+        for row in reader:
+            # Check if the row has the correct number of columns
+            if len(row) == 9:
+                try:
+                    call_site = trim_filename(row[0])
+                    call_expr = row[1]
+                    struct_type = row[2]
+                    struct_def = trim_filename(row[3])
+                    struct_size = int(row[4])
+                    flags = row[5]
+
+                    # Handle 'unknown' for alloc_size
+                    try:
+                        alloc_size = int(row[6])
+                    except ValueError:
+                        alloc_size = None
+
+                    sizeof_expr = row[7]
+                    is_flexible = row[8]
+
+                    cursor.execute(
+                        """
+                        INSERT INTO kmalloc_calls (call_site, call_expr, struct_type, struct_def, struct_size, flags, alloc_size, sizeof_expr, is_flexible) 
+                        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
+                    """,
+                        (
+                            call_site,
+                            call_expr,
+                            struct_type,
+                            struct_def,
+                            struct_size,
+                            flags,
+                            alloc_size,
+                            sizeof_expr,
+                            is_flexible,
+                        ),
+                    )
+
+                except (ValueError, IndexError) as e:
+                    print(f"Skipping invalid row: {row} - Error: {e}")
+            else:
+                print(f"Skipping invalid row: {row}")
+
+    conn.commit()
+    conn.close()
+
+
+def import_functions_to_db(csv_file, db_name="codeql_data.db"):
+    conn = sqlite3.connect(db_name)
+    cursor = conn.cursor()
+
+    # Create table if it doesn't exist
+    cursor.execute(
+        """
+    CREATE TABLE IF NOT EXISTS function_locations (
+      function_name TEXT,
+      file_path TEXT,
+      start_line INTEGER,
+      end_line INTEGER
+    )
+  """
+    )
+
+    with open(csv_file, "r") as file:
+        reader = csv.reader(file)
+        next(reader)
+        for row in reader:
+            if len(row) == 4:
+
+                cursor.execute(
+                    """
+        INSERT INTO function_locations (function_name, file_path, start_line, end_line)
+        VALUES (?, ?, ?, ?)
+      """,
+                    (row[0], trim_filename(row[1]), row[2], row[3]),
+                )
+            else:
+                print(f"Skipping invalid row: {row}")
+
+    conn.commit()
+    conn.close()
+
+
+def import_configs_to_db(csv_file, db_name="codeql_data.db"):
+    conn = sqlite3.connect(db_name)
+    cursor = conn.cursor()
+
+    # Create table if it doesn't exist
+    cursor.execute(
+        """
+    CREATE TABLE IF NOT EXISTS configs (
+      function_name TEXT,
+      config TEXT
+    )
+  """
+    )
+
+    with open(csv_file, "r") as file:
+        reader = csv.reader(file)
+        next(reader)
+        for row in reader:
+            if len(row) == 2:
+                cursor.execute(
+                    """
+          INSERT INTO configs (function_name, config)
+          VALUES (?, ?)
+        """,
+                    row,
+                )
+            else:
+                print(f"Skipping invalid row: {row}")
+
+    conn.commit()
+    conn.close()
+
+
+# Example usage:
+import_allocations_to_db("allocations.csv")
+import_functions_to_db("functions.csv")
+import_configs_to_db("configs.csv")
diff --git a/analysis/kernel/dashboard/Data/codeql/import_sarif_db.py b/analysis/kernel/dashboard/Data/codeql/import_sarif_db.py
new file mode 100644
index 00000000..d7f4f98f
--- /dev/null
+++ b/analysis/kernel/dashboard/Data/codeql/import_sarif_db.py
@@ -0,0 +1,154 @@
+from sarif_om import SarifLog
+from sarif.loader import load_sarif_file
+import sqlite3
+
+
+def create_sarif_database(sarif_file_path, db_name="codeql_data.db"):
+    try:
+        sarif_log: SarifLog = load_sarif_file(sarif_file_path)
+    except Exception as e:
+        print(f"Error loading SARIF file: {e}")
+        return None
+
+    conn = sqlite3.connect(db_name)
+    cursor = conn.cursor()
+
+    # Create tables for runs, results, codeFlows, threadFlows, and locations
+    cursor.execute(
+        """
+      CREATE TABLE IF NOT EXISTS runs (
+        id INTEGER PRIMARY KEY AUTOINCREMENT,
+        tool TEXT NOT NULL,
+        version TEXT
+      )
+    """
+    )
+    cursor.execute(
+        """
+      CREATE TABLE IF NOT EXISTS results (
+        id INTEGER PRIMARY KEY AUTOINCREMENT,
+        run_id INTEGER NOT NULL,
+        ruleId TEXT NOT NULL,
+        message TEXT NOT NULL,
+        FOREIGN KEY (run_id) REFERENCES runs (id)
+      )
+    """
+    )
+    cursor.execute(
+        """
+      CREATE TABLE IF NOT EXISTS codeFlows (
+        id INTEGER PRIMARY KEY AUTOINCREMENT,
+        result_id INTEGER NOT NULL,
+        FOREIGN KEY (result_id) REFERENCES results (id)
+      )
+    """
+    )
+    cursor.execute(
+        """
+      CREATE TABLE IF NOT EXISTS threadFlows (
+        id INTEGER PRIMARY KEY AUTOINCREMENT,
+        codeFlow_id INTEGER NOT NULL,
+        FOREIGN KEY (codeFlow_id) REFERENCES codeFlows (id)
+      )
+    """
+    )
+    cursor.execute(
+        """
+      CREATE TABLE IF NOT EXISTS locations (
+        id INTEGER PRIMARY KEY AUTOINCREMENT,
+        threadFlow_id INTEGER NOT NULL,
+        message TEXT NOT NULL,
+        uri TEXT,
+        startLine INTEGER,
+        startColumn INTEGER,
+        endLine INTEGER,
+        endColumn INTEGER,
+        FOREIGN KEY (threadFlow_id) REFERENCES threadFlows (id)
+      )
+    """
+    )
+    cursor.execute(
+        """
+      CREATE TABLE IF NOT EXISTS edges (
+        id INTEGER PRIMARY KEY AUTOINCREMENT,
+        source_location_id INTEGER NOT NULL,
+        target_location_id INTEGER NOT NULL,
+        rule_id TEXT NOT NULL,
+        FOREIGN KEY (source_location_id) REFERENCES locations (id),
+        FOREIGN KEY (target_location_id) REFERENCES locations (id)
+      )
+    """
+    )
+
+    for run in sarif_log.runs:
+        cursor.execute(
+            "INSERT INTO runs (tool, version) VALUES (?, ?)",
+            ("dashboard", "1.0"),
+        )
+        run_id = cursor.lastrowid
+
+        for result in run.get_results():
+            cursor.execute(
+                "INSERT INTO results (run_id, ruleId, message) VALUES (?, ?, ?)",
+                (run_id, result["ruleId"], result["message"]["text"]),
+            )
+            result_id = cursor.lastrowid
+
+            for code_flow in result.get("codeFlows", []):
+                cursor.execute(
+                    "INSERT INTO codeFlows (result_id) VALUES (?)", (result_id,)
+                )
+                code_flow_id = cursor.lastrowid
+
+                for thread_flow in code_flow.get("threadFlows", []):
+                    cursor.execute(
+                        "INSERT INTO threadFlows (codeFlow_id) VALUES (?)",
+                        (code_flow_id,),
+                    )
+                    thread_flow_id = cursor.lastrowid
+
+                    # Store location data in a list of dictionaries
+                    location_ids = []
+
+                    for location in thread_flow.get("locations", []):
+                        loc = location.get("location", {})
+                        phys_loc = loc.get("physicalLocation", {})
+                        art_loc = phys_loc.get("artifactLocation", {})
+                        region = phys_loc.get("region", {})
+                        cursor.execute(
+                            """
+                        INSERT INTO locations (
+                          threadFlow_id, message, uri, startLine, startColumn, endLine, endColumn
+                        ) VALUES (?, ?, ?, ?, ?, ?, ?)
+                      """,
+                            (
+                                thread_flow_id,
+                                loc["message"]["text"],
+                                art_loc.get("uri", ""),
+                                region.get("startLine"),
+                                region.get("startColumn"),
+                                region.get("endLine"),
+                                region.get("endColumn"),
+                            ),
+                        )
+                        location_ids.append(cursor.lastrowid)
+
+                    for i in range(len(location_ids) - 1):
+                        cursor.execute(
+                            """
+                            INSERT INTO edges (source_location_id, target_location_id, rule_id)
+                            VALUES (?, ?, ?)
+                            """,
+                            (
+                                location_ids[i],
+                                location_ids[i + 1],
+                                result["ruleId"],
+                            ),
+                        )
+    conn.commit()
+    conn.close()
+
+
+create_sarif_database("callgraph.sarif")
+# create_sarif_database("field-free.sarif")
+# create_sarif_database("controlled-field-writes.sarif")
diff --git a/analysis/kernel/dashboard/frontend/README.md b/analysis/kernel/dashboard/frontend/README.md
new file mode 100644
index 00000000..2a1fe916
--- /dev/null
+++ b/analysis/kernel/dashboard/frontend/README.md
@@ -0,0 +1,9 @@
+# installing
+```
+npm install
+```
+
+# serving
+```
+npx webpack serve
+```
diff --git a/analysis/kernel/dashboard/frontend/db/download_and_process.sh b/analysis/kernel/dashboard/frontend/db/download_and_process.sh
new file mode 100644
index 00000000..93a28610
--- /dev/null
+++ b/analysis/kernel/dashboard/frontend/db/download_and_process.sh
@@ -0,0 +1,144 @@
+#!/bin/bash
+
+LOGS_DIR=$(mktemp -d)
+GCS_BUCKET=https://storage.googleapis.com/kernelctf-dash
+INIT="
+.bail on
+.stats on
+.eqp full
+.mode line
+.progress 5000000
+.parameter init
+"
+
+RESIZE_DB="
+PRAGMA main.journal_mode = delete;
+PRAGMA main.page_size = 1024;
+PRAGMA main.auto_vacuum = 1;
+VACUUM;
+"
+
+declare -A SQL=(
+    ["git_blame-6.1.111.db"]='
+.parameter set $file_path "net/socket"
+.parameter set $start_line 2770
+.parameter set $end_line 2776
+'
+    ["codeql_data-6.1.db"]='
+.parameter set $file_path "mm/slab_common.c"
+.parameter set $start_line 969
+.parameter set $end_line 989
+.parameter set $syscall accept
+.parameter set $depth 1
+.parameter set $limit 10
+.parameter set $offset 0
+'
+    ["syzkaller-6.1.111.db"]='
+.parameter set $file_path "net/socket"
+.parameter set $start_line 2770
+.parameter set $end_line 2776'
+    ["btf-kernelctf-6_1_111.db"]='
+.parameter set $struct_name "pipe_buffer"
+.parameter set $kmalloc_bucket_name "128"
+.parameter set $kmalloc_cgroup_name NULL
+.parameter set $overlap_end 128
+.parameter set $overlap_end 64
+.parameter set $limit 100
+.parameter set $offset 0
+'
+)
+
+
+function process_db() {
+    set -evx
+    DB="$1";
+    COST="$2";
+    SQL="${SQL[$DB]}";
+    wget -nc "$GCS_BUCKET/$DB" || true;
+    if [[
+        "$DB" == "syzkaller-6.1.111.db" &&
+        "$(sqlite3 $DB 'select count(*) from sqlite_master where type="table" and name="syzk_sys"')" != "1"
+    ]]; then
+        (
+            echo prog_id,syscall;
+            sqlite3 -json "$DB" 'select * from syzk_prog;' | jq -r '.[] | {id: (.prog_id | tostring), syscalls: ([.prog_code | match("((?:\\w+ = )?(?[^$(]+)(?:[$]\\w+)?.+)\n?"; "g") | .captures[1].string] | unique | .[])} | map(.) | @csv'
+        ) > syzkaller_syscalls.csv
+        sqlite3 -csv "$DB" ".import syzkaller_syscalls.csv syzk_sys"
+        rm syzkaller_syscalls.csv
+    fi
+    if [[
+        "$DB" == "btf-kernelctf-6_1_111.db"
+    ]]; then
+        wget -nc "$GCS_BUCKET/allocs.db" || true;
+        wget -nc "$GCS_BUCKET/codeql_data-6.1.db" || true;
+    fi
+    ionice -c 2 -n 0 sqlite3 -init <(echo "$INIT") -echo $DB  ".read ./sql/$DB/prolog.sql";
+    for query in ./sql/$DB/query-*.sql; do
+        tmp=$(mktemp)
+        sqlite3 -init <(
+            echo "$INIT";
+            echo "$SQL";
+        ) -echo $DB ".read $query" | tee $tmp;
+        (echo -n $query:; grep 'Read.. system calls' $tmp | awk '{print $4}') >> $COST
+        rm $tmp
+    done
+    ionice -c 2 -n 0 sqlite3 -init <(echo "$INIT") -echo $DB "PRAGMA optimize;";
+    if [[
+        "$(sqlite3 $DB 'PRAGMA page_size')" != "1024" ||
+        "$(sqlite3 $DB 'PRAGMA journal_mode')" != "delete" ||
+        "$(sqlite3 $DB 'PRAGMA auto_vacuum')" != "1"
+    ]]; then
+        ionice -c 2 -n 0 sqlite3 -echo $DB "$RESIZE_DB"
+    fi
+}
+
+declare -A PID2DB
+for db in "${!SQL[@]}"; do
+    touch $LOGS_DIR/$db.{log,err,cost,fin}
+    (process_db "$db" "$LOGS_DIR/$db.cost" 1>>$LOGS_DIR/$db.log 2>>$LOGS_DIR/$db.err) &
+    pid=$!
+    PID2DB[$pid]=$db
+    echo RUNNING > $LOGS_DIR/$db.run
+done
+
+tail -f -n +0 $LOGS_DIR/* &
+TAIL_PROC=$!
+
+FAIL=""
+for pid in "${!PID2DB[@]}"; do
+    wait $pid
+    ERR=$?
+    if [ $ERR -ne 0 ]; then
+        FAIL="$FAIL ${PID2DB[$pid]}"
+    fi
+    echo "________FINISHED:$ERR" > $LOGS_DIR/${PID2DB[$pid]}.run
+    grep -v FINISHED $LOGS_DIR/*.run > $LOGS_DIR/${PID2DB[$pid]}.fin
+done
+
+kill $TAIL_PROC
+echo
+echo
+
+if [ "$FAIL" != "" ]; then
+    echo "ERROR (logs: $LOGS_DIR)"
+    echo "Processing failed for $FAIL."
+    for db in $FAIL; do
+        echo "About to print $db STDOUT and STDERR from $LOGS_DIR"
+        cat $LOGS_DIR/$db.run
+        echo "Press enter to continue"
+        read
+        echo "STDOUT $db"
+        cat $LOGS_DIR/$db.log
+        echo "STDERR $db"
+        cat $LOGS_DIR/$db.err
+    done
+    echo "Press enter to continue (will delete logs on $LOGS_DIR)"
+    read
+else
+    echo "All processing completed successfully."
+    echo
+    echo Query costs:
+    cat $LOGS_DIR/*.cost
+fi
+
+rm -rf $LOGS_DIR
diff --git a/analysis/kernel/dashboard/frontend/db/sql/btf-kernelctf-6_1_111.db/prolog.sql b/analysis/kernel/dashboard/frontend/db/sql/btf-kernelctf-6_1_111.db/prolog.sql
new file mode 100644
index 00000000..981c5e79
--- /dev/null
+++ b/analysis/kernel/dashboard/frontend/db/sql/btf-kernelctf-6_1_111.db/prolog.sql
@@ -0,0 +1,585 @@
+ATTACH DATABASE 'allocs.db' AS allocs;
+ATTACH DATABASE 'codeql_data-6.1.db' AS codeql;
+
+CREATE TABLE IF NOT EXISTS kmalloc_bucket AS
+SELECT
+    column1 AS min,
+    column2 AS max,
+    column3 AS name
+FROM (
+    VALUES
+        (1,     8,      '8'),
+        (9,     16,     '16'),
+        (17,    32,     '32'),
+        (33,    64,     '64'),
+        (65,    96,     '96'),
+        (97,    128,    '128'),
+        (129,   192,    '192'),
+        (193,   256,    '256'),
+        (257,   512,    '512'),
+        (513,   1024,   '1k'),
+        (1025,  2048,   '2k'),
+        (2049,  4096,   '4k'),
+        (4097,  8192,   '8k')
+);
+
+CREATE TABLE IF NOT EXISTS kmalloc_cgroup AS
+SELECT
+    column1 AS mask,
+    column2 AS name
+FROM (
+    VALUES
+        (/*GFP_KERNEL_ACCOUNT =*/ (
+            /*GFP_KERNEL*/ (
+                /*GFP_RECLAIM*/ (
+                    /*GFP_DIRECT_RECLAIM*/ 0x400 |
+                    /*GFP_KSWAPD_RECLAIM*/ 0x800
+                ) |
+                /*GFP_IO*/ 0x40 |
+                /*GFP_FS*/ 0x80
+            ) |
+            /*GFP_ACCOUNT*/ (
+                0x100000
+            )
+        ), 'cg')
+);
+
+CREATE TABLE IF NOT EXISTS function_locations_clean AS
+SELECT DISTINCT
+    file_path,
+    start_line,
+    end_line,
+    function_name
+FROM
+    function_locations;
+
+CREATE INDEX IF NOT EXISTS function_locations_clean_tmp ON function_locations_clean (
+    file_path,
+    start_line,
+    end_line,
+    function_name
+);
+
+CREATE TABLE IF NOT EXISTS syscall_node_clean AS
+SELECT DISTINCT
+    substr(function_location, 0, instr(function_location, ':')) function_file_path,
+    function,
+    syscall
+FROM
+    syscall_node;
+
+CREATE INDEX IF NOT EXISTS syscall_node_clean_tmp ON syscall_node_clean (
+    function_file_path,
+    function,
+    syscall
+);
+
+CREATE TABLE IF NOT EXISTS function_to_syscall AS
+SELECT
+    function_locations_clean.file_path,
+    function_locations_clean.start_line,
+    function_locations_clean.end_line,
+    COUNT(DISTINCT syscall) syscalls_num
+FROM
+    syscall_node_clean INDEXED BY syscall_node_clean_tmp
+LEFT JOIN
+    function_locations_clean INDEXED BY function_locations_clean_tmp
+    ON (
+        syscall_node_clean.function_file_path = function_locations_clean.file_path AND
+        syscall_node_clean.function = function_locations_clean.function_name
+    )
+GROUP BY
+    file_path,
+    start_line,
+    end_line;
+
+CREATE INDEX IF NOT EXISTS function_to_syscall_tmp ON function_to_syscall (
+    file_path,
+    start_line,
+    end_line,
+    syscalls_num
+);
+
+CREATE TABLE IF NOT EXISTS fields_with_allocs AS
+SELECT DISTINCT
+    struct_name,
+    struct_size,
+    parent_type,
+    kind,
+    type,
+    RTRIM(SUBSTR(
+        types.name,
+        LENGTH(RTRIM(
+            types.name, REPLACE(REPLACE(
+                types.name, '/', ''
+            ), '.', '')
+        )) + 1),
+        '[0123456789]'
+    ) field_name,
+    types.name full_field_name,
+    bits_offset,
+    nr_bits,
+    bits_end,
+    is_flex,
+    CAST(allocSizeMax_value AS decimal) allocSizeMax,
+    CAST(allocSizeMin_value AS decimal) allocSizeMin,
+    CAST(allocSize_value AS decimal) allocSize,
+    call_startColumn,
+    call_startLine,
+    call_uri,
+    call_value,
+    CAST(depth_value AS decimal) depth,
+    CAST(flagsMax_value AS decimal) flagsMax,
+    CAST(flagsMin_value AS decimal) flagsMin,
+    CAST(flags_value AS decimal) flags,
+    type_startColumn,
+    type_startLine,
+    type_uri,
+    type_value,
+    function_locations.function_name function,
+    function_locations.start_line function_start_line,
+    function_locations.end_line function_end_line,
+    syscall_node_clean.syscall syscall,
+    kmalloc_cgroup.name kmalloc_cgroup_name,
+    kmalloc_bucket.name kmalloc_bucket_name,
+    kmalloc_bucket.min kmalloc_bucket_min,
+    kmalloc_bucket.max kmalloc_bucket_max,
+    (allocSizeMax_value <> allocSizeMin_value) kmalloc_dyn
+FROM
+    types
+LEFT JOIN allocs ON (
+    type_value = struct_name AND
+    CAST(objectSize_value AS decimal) = struct_size
+)
+LEFT JOIN function_locations_clean AS function_locations INDEXED BY function_locations_clean_tmp ON (
+    function_locations.file_path = allocs.call_uri
+    AND function_locations.start_line <= allocs.call_startLine
+    AND function_locations.end_line >= allocs.call_startLine
+)
+LEFT JOIN syscall_node_clean INDEXED BY syscall_node_clean_tmp ON (
+    function_locations.file_path = syscall_node_clean.function_file_path
+    AND function_locations.function_name = syscall_node_clean.function
+)
+LEFT JOIN kmalloc_bucket ON (
+    allocSizeMax >= kmalloc_bucket.min AND
+    allocSizeMin <= kmalloc_bucket.max
+)
+LEFT JOIN kmalloc_cgroup ON (
+    kmalloc_cgroup.mask BETWEEN flagsMin AND flagsMax
+)
+WHERE
+    struct_name <> '(anon)';
+
+CREATE TABLE IF NOT EXISTS field_access_clean AS
+SELECT DISTINCT
+    field_access.type field_access_type,
+    SUBSTR(field_access.location, 0, instr(field_access.location, ':')) field_access_uri,
+    1 * SUBSTR(field_access.location, instr(field_access.location, ':') + 1) field_access_start_line,
+    field_access.field field_access_field,
+    field_access.parent field_access_parent
+FROM
+    field_access
+WHERE
+    parent NOT LIKE '(%';
+
+CREATE INDEX IF NOT EXISTS field_access_clean_tmp ON field_access_clean (
+    field_access_field,
+    field_access_parent,
+    field_access_uri,
+    field_access_start_line,
+    field_access_type
+);
+
+CREATE INDEX IF NOT EXISTS fields_with_allocs_tmp ON fields_with_allocs (
+    field_name,
+    parent_type,
+
+    struct_name,
+    struct_size,
+    kind,
+    type,
+    full_field_name,
+    bits_offset,
+    nr_bits,
+    bits_end,
+    is_flex,
+    allocSizeMax,
+    allocSizeMin,
+    allocSize,
+    call_startColumn,
+    call_startLine,
+    call_uri,
+    call_value,
+    depth,
+    flagsMax,
+    flagsMin,
+    flags,
+    type_startColumn,
+    type_startLine,
+    type_uri,
+    type_value,
+    kmalloc_cgroup_name,
+    kmalloc_bucket_name,
+    kmalloc_bucket_min,
+    kmalloc_bucket_max,
+    kmalloc_dyn
+);
+
+CREATE TABLE IF NOT EXISTS fields_with_allocs_and_access AS
+SELECT DISTINCT
+    field_access_clean.field_access_type field_access_type,
+    field_access_clean.field_access_uri field_access_uri,
+    field_access_clean.field_access_start_line field_access_start_line,
+    fields_with_allocs.struct_name struct_name,
+    fields_with_allocs.struct_size struct_size,
+    fields_with_allocs.parent_type parent_type,
+    fields_with_allocs.kind kind,
+    fields_with_allocs.type type,
+    fields_with_allocs.field_name field_name,
+    fields_with_allocs.full_field_name full_field_name,
+    fields_with_allocs.bits_offset bits_offset,
+    fields_with_allocs.nr_bits nr_bits,
+    fields_with_allocs.bits_end bits_end,
+    fields_with_allocs.is_flex is_flex,
+    fields_with_allocs.allocSizeMax allocSizeMax,
+    fields_with_allocs.allocSizeMin allocSizeMin,
+    fields_with_allocs.allocSize allocSize,
+    fields_with_allocs.call_startColumn call_startColumn,
+    fields_with_allocs.call_startLine call_startLine,
+    fields_with_allocs.call_uri call_uri,
+    fields_with_allocs.call_value call_value,
+    fields_with_allocs.depth depth,
+    fields_with_allocs.flagsMax flagsMax,
+    fields_with_allocs.flagsMin flagsMin,
+    fields_with_allocs.flags flags,
+    fields_with_allocs.type_startColumn type_startColumn,
+    fields_with_allocs.type_startLine type_startLine,
+    fields_with_allocs.type_uri type_uri,
+    fields_with_allocs.type_value type_value,
+    fields_with_allocs.kmalloc_cgroup_name kmalloc_cgroup_name,
+    fields_with_allocs.kmalloc_bucket_name kmalloc_bucket_name,
+    fields_with_allocs.kmalloc_bucket_min kmalloc_bucket_min,
+    fields_with_allocs.kmalloc_bucket_max kmalloc_bucket_max,
+    fields_with_allocs.kmalloc_dyn kmalloc_dyn
+FROM
+    field_access_clean INDEXED BY field_access_clean_tmp
+INNER JOIN
+    fields_with_allocs INDEXED BY fields_with_allocs_tmp ON (
+        fields_with_allocs.field_name=field_access_clean.field_access_field AND
+        fields_with_allocs.parent_type=field_access_clean.field_access_parent
+    )
+WHERE
+    kmalloc_bucket_name IS NOT NULL;
+
+CREATE INDEX IF NOT EXISTS fields_with_allocs_and_access_tmp ON fields_with_allocs_and_access (
+    field_access_uri,
+    field_access_start_line,
+
+    field_access_type,
+    struct_name,
+    type,
+    full_field_name,
+    bits_offset,
+    bits_end,
+    kmalloc_cgroup_name,
+    kmalloc_bucket_name,
+    kmalloc_dyn
+);
+
+CREATE TABLE IF NOT EXISTS fields_with_allocs_and_access_and_functions AS
+SELECT
+    field_access_type,
+    field_access_uri,
+    field_access_start_line,
+    struct_name,
+    type,
+    full_field_name,
+    bits_offset,
+    bits_end,
+    kmalloc_cgroup_name,
+    kmalloc_bucket_name,
+    kmalloc_dyn,
+    function_to_syscall.file_path function_file_path,
+    function_to_syscall.start_line function_start_line,
+    function_to_syscall.end_line function_end_line,
+    function_to_syscall.syscalls_num
+FROM
+    fields_with_allocs_and_access INDEXED BY fields_with_allocs_and_access_tmp
+INNER JOIN function_to_syscall INDEXED BY function_to_syscall_tmp ON (
+    field_access_uri = function_to_syscall.file_path AND
+    field_access_start_line BETWEEN function_to_syscall.start_line AND function_to_syscall.end_line
+)
+GROUP BY
+    field_access_type,
+    field_access_uri,
+    field_access_start_line,
+    struct_name,
+    type,
+    full_field_name,
+    bits_offset,
+    bits_end,
+    kmalloc_cgroup_name,
+    kmalloc_bucket_name,
+    kmalloc_dyn,
+    function_file_path,
+    function_start_line,
+    function_end_line;
+
+CREATE INDEX IF NOT EXISTS getAccessByCache ON fields_with_allocs_and_access_and_functions (
+    kmalloc_bucket_name,
+    kmalloc_cgroup_name,
+    bits_end,
+    bits_offset,
+    struct_name,
+    type,
+    full_field_name,
+    field_access_type,
+    field_access_start_line,
+    function_file_path,
+    function_start_line,
+    function_end_line,
+    syscalls_num
+);
+
+CREATE INDEX IF NOT EXISTS types_tmp ON types (
+    RTRIM(SUBSTR(
+        name,
+        LENGTH(RTRIM(
+            name, REPLACE(REPLACE(
+                name, '/', ''
+            ), '.', '')
+        )) + 1),
+        '[0123456789]'
+    ),
+    parent_type,
+    type,
+    bits_offset,
+    bits_end,
+    name
+);
+
+CREATE TABLE IF NOT EXISTS fields_with_access AS
+SELECT DISTINCT
+    field_access_clean.field_access_type field_access_type,
+    field_access_clean.field_access_uri field_access_uri,
+    field_access_clean.field_access_start_line field_access_start_line,
+    types.struct_name struct_name,
+    types.type type,
+    types.name full_field_name,
+    types.bits_offset bits_offset,
+    types.bits_end bits_end,
+    function_to_syscall.file_path function_file_path,
+    function_to_syscall.start_line function_start_line,
+    function_to_syscall.end_line function_end_line,
+    function_to_syscall.syscalls_num syscalls_num
+FROM
+    types INDEXED BY types_tmp
+INNER JOIN
+    field_access_clean INDEXED BY field_access_clean_tmp ON (
+        field_access_field = RTRIM(SUBSTR(
+            types.name,
+            LENGTH(RTRIM(
+                types.name, REPLACE(REPLACE(
+                    types.name, '/', ''
+                ), '.', '')
+            )) + 1),
+            '[0123456789]'
+        ) AND
+        field_access_parent = types.parent_type
+    )
+INNER JOIN function_to_syscall INDEXED BY function_to_syscall_tmp ON (
+    field_access_uri = function_to_syscall.file_path AND
+    field_access_start_line BETWEEN function_to_syscall.start_line AND function_to_syscall.end_line
+);
+
+CREATE INDEX IF NOT EXISTS getAccessByStruct ON fields_with_access (
+    struct_name,
+    bits_end,
+    bits_offset,
+
+    type,
+    full_field_name,
+    bits_offset,
+    bits_end,
+    field_access_type,
+    field_access_start_line,
+    function_file_path,
+    function_start_line,
+    function_end_line,
+    syscalls_num
+);
+
+CREATE TABLE IF NOT EXISTS structs_with_allocs AS
+SELECT DISTINCT
+    struct_name,
+    parent_type,
+    struct_size,
+    allocSizeMax,
+    allocSizeMin,
+    allocSize,
+    call_startLine,
+    call_uri,
+    call_value,
+    depth,
+    flagsMax,
+    flagsMin,
+    flags,
+    function,
+    function_start_line,
+    function_end_line,
+    syscall,
+    kmalloc_cgroup_name,
+    kmalloc_bucket_name,
+    kmalloc_bucket_min,
+    kmalloc_bucket_max,
+    kmalloc_dyn
+FROM
+    fields_with_allocs;
+
+CREATE TABLE IF NOT EXISTS structs_with_allocs_search AS
+SELECT
+    struct_name struct_or_parent,
+    struct_name,
+    struct_size,
+    allocSizeMax,
+    allocSizeMin,
+    allocSize,
+    call_startLine,
+    call_uri,
+    call_value,
+    depth,
+    flagsMax,
+    flagsMin,
+    flags,
+    function,
+    function_start_line,
+    function_end_line,
+    syscall,
+    kmalloc_cgroup_name,
+    kmalloc_bucket_name,
+    kmalloc_bucket_min,
+    kmalloc_bucket_max,
+    kmalloc_dyn
+FROM structs_with_allocs
+UNION
+SELECT
+    parent_type struct_or_parent,
+    struct_name,
+    struct_size,
+    allocSizeMax,
+    allocSizeMin,
+    allocSize,
+    call_startLine,
+    call_uri,
+    call_value,
+    depth,
+    flagsMax,
+    flagsMin,
+    flags,
+    function,
+    function_start_line,
+    function_end_line,
+    syscall,
+    kmalloc_cgroup_name,
+    kmalloc_bucket_name,
+    kmalloc_bucket_min,
+    kmalloc_bucket_max,
+    kmalloc_dyn
+FROM structs_with_allocs;
+
+CREATE TABLE IF NOT EXISTS structs_with_allocs_syscall_num AS
+SELECT
+    struct_name,
+    struct_size,
+    allocSizeMax,
+    allocSizeMin,
+    allocSize,
+    call_startLine,
+    call_uri,
+    call_value,
+    depth,
+    flagsMax,
+    flagsMin,
+    flags,
+    function,
+    function_start_line,
+    function_end_line,
+    COUNT(DISTINCT syscall) FILTER (WHERE syscall IS NOT NULL) syscalls_num,
+    kmalloc_bucket_name,
+    kmalloc_cgroup_name,
+    kmalloc_dyn
+FROM
+    structs_with_allocs
+GROUP BY
+    struct_name,
+    struct_size,
+    allocSizeMax,
+    allocSizeMin,
+    allocSize,
+    call_startLine,
+    call_uri,
+    call_value,
+    depth,
+    flagsMax,
+    flagsMin,
+    flags,
+    function,
+    function_start_line,
+    function_end_line,
+    kmalloc_bucket_name,
+    kmalloc_cgroup_name,
+    kmalloc_dyn;
+
+CREATE INDEX IF NOT EXISTS getStructsByStructName ON structs_with_allocs_search (
+    struct_or_parent,
+    struct_name,
+    struct_size,
+    allocSizeMax,
+    allocSizeMin,
+    allocSize,
+    call_startLine,
+    call_uri,
+    call_value,
+    depth,
+    flagsMax,
+    flagsMin,
+    flags,
+    function,
+    function_start_line,
+    function_end_line,
+    syscall,
+    kmalloc_cgroup_name,
+    kmalloc_bucket_name,
+    kmalloc_dyn
+);
+
+CREATE INDEX IF NOT EXISTS getStructsByAllocation ON structs_with_allocs_syscall_num (
+    kmalloc_bucket_name,
+    kmalloc_cgroup_name,
+    kmalloc_dyn,
+    allocSizeMin,
+    allocSizeMax,
+    flagsMin,
+    flagsMax,
+    allocSize,
+    flags,
+    struct_name,
+    struct_size,
+    call_startLine,
+    call_uri,
+    call_value,
+    depth,
+    function,
+    function_start_line,
+    function_end_line,
+    syscalls_num
+);
+
+CREATE INDEX IF NOT EXISTS getFieldsByStructName ON types (
+    struct_name,
+    bits_offset,
+    name,
+    parent_type,
+    bits_end,
+    type
+);
diff --git a/analysis/kernel/dashboard/frontend/db/sql/btf-kernelctf-6_1_111.db/query-Btf_getAccessByCache.sql b/analysis/kernel/dashboard/frontend/db/sql/btf-kernelctf-6_1_111.db/query-Btf_getAccessByCache.sql
new file mode 100644
index 00000000..a496fb70
--- /dev/null
+++ b/analysis/kernel/dashboard/frontend/db/sql/btf-kernelctf-6_1_111.db/query-Btf_getAccessByCache.sql
@@ -0,0 +1,21 @@
+SELECT
+    struct_name,
+    type,
+    full_field_name,
+    bits_offset,
+    bits_end,
+    field_access_type,
+    field_access_start_line,
+    function_file_path,
+    function_start_line,
+    function_end_line,
+    syscalls_num
+FROM
+    fields_with_allocs_and_access_and_functions
+WHERE
+    kmalloc_bucket_name = $kmalloc_bucket_name AND
+    kmalloc_cgroup_name is $kmalloc_cgroup_name AND
+    bits_end >= $overlap_start AND
+    bits_offset <= $overlap_end
+LIMIT $limit
+OFFSET $offset;
diff --git a/analysis/kernel/dashboard/frontend/db/sql/btf-kernelctf-6_1_111.db/query-Btf_getAccessByStruct.sql b/analysis/kernel/dashboard/frontend/db/sql/btf-kernelctf-6_1_111.db/query-Btf_getAccessByStruct.sql
new file mode 100644
index 00000000..df35fddd
--- /dev/null
+++ b/analysis/kernel/dashboard/frontend/db/sql/btf-kernelctf-6_1_111.db/query-Btf_getAccessByStruct.sql
@@ -0,0 +1,20 @@
+SELECT
+    struct_name,
+    type,
+    full_field_name,
+    bits_offset,
+    bits_end,
+    field_access_type,
+    field_access_start_line,
+    function_file_path,
+    function_start_line,
+    function_end_line,
+    syscalls_num
+FROM
+    fields_with_access
+WHERE
+    struct_name = $struct_name AND
+    bits_end >= $overlap_start AND
+    bits_offset <= $overlap_end
+LIMIT $limit
+OFFSET $offset;
diff --git a/analysis/kernel/dashboard/frontend/db/sql/btf-kernelctf-6_1_111.db/query-Btf_getFieldsByStructName.sql b/analysis/kernel/dashboard/frontend/db/sql/btf-kernelctf-6_1_111.db/query-Btf_getFieldsByStructName.sql
new file mode 100644
index 00000000..d8966f92
--- /dev/null
+++ b/analysis/kernel/dashboard/frontend/db/sql/btf-kernelctf-6_1_111.db/query-Btf_getFieldsByStructName.sql
@@ -0,0 +1,12 @@
+SELECT
+    bits_offset,
+    bits_end,
+    type,
+    parent_type,
+    name
+FROM types
+WHERE
+    struct_name = $struct_name
+ORDER BY
+    bits_offset ASC,
+    name ASC;
\ No newline at end of file
diff --git a/analysis/kernel/dashboard/frontend/db/sql/btf-kernelctf-6_1_111.db/query-Btf_getStructsByAllocation.sql b/analysis/kernel/dashboard/frontend/db/sql/btf-kernelctf-6_1_111.db/query-Btf_getStructsByAllocation.sql
new file mode 100644
index 00000000..1df519a7
--- /dev/null
+++ b/analysis/kernel/dashboard/frontend/db/sql/btf-kernelctf-6_1_111.db/query-Btf_getStructsByAllocation.sql
@@ -0,0 +1,27 @@
+SELECT
+    struct_name,
+    struct_size,
+    allocSizeMax,
+    allocSizeMin,
+    allocSize,
+    call_startLine,
+    call_uri,
+    call_value,
+    depth,
+    flagsMax,
+    flagsMin,
+    flags,
+    function,
+    function_start_line,
+    function_end_line,
+    syscalls_num,
+    kmalloc_bucket_name,
+    kmalloc_cgroup_name,
+    kmalloc_dyn
+FROM
+    structs_with_allocs_syscall_num
+WHERE
+    kmalloc_bucket_name = $kmalloc_bucket_name AND
+    kmalloc_cgroup_name IS $kmalloc_cgroup_name
+LIMIT $limit
+OFFSET $offset;
\ No newline at end of file
diff --git a/analysis/kernel/dashboard/frontend/db/sql/btf-kernelctf-6_1_111.db/query-Btf_getStructsByStructName.sql b/analysis/kernel/dashboard/frontend/db/sql/btf-kernelctf-6_1_111.db/query-Btf_getStructsByStructName.sql
new file mode 100644
index 00000000..46fcb132
--- /dev/null
+++ b/analysis/kernel/dashboard/frontend/db/sql/btf-kernelctf-6_1_111.db/query-Btf_getStructsByStructName.sql
@@ -0,0 +1,43 @@
+SELECT
+    struct_name,
+    struct_size,
+    allocSizeMax,
+    allocSizeMin,
+    allocSize,
+    call_startLine,
+    call_uri,
+    call_value,
+    depth,
+    flagsMax,
+    flagsMin,
+    flags,
+    function,
+    function_start_line,
+    function_end_line,
+    COUNT(DISTINCT syscall) FILTER (WHERE syscall IS NOT NULL) syscalls_num,
+    kmalloc_bucket_name,
+    kmalloc_cgroup_name,
+    kmalloc_dyn
+FROM
+    structs_with_allocs_search
+WHERE
+    $struct_name = struct_or_parent
+GROUP BY
+    struct_name,
+    struct_size,
+    allocSizeMax,
+    allocSizeMin,
+    allocSize,
+    call_startLine,
+    call_uri,
+    call_value,
+    depth,
+    flagsMax,
+    flagsMin,
+    flags,
+    function,
+    function_start_line,
+    function_end_line,
+    kmalloc_bucket_name,
+    kmalloc_cgroup_name,
+    kmalloc_dyn;
\ No newline at end of file
diff --git a/analysis/kernel/dashboard/frontend/db/sql/codeql_data-6.1.db/prolog.sql b/analysis/kernel/dashboard/frontend/db/sql/codeql_data-6.1.db/prolog.sql
new file mode 100644
index 00000000..e5c98931
--- /dev/null
+++ b/analysis/kernel/dashboard/frontend/db/sql/codeql_data-6.1.db/prolog.sql
@@ -0,0 +1,274 @@
+CREATE TABLE IF NOT EXISTS edges_with_functions AS
+SELECT rule_id,
+    group_concat(distinct target_function.function_name) target_function_names,
+    min(target_function.start_line) target_function_start_line,
+    max(target_function.end_line) target_function_end_line,
+    group_concat(distinct source_function.function_name) source_function_names,
+    min(source_function.start_line) source_function_start_line,
+    max(source_function.end_line) source_function_end_line,
+    targets.message target_message,
+    targets.uri target_uri,
+    targets.startLine target_startLine,
+    targets.startColumn target_startColumn,
+    targets.endLine target_endLine,
+    targets.endColumn target_endColumn,
+    sources.message source_message,
+    sources.uri source_uri,
+    sources.startLine source_startLine,
+    sources.startColumn source_startColumn,
+    sources.endLine source_endLine,
+    sources.endColumn source_endColumn
+FROM edges
+    JOIN locations targets ON (targets.id = edges.target_location_id)
+    JOIN locations sources ON (sources.id = edges.source_location_id)
+    LEFT JOIN function_locations target_function ON (
+        target_function.file_path = targets.uri
+        AND target_function.function_name = targets.message
+    )
+    LEFT JOIN function_locations source_function ON (
+        source_function.file_path = sources.uri
+        AND source_function.start_line <= sources.startLine
+        AND source_function.end_line >= sources.startLine
+    )
+GROUP BY rule_id,
+    target_message,
+    target_uri,
+    target_startLine,
+    target_startColumn,
+    target_endLine,
+    target_endColumn,
+    source_message,
+    source_uri,
+    source_startLine,
+    source_startColumn,
+    source_endLine,
+    source_endColumn;
+
+CREATE TABLE IF NOT EXISTS syscall_node_with_functions AS
+SELECT DISTINCT
+    syscall,
+    syscall_location,
+    function,
+    substr(function_location, 0, instr(function_location, ':')) function_file_path,
+    start_line function_start_line,
+    end_line function_end_line
+FROM syscall_node
+JOIN function_locations ON (
+    file_path = function_file_path
+    AND function_name = function
+);
+
+CREATE TABLE IF NOT EXISTS syscall_all AS
+SELECT DISTINCT
+    SUBSTR(syscall, length('__do_sys_') + 1) syscall
+FROM syscall_node;
+
+CREATE TABLE IF NOT EXISTS edges_with_functions_per_syscall AS
+SELECT DISTINCT
+    s1.syscall syscall,
+    s1.syscall_location syscall_location,
+    rule_id,
+    target_function_names,
+    target_function_start_line,
+    target_function_end_line,
+    source_function_names,
+    source_function_start_line,
+    source_function_end_line,
+    target_message,
+    target_uri,
+    target_startLine,
+    target_startColumn,
+    target_endLine,
+    target_endColumn,
+    source_message,
+    source_uri,
+    source_startLine,
+    source_startColumn,
+    source_endLine,
+    source_endColumn
+FROM
+    edges_with_functions
+JOIN
+    syscall_node_with_functions s1 ON (
+        rule_id = 'callgraph-all'
+        AND source_uri = s1.function_file_path
+        AND source_function_start_line = s1.function_start_line
+    )
+JOIN
+    syscall_node_with_functions s2 ON (
+        s1.syscall = s2.syscall
+        AND target_uri = s2.function_file_path
+        AND target_function_start_line = s2.function_start_line
+    );
+
+CREATE TABLE IF NOT EXISTS conditions_split AS
+SELECT DISTINCT
+    type,
+    argument,
+    SUBSTR(call_location, 0, INSTR(call_location, ':')) call_file_path,
+    1 * SUBSTR(call_location, INSTR(call_location, ':') + 1) call_line,
+    call_location
+FROM conditions;
+
+CREATE TABLE IF NOT EXISTS syscall_condition_node AS
+SELECT DISTINCT
+    syscall_node_with_functions.syscall,
+    conditions_split.type,
+    conditions_split.argument,
+    conditions_split.call_file_path,
+    conditions_split.call_line,
+    function_locations.file_path function_file_path,
+    function_locations.start_line function_start_line,
+    function_locations.end_line function_end_line
+FROM
+    conditions_split
+INNER JOIN
+    syscall_node_with_functions ON (
+        conditions_split.call_file_path = syscall_node_with_functions.function_file_path AND
+        conditions_split.call_line BETWEEN syscall_node_with_functions.function_start_line AND syscall_node_with_functions.function_end_line
+    )
+LEFT JOIN
+    conditions_node ON (
+        conditions_node.conditions_location = conditions_split.call_location
+    )
+LEFT JOIN
+    function_locations ON (
+        function_locations.file_path = substr(conditions_node.function_location, 0, instr(conditions_node.function_location, ':')) AND
+        function_locations.function_name = conditions_node.function
+    );
+
+CREATE TABLE IF NOT EXISTS syscall_condition_node_with_functions AS
+SELECT DISTINCT
+    SUBSTR(syscall, LENGTH('__do_sys_') + 1) syscall,
+    type,
+    argument,
+    function_locations.file_path function_call_file_path,
+    function_locations.start_line function_call_start_line,
+    function_locations.end_line function_call_end_line,
+    function_file_path,
+    function_start_line,
+    function_end_line
+FROM
+    syscall_condition_node
+LEFT JOIN function_locations ON (
+    function_locations.file_path = syscall_condition_node.call_file_path AND
+    syscall_condition_node.call_line BETWEEN function_locations.start_line AND function_locations.end_line
+);
+
+CREATE TABLE IF NOT EXISTS macro_edges AS
+SELECT DISTINCT
+    macro_name,
+    macroinvocation_locations.file_path edge_file_path,
+    macroinvocation_locations.start_line edge_start_line,
+    macro_locations.file_path macro_file_path,
+    macro_locations.start_line macro_start_line,
+    macro_locations.end_line macro_end_line
+FROM macroinvocation_locations
+JOIN macro_locations ON (macroinvocation_name=macro_name);
+
+CREATE INDEX IF NOT EXISTS getConditionsByFileLine1 ON syscall_condition_node_with_functions (
+    function_file_path,
+    function_start_line,
+    syscall,
+    type,
+    argument,
+    function_call_file_path,
+    function_call_start_line,
+    function_call_end_line
+);
+
+CREATE INDEX IF NOT EXISTS getConditionsByFileLine2 ON syscall_condition_node_with_functions (
+    function_file_path,
+    function_end_line,
+    syscall,
+    type,
+    argument,
+    function_call_file_path,
+    function_call_start_line,
+    function_call_end_line
+);
+
+CREATE INDEX IF NOT EXISTS getSyscallParentEdgesByFileLine ON edges_with_functions_per_syscall (
+    syscall,
+    target_uri,
+    target_function_end_line,
+    source_uri,
+    source_function_end_line
+);
+
+CREATE INDEX IF NOT EXISTS getChildEdgesByFileLine ON edges_with_functions (
+    rule_id,
+    source_uri,
+    source_startLine,
+    source_message,
+    target_startLine,
+    target_uri,
+    target_function_end_line
+);
+
+CREATE INDEX IF NOT EXISTS getParentEdgesByFileLine1 ON edges_with_functions_per_syscall (
+    rule_id,
+    target_uri,
+    target_startLine,
+    target_function_end_line,
+    source_uri,
+    source_function_start_line,
+    source_function_end_line,
+    source_function_names,
+    syscall
+);
+
+CREATE INDEX IF NOT EXISTS getParentEdgesByFileLine2 ON edges_with_functions_per_syscall (
+    rule_id,
+    target_uri,
+    target_function_end_line,
+    target_startLine,
+    source_uri,
+    source_function_start_line,
+    source_function_end_line,
+    source_function_names,
+    syscall
+);
+
+CREATE INDEX IF NOT EXISTS getSyscallsByFileLine ON syscall_node_with_functions (
+    function_file_path,
+    function_start_line,
+    function_end_line,
+    syscall
+);
+
+CREATE INDEX IF NOT EXISTS getSyscallParentEdgesByFileLine ON edges_with_functions_per_syscall (
+    syscall,
+    target_uri,
+    target_function_end_line,
+    source_uri,
+    source_function_end_line
+);
+
+CREATE INDEX IF NOT EXISTS getAllEdgesFromFileLine_ops ON ops_targets (
+    exprcall_file,
+    exprcall_line,
+    field,
+    target_file,
+    target_start,
+    target_end
+);
+
+CREATE INDEX IF NOT EXISTS getAllEdgesFromFileLine_edges ON edges_with_functions (
+    rule_id,
+    source_uri,
+    source_startLine,
+    source_message,
+    target_uri,
+    target_startLine,
+    target_function_end_line
+);
+
+CREATE INDEX IF NOT EXISTS getAllEdgesFromFileLine_macros ON macro_edges (
+    edge_file_path,
+    edge_start_line,
+    macro_name,
+    macro_file_path,
+    macro_start_line,
+    macro_end_line
+);
\ No newline at end of file
diff --git a/analysis/kernel/dashboard/frontend/db/sql/codeql_data-6.1.db/query-Paths_getAllEdgesFromFileLine.sql b/analysis/kernel/dashboard/frontend/db/sql/codeql_data-6.1.db/query-Paths_getAllEdgesFromFileLine.sql
new file mode 100644
index 00000000..e9572b8d
--- /dev/null
+++ b/analysis/kernel/dashboard/frontend/db/sql/codeql_data-6.1.db/query-Paths_getAllEdgesFromFileLine.sql
@@ -0,0 +1,34 @@
+SELECT DISTINCT
+    'indirect call' edge_type,
+    field identifier,
+    target_file file_path,
+    target_start start_line,
+    target_end end_line
+FROM
+    ops_targets
+WHERE
+    exprcall_file = $file_path AND
+    exprcall_line = $start_line
+UNION
+SELECT DISTINCT
+    'direct call' edge_type,
+    source_message identifier,
+    target_uri file_path,
+    target_startLine start_line,
+    target_function_end_line end_line
+FROM edges_with_functions
+WHERE
+    rule_id = 'callgraph-all' AND
+    source_uri = $file_path AND
+    source_startLine = $start_line
+UNION
+SELECT DISTINCT
+    'macro call' edge_type,
+    macro_name identifier,
+    macro_file_path file_path,
+    macro_start_line start_line,
+    macro_end_line end_line
+FROM macro_edges
+WHERE
+    edge_file_path = $file_path AND
+    edge_start_line = $start_line;
\ No newline at end of file
diff --git a/analysis/kernel/dashboard/frontend/db/sql/codeql_data-6.1.db/query-Paths_getAllSyscalls.sql b/analysis/kernel/dashboard/frontend/db/sql/codeql_data-6.1.db/query-Paths_getAllSyscalls.sql
new file mode 100644
index 00000000..67e356b4
--- /dev/null
+++ b/analysis/kernel/dashboard/frontend/db/sql/codeql_data-6.1.db/query-Paths_getAllSyscalls.sql
@@ -0,0 +1,3 @@
+SELECT
+    syscall
+FROM syscall_all;
\ No newline at end of file
diff --git a/analysis/kernel/dashboard/frontend/db/sql/codeql_data-6.1.db/query-Paths_getChildEdgesByFileLine.sql b/analysis/kernel/dashboard/frontend/db/sql/codeql_data-6.1.db/query-Paths_getChildEdgesByFileLine.sql
new file mode 100644
index 00000000..8fae065e
--- /dev/null
+++ b/analysis/kernel/dashboard/frontend/db/sql/codeql_data-6.1.db/query-Paths_getChildEdgesByFileLine.sql
@@ -0,0 +1,11 @@
+SELECT DISTINCT
+    source_message,
+    target_startLine,
+    target_uri,
+    target_function_end_line
+FROM edges_with_functions
+WHERE
+    rule_id = 'callgraph-all'
+    AND source_uri = $file_path
+    AND source_startLine > $start_line
+    AND source_startLine < $end_line;
\ No newline at end of file
diff --git a/analysis/kernel/dashboard/frontend/db/sql/codeql_data-6.1.db/query-Paths_getConditionsByFileLine.sql b/analysis/kernel/dashboard/frontend/db/sql/codeql_data-6.1.db/query-Paths_getConditionsByFileLine.sql
new file mode 100644
index 00000000..a447d5d7
--- /dev/null
+++ b/analysis/kernel/dashboard/frontend/db/sql/codeql_data-6.1.db/query-Paths_getConditionsByFileLine.sql
@@ -0,0 +1,27 @@
+SELECT DISTINCT
+    syscall,
+    type condition_type,
+    argument condition_argument,
+    function_call_file_path,
+    function_call_start_line,
+    function_call_end_line
+FROM
+    syscall_condition_node_with_functions
+WHERE
+    function_file_path = $file_path AND (
+        function_start_line = $start_line
+    )
+UNION
+SELECT DISTINCT
+    syscall,
+    type condition_type,
+    argument condition_argument,
+    function_call_file_path,
+    function_call_start_line,
+    function_call_end_line
+FROM
+    syscall_condition_node_with_functions
+WHERE
+    function_file_path = $file_path AND (
+        function_end_line = $end_line
+    );
\ No newline at end of file
diff --git a/analysis/kernel/dashboard/frontend/db/sql/codeql_data-6.1.db/query-Paths_getParentEdgesByFileLine.sql b/analysis/kernel/dashboard/frontend/db/sql/codeql_data-6.1.db/query-Paths_getParentEdgesByFileLine.sql
new file mode 100644
index 00000000..7d5a2fff
--- /dev/null
+++ b/analysis/kernel/dashboard/frontend/db/sql/codeql_data-6.1.db/query-Paths_getParentEdgesByFileLine.sql
@@ -0,0 +1,19 @@
+SELECT
+    source_uri,
+    source_function_start_line,
+    source_function_end_line,
+    source_function_names,
+    group_concat(SUBSTR(syscall, length('__do_sys_') + 1), ' ') syscalls
+FROM edges_with_functions_per_syscall
+WHERE
+    rule_id = 'callgraph-all'
+    AND target_uri = $file_path
+    AND (
+        target_startLine = $start_line
+        OR target_function_end_line = $end_line
+    )
+GROUP BY
+    source_uri,
+    source_function_start_line,
+    source_function_end_line,
+    source_function_names;
\ No newline at end of file
diff --git a/analysis/kernel/dashboard/frontend/db/sql/codeql_data-6.1.db/query-Paths_getSyscallParentEdgesByFileLine.sql b/analysis/kernel/dashboard/frontend/db/sql/codeql_data-6.1.db/query-Paths_getSyscallParentEdgesByFileLine.sql
new file mode 100644
index 00000000..c9f23ef6
--- /dev/null
+++ b/analysis/kernel/dashboard/frontend/db/sql/codeql_data-6.1.db/query-Paths_getSyscallParentEdgesByFileLine.sql
@@ -0,0 +1,34 @@
+WITH RECURSIVE traverse_callgraph AS (
+    SELECT
+        -1 depth,
+        $file_path caller_uri,
+        $end_line caller_endLine,
+        null callee_uri,
+        null callee_endLine
+    UNION ALL
+    SELECT
+        depth + 1 depth,
+        source_uri caller_uri,
+        source_function_end_line caller_endLine,
+        target_uri callee_uri,
+        target_function_end_line callee_endLine
+    FROM
+        edges_with_functions_per_syscall
+    JOIN
+        traverse_callgraph on (
+            syscall = $syscall
+            AND target_uri = caller_uri
+            AND target_function_end_line = caller_endLine
+        )
+    WHERE
+        depth < $depth
+    ORDER BY depth ASC
+    LIMIT $limit
+    OFFSET $offset
+)
+SELECT
+    caller_uri,
+    caller_endLine,
+    callee_uri,
+    callee_endLine
+FROM traverse_callgraph WHERE depth BETWEEN 0 AND $depth;
\ No newline at end of file
diff --git a/analysis/kernel/dashboard/frontend/db/sql/codeql_data-6.1.db/query-Paths_getSyscallsByFileLine.sql b/analysis/kernel/dashboard/frontend/db/sql/codeql_data-6.1.db/query-Paths_getSyscallsByFileLine.sql
new file mode 100644
index 00000000..553ee685
--- /dev/null
+++ b/analysis/kernel/dashboard/frontend/db/sql/codeql_data-6.1.db/query-Paths_getSyscallsByFileLine.sql
@@ -0,0 +1,11 @@
+SELECT DISTINCT
+    SUBSTR(syscall, length('__do_sys_') + 1) syscall
+FROM syscall_node_with_functions
+WHERE
+    syscall IS NOT NULL AND
+    function_file_path = $file_path AND
+    (
+        function_start_line = $start_line
+        OR function_end_line = $end_line
+    )
+GROUP BY syscall
\ No newline at end of file
diff --git a/analysis/kernel/dashboard/frontend/db/sql/git_blame-6.1.111.db/prolog.sql b/analysis/kernel/dashboard/frontend/db/sql/git_blame-6.1.111.db/prolog.sql
new file mode 100644
index 00000000..231a635c
--- /dev/null
+++ b/analysis/kernel/dashboard/frontend/db/sql/git_blame-6.1.111.db/prolog.sql
@@ -0,0 +1 @@
+CREATE INDEX IF NOT EXISTS getSourceByFileLine ON git_blame (file_path, line_no, data);
diff --git a/analysis/kernel/dashboard/frontend/db/sql/git_blame-6.1.111.db/query-Code_getSourceByFileLine.sql b/analysis/kernel/dashboard/frontend/db/sql/git_blame-6.1.111.db/query-Code_getSourceByFileLine.sql
new file mode 100644
index 00000000..5d81eac7
--- /dev/null
+++ b/analysis/kernel/dashboard/frontend/db/sql/git_blame-6.1.111.db/query-Code_getSourceByFileLine.sql
@@ -0,0 +1,6 @@
+SELECT file_path, line_no, data
+FROM git_blame
+WHERE
+    file_path = $file_path
+    AND line_no >= $start_line
+    AND line_no <= $end_line;
\ No newline at end of file
diff --git a/analysis/kernel/dashboard/frontend/db/sql/syzkaller-6.1.111.db/prolog.sql b/analysis/kernel/dashboard/frontend/db/sql/syzkaller-6.1.111.db/prolog.sql
new file mode 100644
index 00000000..16a67aa5
--- /dev/null
+++ b/analysis/kernel/dashboard/frontend/db/sql/syzkaller-6.1.111.db/prolog.sql
@@ -0,0 +1,31 @@
+CREATE TABLE IF NOT EXISTS syzk_cov_with_file_path AS
+SELECT
+    file_path,
+    code_line_no,
+    prog_id
+FROM
+    syzk_cov
+JOIN
+    file_path USING (file_id);
+
+CREATE TABLE IF NOT EXISTS syzk_cov_with_file_paths_and_syscalls AS
+SELECT
+    prog_id,
+    syscall,
+    file_path,
+    code_line_no
+FROM syzk_sys JOIN syzk_cov_with_file_path USING (prog_id);
+
+CREATE TABLE IF NOT EXISTS syzk_all_syscalls AS
+SELECT DISTINCT
+    syscall
+FROM
+    syzk_sys;
+
+CREATE INDEX IF NOT EXISTS getCoverageByFileLine ON syzk_cov_with_file_path (
+    file_path, code_line_no, prog_id
+);
+
+CREATE INDEX IF NOT EXISTS getSyscallsByFileLine ON syzk_cov_with_file_paths_and_syscalls (
+    file_path, code_line_no, syscall
+);
diff --git a/analysis/kernel/dashboard/frontend/db/sql/syzkaller-6.1.111.db/query-Syzkaller_getAllSyscalls.sql b/analysis/kernel/dashboard/frontend/db/sql/syzkaller-6.1.111.db/query-Syzkaller_getAllSyscalls.sql
new file mode 100644
index 00000000..1a5059e0
--- /dev/null
+++ b/analysis/kernel/dashboard/frontend/db/sql/syzkaller-6.1.111.db/query-Syzkaller_getAllSyscalls.sql
@@ -0,0 +1,4 @@
+SELECT
+    syscall
+FROM
+    syzk_all_syscalls;
\ No newline at end of file
diff --git a/analysis/kernel/dashboard/frontend/db/sql/syzkaller-6.1.111.db/query-Syzkaller_getCoverageByFileLine.sql b/analysis/kernel/dashboard/frontend/db/sql/syzkaller-6.1.111.db/query-Syzkaller_getCoverageByFileLine.sql
new file mode 100644
index 00000000..58fbfdca
--- /dev/null
+++ b/analysis/kernel/dashboard/frontend/db/sql/syzkaller-6.1.111.db/query-Syzkaller_getCoverageByFileLine.sql
@@ -0,0 +1,9 @@
+SELECT
+    file_path,
+    code_line_no,
+    prog_id
+FROM syzk_cov_with_file_path
+WHERE
+    file_path = $file_path
+    AND code_line_no >= $start_line
+    AND code_line_no <= $end_line;
\ No newline at end of file
diff --git a/analysis/kernel/dashboard/frontend/db/sql/syzkaller-6.1.111.db/query-Syzkaller_getProgramById.sql b/analysis/kernel/dashboard/frontend/db/sql/syzkaller-6.1.111.db/query-Syzkaller_getProgramById.sql
new file mode 100644
index 00000000..57366683
--- /dev/null
+++ b/analysis/kernel/dashboard/frontend/db/sql/syzkaller-6.1.111.db/query-Syzkaller_getProgramById.sql
@@ -0,0 +1,5 @@
+SELECT
+    prog_code
+FROM syzk_prog
+WHERE
+    prog_id IN (SELECT value FROM json_each($prog_ids));
\ No newline at end of file
diff --git a/analysis/kernel/dashboard/frontend/db/sql/syzkaller-6.1.111.db/query-Syzkaller_getSyscallsByFileLine.sql b/analysis/kernel/dashboard/frontend/db/sql/syzkaller-6.1.111.db/query-Syzkaller_getSyscallsByFileLine.sql
new file mode 100644
index 00000000..029e0d63
--- /dev/null
+++ b/analysis/kernel/dashboard/frontend/db/sql/syzkaller-6.1.111.db/query-Syzkaller_getSyscallsByFileLine.sql
@@ -0,0 +1,9 @@
+SELECT DISTINCT
+    syscall,
+    prog_id
+FROM
+    syzk_cov_with_file_paths_and_syscalls
+WHERE
+    file_path = $file_path
+    AND code_line_no >= $start_line
+    AND code_line_no <= $end_line;
\ No newline at end of file
diff --git a/analysis/kernel/dashboard/frontend/dist/healthz b/analysis/kernel/dashboard/frontend/dist/healthz
new file mode 100644
index 00000000..9766475a
--- /dev/null
+++ b/analysis/kernel/dashboard/frontend/dist/healthz
@@ -0,0 +1 @@
+ok
diff --git a/analysis/kernel/dashboard/frontend/index.html b/analysis/kernel/dashboard/frontend/index.html
new file mode 100644
index 00000000..8c5c9c8d
--- /dev/null
+++ b/analysis/kernel/dashboard/frontend/index.html
@@ -0,0 +1,44 @@
+
+
+    
+    
+    
+    🐧 kernel explorer
+    
+    
+    
+
+
+    
+ + +
+
+
+

+ kernel explorer +

+
+
+ + +
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+ diff --git a/analysis/kernel/dashboard/frontend/main.d.ts b/analysis/kernel/dashboard/frontend/main.d.ts new file mode 100644 index 00000000..fa72a30e --- /dev/null +++ b/analysis/kernel/dashboard/frontend/main.d.ts @@ -0,0 +1,28 @@ +declare module '*.html' { + const value: string; + export default value +} + +declare module '*.sql' { + const value: string; + export default value +} + +declare module 'js-treeview' { + export type TreeViewItem = { + name: string; + expanded?: boolean; + children: item[]; + data?: T; + }; + type TreeViewEvent = 'expand'|'expandAll'|'collapse'|'collapseAll'|'select'; + class TreeView { + constructor(data: TreeViewItem[], node: DOMElement); + on( + event: TreeViewEvent, + handler: function({target: UIEvent, data: TreeViewItem}) + ); + off(event: TreeViewEvent, handler: Function); + } + export default TreeView; +} \ No newline at end of file diff --git a/analysis/kernel/dashboard/frontend/package-lock.json b/analysis/kernel/dashboard/frontend/package-lock.json new file mode 100644 index 00000000..e129e2d7 --- /dev/null +++ b/analysis/kernel/dashboard/frontend/package-lock.json @@ -0,0 +1,4740 @@ +{ + "name": "frontend", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "js-treeview": "^1.1.5", + "leader-line-new": "^1.1.9", + "prismjs": "^1.29.0", + "raw-loader": "^4.0.2", + "sqlite-wasm-http": "^1.2.0" + }, + "devDependencies": { + "@types/prismjs": "^1.26.5", + "clean-webpack-plugin": "^4.0.0", + "css-loader": "^7.1.2", + "html-loader": "^5.1.0", + "http-server": "^14.1.0", + "style-loader": "^4.0.0", + "ts-loader": "^9.2.6", + "typescript": "^4.5.5", + "webpack": "^5.96.1", + "webpack-cli": "^5.1.4", + "webpack-dev-server": "^5.1.0" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jsonjoy.com/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", + "dev": true, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.1.0.tgz", + "integrity": "sha512-zlQONA+msXPPwHWZMKFVS78ewFczIll5lXiVPwFPCZUsrOKdxc2AvxU1HoNBmMRhqDZUR9HkC3UOm+6pME6Xsg==", + "dev": true, + "dependencies": { + "@jsonjoy.com/base64": "^1.1.1", + "@jsonjoy.com/util": "^1.1.2", + "hyperdyperid": "^1.2.0", + "thingies": "^1.20.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/util": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.5.0.tgz", + "integrity": "sha512-ojoNsrIuPI9g6o8UxhraZQSyF2ByJanAY4cTFbc8Mf2AXEF4aQRGY1dJxyJpuyav8r9FGflEt/Ff3u5Nt6YMPA==", + "dev": true, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "dev": true + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", + "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", + "dev": true, + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==" + }, + "node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.1.tgz", + "integrity": "sha512-CRICJIl0N5cXDONAdlTv5ShATZ4HEwk6kDDIW2/w9qOWKg+NU/5F8wYRWCrONad0/UKkloNSmmyN/wX4rtpbVA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/express/node_modules/@types/express-serve-static-core": { + "version": "4.19.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", + "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", + "dev": true, + "dependencies": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true + }, + "node_modules/@types/http-proxy": { + "version": "1.17.15", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.15.tgz", + "integrity": "sha512-25g5atgiVNTIv0LBDTg1H74Hvayx0ajtJPLLcYE3whFv75J0pWNtOBzaXJQgDTmrX1bx5U9YC2w/n65BN1HwRQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true + }, + "node_modules/@types/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "22.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.1.tgz", + "integrity": "sha512-p8Yy/8sw1caA8CdRIQBG5tiLHmxtQKObCijiAa9Ez+d4+PRffM4054xbju0msf+cvhJpnFEeNjxmVT/0ipktrg==", + "dependencies": { + "undici-types": "~6.19.8" + } + }, + "node_modules/@types/node-forge": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", + "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/prismjs": { + "version": "1.26.5", + "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.5.tgz", + "integrity": "sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==", + "dev": true + }, + "node_modules/@types/qs": { + "version": "6.9.17", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.17.tgz", + "integrity": "sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true + }, + "node_modules/@types/retry": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", + "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", + "dev": true + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-index": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", + "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "dev": true, + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/sockjs": { + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", + "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ws": { + "version": "8.5.13", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz", + "integrity": "sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webpack-cli/configtest": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", + "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", + "dev": true, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + } + }, + "node_modules/@webpack-cli/info": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", + "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", + "dev": true, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + } + }, + "node_modules/@webpack-cli/serve": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", + "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", + "dev": true, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "dev": true, + "engines": [ + "node >= 0.8.0" + ], + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true + }, + "node_modules/array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", + "dev": true, + "dependencies": { + "array-uniq": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dev": true, + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "dev": true + }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/bonjour-service": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", + "integrity": "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", + "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001669", + "electron-to-chromium": "^1.5.41", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dev": true, + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001680", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001680.tgz", + "integrity": "sha512-rPQy70G6AGUMnbwS1z6Xg+RkHYPAi18ihs47GH0jcxIG7wArmPgY3XbS2sRdBbxJljp3thdT8BIqv9ccCypiPA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/clean-css": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", + "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", + "dev": true, + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 10.0" + } + }, + "node_modules/clean-css/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/clean-webpack-plugin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/clean-webpack-plugin/-/clean-webpack-plugin-4.0.0.tgz", + "integrity": "sha512-WuWE1nyTNAyW5T7oNyys2EN0cfP2fdRxhxnIQWiAp0bMabPdHhoGxM8A6YL2GhqwgrPnnaemVE7nv5XJ2Fhh2w==", + "dev": true, + "dependencies": { + "del": "^4.1.1" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "webpack": ">=4.0.0 <6.0.0" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.5.tgz", + "integrity": "sha512-bQJ0YRck5ak3LgtnpKkiabX5pNF7tMUh1BSy2ZBOTh0Dim0BUu6aPPwByIns6/A5Prh8PufSPerMDUklpzes2Q==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.0.2", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/compression/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-disposition/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "node_modules/corser": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz", + "integrity": "sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-loader": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz", + "integrity": "sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA==", + "dev": true, + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.27.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/default-browser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", + "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", + "dev": true, + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", + "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/del": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz", + "integrity": "sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==", + "dev": true, + "dependencies": { + "@types/glob": "^7.1.1", + "globby": "^6.1.0", + "is-path-cwd": "^2.0.0", + "is-path-in-cwd": "^2.0.0", + "p-map": "^2.0.0", + "pify": "^4.0.1", + "rimraf": "^2.6.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true + }, + "node_modules/dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "dev": true, + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true + }, + "node_modules/electron-to-chromium": { + "version": "1.5.63", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.63.tgz", + "integrity": "sha512-ddeXKuY9BHo/mw145axlyWjlJ1UBt4WK3AlvkT7W2AbqfRQoacVoRUCF6wL3uIx/8wT9oLKXzI+rFqHHscByaA==" + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/envinfo": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.14.0.tgz", + "integrity": "sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg==", + "dev": true, + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", + "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==" + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/express": { + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "dev": true, + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.10", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/express/node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/express/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "node_modules/fast-uri": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.3.tgz", + "integrity": "sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==", + "dev": true + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dev": true, + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" + }, + "node_modules/globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw==", + "dev": true, + "dependencies": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/globby/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "dev": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/hpack.js/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/hpack.js/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/html-entities": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz", + "integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ] + }, + "node_modules/html-loader": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/html-loader/-/html-loader-5.1.0.tgz", + "integrity": "sha512-Jb3xwDbsm0W3qlXrCZwcYqYGnYz55hb6aoKQTlzyZPXsPpi6tHXzAfqalecglMQgNvtEfxrCQPaKT90Irt5XDA==", + "dev": true, + "dependencies": { + "html-minifier-terser": "^7.2.0", + "parse5": "^7.1.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/html-minifier-terser": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz", + "integrity": "sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==", + "dev": true, + "dependencies": { + "camel-case": "^4.1.2", + "clean-css": "~5.3.2", + "commander": "^10.0.0", + "entities": "^4.4.0", + "param-case": "^3.0.4", + "relateurl": "^0.2.7", + "terser": "^5.15.1" + }, + "bin": { + "html-minifier-terser": "cli.js" + }, + "engines": { + "node": "^14.13.1 || >=16.0.0" + } + }, + "node_modules/html-minifier-terser/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", + "dev": true + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "dev": true + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-middleware": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz", + "integrity": "sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA==", + "dev": true, + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, + "node_modules/http-server": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/http-server/-/http-server-14.1.1.tgz", + "integrity": "sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A==", + "dev": true, + "dependencies": { + "basic-auth": "^2.0.1", + "chalk": "^4.1.2", + "corser": "^2.0.1", + "he": "^1.2.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy": "^1.18.1", + "mime": "^1.6.0", + "minimist": "^1.2.6", + "opener": "^1.5.1", + "portfinder": "^1.0.28", + "secure-compare": "3.0.1", + "union": "~0.5.0", + "url-join": "^4.0.1" + }, + "bin": { + "http-server": "bin/http-server" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/hyperdyperid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", + "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", + "dev": true, + "engines": { + "node": ">=10.18" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/ipaddr.js": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "dev": true, + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-network-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.1.0.tgz", + "integrity": "sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-cwd": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", + "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-path-in-cwd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz", + "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==", + "dev": true, + "dependencies": { + "is-path-inside": "^2.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-path-inside": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz", + "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==", + "dev": true, + "dependencies": { + "path-is-inside": "^1.0.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "dev": true, + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-treeview": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/js-treeview/-/js-treeview-1.1.5.tgz", + "integrity": "sha512-PtOfvElB7KZL4O0aO3z7UY38G7ReMN74Y1iRSZPsOfyS+YGtVrHVTst+rwIy96PGoWQ2/7wEUOjjpSeYUj7+mg==" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/launch-editor": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.9.1.tgz", + "integrity": "sha512-Gcnl4Bd+hRO9P9icCP/RVVT2o8SFlPXofuCxvA2SaZuH45whSvf5p8x5oih5ftLiVhEI4sp5xDY+R+b3zJBh5w==", + "dev": true, + "dependencies": { + "picocolors": "^1.0.0", + "shell-quote": "^1.8.1" + } + }, + "node_modules/leader-line-new": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/leader-line-new/-/leader-line-new-1.1.9.tgz", + "integrity": "sha512-x56aRYaqJTA4aAS2fgbSpvWKY3IxRXDSFeGkNBmCQ3LX44B3F1HEjcBTkCiK5I3W0nPWeUTWe7dTt59VtdfFWw==" + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memfs": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.14.0.tgz", + "integrity": "sha512-JUeY0F/fQZgIod31Ja1eJgiSxLn7BfQlCnqhwXFBzFHEw63OdLK7VJUJ7bnzNsWgCyoUP5tEp1VRY8rDaYzqOA==", + "dev": true, + "dependencies": { + "@jsonjoy.com/json-pack": "^1.0.3", + "@jsonjoy.com/util": "^1.3.0", + "tree-dump": "^1.0.1", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">= 4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "dev": true, + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "dev": true, + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-releases": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/open": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz", + "integrity": "sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==", + "dev": true, + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true, + "bin": { + "opener": "bin/opener-bin.js" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/p-retry": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz", + "integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==", + "dev": true, + "dependencies": { + "@types/retry": "0.12.2", + "is-network-error": "^1.0.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "dev": true, + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/parse5": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", + "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", + "dev": true, + "dependencies": { + "entities": "^4.5.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dev": true, + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", + "dev": true + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-to-regexp": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "dev": true, + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/portfinder": { + "version": "1.0.32", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz", + "integrity": "sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==", + "dev": true, + "dependencies": { + "async": "^2.6.4", + "debug": "^3.2.7", + "mkdirp": "^0.5.6" + }, + "engines": { + "node": ">= 0.12.0" + } + }, + "node_modules/postcss": { + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.1.0.tgz", + "integrity": "sha512-rm0bdSv4jC3BDma3s9H19ZddW0aHX6EoqwDYU2IfZhRN+53QrufTRo2IdkAbRqLx4R2IYbZnbjKKxg4VN5oU9Q==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^7.0.0", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", + "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "node_modules/prismjs": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", + "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", + "engines": { + "node": ">=6" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-addr/node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.1.tgz", + "integrity": "sha512-EJPeIn0CYrGu+hli1xilKAPXODtJ12T0sP63Ijx2/khC2JtuaN3JyNIpvmnkmaEtha9ocbG4A4cMcr+TvqvwQg==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/raw-loader": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-4.0.2.tgz", + "integrity": "sha512-ZnScIV3ag9A4wPX/ZayxL/jZH+euYb6FcUinPcgiQW0+UBtEv0O6Q3lGd3cqJ+GHH+rksEv3Pj99oxJ3u3VIKA==", + "dependencies": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/run-applescript": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", + "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/secure-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz", + "integrity": "sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==", + "dev": true + }, + "node_modules/select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", + "dev": true + }, + "node_modules/selfsigned": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "dev": true, + "dependencies": { + "@types/node-forge": "^1.3.0", + "node-forge": "^1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "dev": true, + "dependencies": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true + }, + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/serve-index/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "node_modules/serve-index/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "dev": true, + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "dev": true, + "dependencies": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "node_modules/spdy-transport/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/spdy/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/sqlite-wasm-http": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/sqlite-wasm-http/-/sqlite-wasm-http-1.2.0.tgz", + "integrity": "sha512-mTZw4MHSw/dsO7PhAZeV14WIiVw/e8ECJSDV5oMEgInbCw+ITP/Y8YqbzggDiWRoPpbJ+uPoOXe5myIQbX6XxQ==", + "dependencies": { + "lru-cache": "^7.18.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/style-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-4.0.0.tgz", + "integrity": "sha512-1V4WqhhZZgjVAVJyt7TdDPZoPBPNHbekX4fWnCJL1yQukhCeZhJySUL+gL9y6sNdN95uEOS83Y55SqHcP7MzLA==", + "dev": true, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.27.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/terser": { + "version": "5.36.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.36.0.tgz", + "integrity": "sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w==", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", + "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.20", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.26.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/thingies": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.21.0.tgz", + "integrity": "sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g==", + "dev": true, + "engines": { + "node": ">=10.18" + }, + "peerDependencies": { + "tslib": "^2" + } + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tree-dump": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.2.tgz", + "integrity": "sha512-dpev9ABuLWdEubk+cIaI9cHwRNNDjkBBLXTwI4UCUFdQ5xXKqNXoK4FEciw/vxf+NQ7Cb7sGUyeUtORvHIdRXQ==", + "dev": true, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/ts-loader": { + "version": "9.5.1", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.1.tgz", + "integrity": "sha512-rNH3sK9kGZcH9dYzC7CewQm4NtxJTjSEVRJ2DyBZR7f8/wcta+iV44UPCXc5+nzDzivKtlzV6c9P4e+oFhDLYg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4", + "source-map": "^0.7.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "typescript": "*", + "webpack": "^5.0.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" + }, + "node_modules/union": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", + "integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==", + "dev": true, + "dependencies": { + "qs": "^6.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", + "dev": true + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/watchpack": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", + "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "dependencies": { + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/webpack": { + "version": "5.96.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.96.1.tgz", + "integrity": "sha512-l2LlBSvVZGhL4ZrPwyr8+37AunkcYj5qh8o6u2/2rzoPc8gxFJkLj1WxNgooi9pnoc06jh0BjuXnamM4qlujZA==", + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.6", + "@webassemblyjs/ast": "^1.12.1", + "@webassemblyjs/wasm-edit": "^1.12.1", + "@webassemblyjs/wasm-parser": "^1.12.1", + "acorn": "^8.14.0", + "browserslist": "^4.24.0", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.1", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.10", + "watchpack": "^2.4.1", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-cli": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", + "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", + "dev": true, + "dependencies": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^2.1.1", + "@webpack-cli/info": "^2.0.2", + "@webpack-cli/serve": "^2.0.5", + "colorette": "^2.0.14", + "commander": "^10.0.1", + "cross-spawn": "^7.0.3", + "envinfo": "^7.7.3", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-merge": "^5.7.3" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "5.x.x" + }, + "peerDependenciesMeta": { + "@webpack-cli/generators": { + "optional": true + }, + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/webpack-cli/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/webpack-dev-middleware": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.2.tgz", + "integrity": "sha512-xOO8n6eggxnwYpy1NlzUKpvrjfJTvae5/D6WOK0S2LSo7vjmo5gCM1DbLUmFqrMTJP+W/0YZNctm7jasWvLuBA==", + "dev": true, + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^4.6.0", + "mime-types": "^2.1.31", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + } + } + }, + "node_modules/webpack-dev-middleware/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/webpack-dev-middleware/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/webpack-dev-middleware/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/webpack-dev-middleware/node_modules/schema-utils": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/webpack-dev-server": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.1.0.tgz", + "integrity": "sha512-aQpaN81X6tXie1FoOB7xlMfCsN19pSvRAeYUHOdFWOlhpQ/LlbfTqYwwmEDFV0h8GGuqmCmKmT+pxcUV/Nt2gQ==", + "dev": true, + "dependencies": { + "@types/bonjour": "^3.5.13", + "@types/connect-history-api-fallback": "^1.5.4", + "@types/express": "^4.17.21", + "@types/serve-index": "^1.9.4", + "@types/serve-static": "^1.15.5", + "@types/sockjs": "^0.3.36", + "@types/ws": "^8.5.10", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.2.1", + "chokidar": "^3.6.0", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "express": "^4.19.2", + "graceful-fs": "^4.2.6", + "html-entities": "^2.4.0", + "http-proxy-middleware": "^2.0.3", + "ipaddr.js": "^2.1.0", + "launch-editor": "^2.6.1", + "open": "^10.0.3", + "p-retry": "^6.2.0", + "schema-utils": "^4.2.0", + "selfsigned": "^2.4.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^7.4.2", + "ws": "^8.18.0" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/webpack-dev-server/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/webpack-dev-server/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/webpack-dev-server/node_modules/schema-utils": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/webpack-merge": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", + "dev": true, + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dev": true, + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/analysis/kernel/dashboard/frontend/package.json b/analysis/kernel/dashboard/frontend/package.json new file mode 100644 index 00000000..7ca5fbbe --- /dev/null +++ b/analysis/kernel/dashboard/frontend/package.json @@ -0,0 +1,22 @@ +{ + "devDependencies": { + "@types/prismjs": "^1.26.5", + "clean-webpack-plugin": "^4.0.0", + "css-loader": "^7.1.2", + "html-loader": "^5.1.0", + "http-server": "^14.1.0", + "style-loader": "^4.0.0", + "ts-loader": "^9.2.6", + "typescript": "^4.5.5", + "webpack": "^5.96.1", + "webpack-cli": "^5.1.4", + "webpack-dev-server": "^5.1.0" + }, + "dependencies": { + "js-treeview": "^1.1.5", + "leader-line-new": "^1.1.9", + "prismjs": "^1.29.0", + "raw-loader": "^4.0.2", + "sqlite-wasm-http": "^1.2.0" + } +} diff --git a/analysis/kernel/dashboard/frontend/src/controllers/heap.ts b/analysis/kernel/dashboard/frontend/src/controllers/heap.ts new file mode 100644 index 00000000..9a31d109 --- /dev/null +++ b/analysis/kernel/dashboard/frontend/src/controllers/heap.ts @@ -0,0 +1,99 @@ +import { Index } from '..'; +import { Btf } from '../models/btf'; +import { STRUCT_RESULTS } from '../types'; +import { Access } from '../views/access/access'; +import { Fields } from '../views/fields/fields'; +import { Struct } from '../views/struct/struct'; + +export class Heap { + private struct: Struct = new Struct(this); + private fields: Fields = new Fields(this); + private access: Access = new Access(this); + + private kmalloc_cache_name?: string; + private kmalloc_cgroup_name?: string; + private kmalloc_dyn?: string; + + private constructor( + private index: Index, + private btfPromise: Promise + ) {} + + async onLocationChange(locationHash: string) { + let [match, kmalloc, cg, size, dyn, kmalloc_offset, struct, bits_offset, bits_end, access_offset] = [...locationHash.match( + /#!heap[/](kmalloc-(?:(cg)-)?(\w+)(-dyn)?(?:,(\d+))?|[*])(?:[/](\w+))?(?:[/](\d+)[.][.](\d+)(?:,(\d+))?)?/ + ) || []]; + console.log([match, kmalloc, cg, size, dyn, struct]); + if (!match) return; + + const btf = await this.btfPromise; + let results: Promise; + + if (kmalloc != "*") { + this.kmalloc_cache_name = size; + this.kmalloc_cgroup_name = cg; + this.kmalloc_dyn = dyn; + results = btf.getStructsByAllocation(size, cg, Number(kmalloc_offset || 0)); + } else { + results = btf.getStructsByStructName(struct); + } + + this.struct.displayStructs( + await Struct.getRootNode(), + await results, + kmalloc, + struct, + Number(kmalloc_offset || 0) + ); + + if (!struct) { + struct = (await results)[0].struct_name; + } + + this.fields.displayStructs( + await Fields.getRootNode(), + await btf.getFieldsByStructName(struct), + struct, + kmalloc + ); + + if (bits_offset && bits_end) { + this.displayAccess( + Number(bits_offset), + Number(bits_end), + struct, + kmalloc, + this.kmalloc_cache_name, + this.kmalloc_cgroup_name, + Number(access_offset || 0)); + } + } + + static async init(index: Index) { + const heap: Heap = new Heap( + index, + Promise.resolve().then(() => Btf.init()) + ); + return heap; + } + + async displayAccess(bits_offset:number, bits_end:number, struct: string, kmalloc: string, kmalloc_cache_name?: string|undefined, kmalloc_cgroup_name?: string, offset = 0) { + const btf = await this.btfPromise; + let access; + if (kmalloc_cache_name) { + access = await btf.getAccessByCache( + kmalloc_cache_name, + kmalloc_cgroup_name || null, + bits_offset, bits_end, + offset + ); + } else { + access = await btf.getAccessByStruct( + struct, + bits_offset, bits_end, + offset + ); + } + this.access.displayAccess(await Access.getRootNode(), access, kmalloc, offset); + } +} diff --git a/analysis/kernel/dashboard/frontend/src/controllers/reachability.ts b/analysis/kernel/dashboard/frontend/src/controllers/reachability.ts new file mode 100644 index 00000000..7135d890 --- /dev/null +++ b/analysis/kernel/dashboard/frontend/src/controllers/reachability.ts @@ -0,0 +1,160 @@ +import { Code } from '../models/code'; +import { Paths } from '../models/paths'; +import { Syzkaller } from '../models/syzkaller'; + +import { Snippets } from '../views/snippets/snippets'; +import { Navigation } from '../views/navigation/navigation'; +import { Index } from '..'; +import { Dialog } from '../views/dialog/dialog'; +import { CHILD_EDGE_LOCATIONS, CONDITION_LOCATIONS } from '../types'; + +export class Reachability { + private snippets = new Snippets(this); + private navigation = new Navigation(this); + private dialog = new Dialog(this); + + private syscalls = new Set; + + private constructor( + private index: Index, + private codePromise: Promise, + private pathsPromise: Promise, + private syzkallerPromise: Promise + ) {} + + async onLocationChange(locationHash: string) { + let identifier = locationHash.slice(1); + let [$file_path, $start_line, $end_line] = identifier.split(':'); + await Promise.all([ + this.snippets.displayLoading(identifier), + this.navigation.initNavigation(), + this.loadAllSyscalls(), + this.showFileLine( + await Snippets.getRootNode(), + $file_path, + Number($start_line), + Number($end_line), + identifier + ) + ]); + } + + static async init(index: Index) { + const reachability: Reachability = new Reachability( + index, + Promise.resolve().then(() => Code.init(reachability.snippets)), + Promise.resolve().then(() => Paths.init()), + Promise.resolve().then(() => Syzkaller.init()) + ); + return reachability; + } + + async showFileLine( + parentNode : HTMLElement, + $file_path: string, $start_line: number, $end_line: number, + title: string = "", + arrowSource: Node | null = null + ) { + let promise = this.showFileLineInternal(parentNode, $file_path, $start_line, $end_line, title, arrowSource); + if (parentNode.style) { + parentNode.style.cursor = 'progress'; + await promise; + parentNode.style.cursor = ''; + } + return promise; + } + + private async showFileLineInternal( + parentNode : HTMLElement, + $file_path: string, $start_line: number, $end_line: number, + title: string, + arrowSource: Node | null + ) { + const code = await this.codePromise; + const paths = await this.pathsPromise; + const syzkaller = await this.syzkallerPromise; + + // end_line = 0 means it is the same as start_line + $end_line = $end_line || $start_line; + const coverage = syzkaller.getCoverageByFileLine($file_path, $start_line, $end_line); + const childEdges = paths.getChildEdgesByFileLine($file_path, $start_line, $end_line); + const parentEdges = paths.getParentEdgesByFileLine($file_path, $start_line, $end_line); + const syzkalls = syzkaller.getSyscallsByFileLine($file_path, $start_line, $end_line); + const syscalls = paths.getSyscallsByFileLine($file_path, $start_line, $end_line); + const conditions = paths.getConditionsByFileLine($file_path, $start_line, $end_line); + const linesOfCode = await code.getSourceByFileLine($file_path, $start_line, $end_line); + if (linesOfCode.length == 0) { + console.log('No code found for', $file_path, $start_line, $end_line); + return; + } + + let snippet = await this.snippets.displaySnippet( + parentNode, + $file_path, + linesOfCode.map(loc => loc.data), + linesOfCode[0].line_no, + title, + childEdges, + parentEdges, + coverage, + syscalls, + syzkalls, + conditions + ); + + if (arrowSource) { + this.navigation.addLine(arrowSource, snippet); + } + + this.updateViews(); + + console.log(await Promise.all([ + 'loc', linesOfCode, 'cov', coverage, + 'chi', childEdges, 'par', parentEdges, + 'sys', syscalls, 'syz', syzkalls + ])); + } + + async displaySyzkallerPrograms(programs: string[]) { + let syzkaller = await this.syzkallerPromise; + this.dialog.displaySyzkallerPrograms( + await Dialog.getRootNode(), + await syzkaller.getProgramById(programs) + ); + } + + async displayConditions(conditions: CONDITION_LOCATIONS[]) { + this.dialog.displayConditions( + await Dialog.getRootNode(), + conditions + ); + } + + async displayEdgeSelection( + functionName: string, filePath: string, lineNumber: number, + node: Node, childEdges: HTMLElement + ) { + const paths = await this.pathsPromise; + const allEdges = await paths.getAllEdgesFromFileLine(filePath, lineNumber); + this.dialog.displayEdgeSelection( + await Dialog.getRootNode(), + allEdges, functionName, + node, childEdges + ); + } + + async loadAllSyscalls() { + let syzkaller = await this.syzkallerPromise; + let paths = await this.pathsPromise; + [ + ...(await syzkaller.getAllSyscalls()), + ...(await paths.getAllSyscalls()) + ].forEach(row => this.syscalls.add(row.syscall)); + let syscalls = this.syscalls.values(); + this.snippets.createSyscallFilters([...syscalls]); + } + + updateViews() { + this.navigation.updateLinesPositions(); + } +} diff --git a/analysis/kernel/dashboard/frontend/src/index.css b/analysis/kernel/dashboard/frontend/src/index.css new file mode 100644 index 00000000..2ff5a48d --- /dev/null +++ b/analysis/kernel/dashboard/frontend/src/index.css @@ -0,0 +1,110 @@ +:root { + display: flex; + height: max-content; + width: max-content; + overflow: scroll; + padding: 100vh 100vw 100vh 100vw; + scrollbar-color: #888 #222; + + body { + background: linear-gradient(#222, #001) fixed; + color: #eee; + font-family: "Source Code Pro", Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; + .explorer-sandwich:has(#explorer-filter-visible:checked) ~ .explorer-filters { + transform: scaleX(1); + } + .explorer-filters { + position: fixed; + top: 0; + left: 0; + bottom: 0; + --panning-sticky: sticky; + --panning-visible: visible; + visibility: var(--panning-visible); + z-index: 10; + display: flex; + flex-direction: column; + overflow: hidden; + transform-origin: left; + transition-delay: 3s; + transition: transform 300ms linear; + &:not(:hover):not(:focus-within) { + transform: scaleX(0.0001); + } + .explorer-header{ + background: linear-gradient(hsla(24, 20%, 20%, .8), hsla(24, 20%, 50%, 1));; + .kernel-explorer-title { + font-family: Oxanium, serif; + font-weight: 200; + padding-left: calc(25pt + 20px); + padding-right: 1em; + line-height: 25pt; + } + } + .explorer-tabs { + flex: 0 0 auto; + background: #2d2d2ddd; + border-bottom: 5px solid hsla(24, 20%, 40%, 1); + label { + display: inline-block; + border-radius: 33% 33% 0 0; + background: hsla(24, 20%, 40%, 1); + padding: 5px 10px; + margin-top: 5px; + border-bottom: 1px solid black; + &:has(input:checked) { + border-bottom-color: hsla(24, 20%, 40%, 1); + } + } + } + .explorer-panels { + flex: 1 1 0; + overflow: hidden; + display: flex; + } + .explorer-tabs:has(.struct-explorer-tab:not(:checked)) ~ .explorer-panels .struct-explorer, + .explorer-tabs:has(.file-explorer-tab:not(:checked)) ~ .explorer-panels .file-explorer { + display: none; + } + } + .explorer-sandwich { + position: fixed; + top: 0; + left: 0; + z-index: 100; + margin: 6px 3px; + --panning-visible: visible; + visibility: var(--panning-visible); + label { + user-select: none; + display: inline-block; + font-size: 17pt; + padding: 3px; + border-radius: 50%; + border: 1px solid #888; + background: #222; + height: 25pt; + width: 25pt; + margin: auto; + line-height: 25pt; + text-align: center; + &:active:hover { + font-size: 15pt; + box-shadow: 0 0 16px 0 #ccc, 0 0 16px 0 #aaa; + } + &:hover { + font-size: 20pt; + box-shadow: inset 0 0 16px 0 #ccc, inset 0 0 16px 0 #aaa; + } + } + input { + appearance: none; + width: 0; + display: block; + } + } + .code-explorer { + z-index: 0; + } + } +} \ No newline at end of file diff --git a/analysis/kernel/dashboard/frontend/src/index.ts b/analysis/kernel/dashboard/frontend/src/index.ts new file mode 100644 index 00000000..c4614956 --- /dev/null +++ b/analysis/kernel/dashboard/frontend/src/index.ts @@ -0,0 +1,42 @@ +import { Reachability } from './controllers/reachability'; +import { Heap } from './controllers/heap'; + +import "./index.css"; + +export class Index { + + private constructor( + private reachabilityPromise: Promise, + private heapPromise: Promise + ) { } + + async onLocationChange(locationHash: string) { + switch (true) { + case !!locationHash.match(/^#!heap/): + await (await this.heapPromise).onLocationChange(locationHash); + (document.querySelector('.struct-explorer') as HTMLDivElement).focus(); + break; + case !!locationHash.match(/^#\w+[/]/): + await (await this.reachabilityPromise).onLocationChange(locationHash); + break; + default: + console.error(404); + return; + } + } + + static async init() { + let index: Index = new Index( + Promise.resolve().then(() => Reachability.init(index)), + Promise.resolve().then(() => Heap.init(index)) + ); + document.onreadystatechange = onhashchange = async () => { + document.documentElement.style.cursor = 'progress'; + await index.onLocationChange(location.hash); + document.documentElement.style.cursor = ''; + }; + return index; + } +} + +Index.init(); \ No newline at end of file diff --git a/analysis/kernel/dashboard/frontend/src/models/btf.ts b/analysis/kernel/dashboard/frontend/src/models/btf.ts new file mode 100644 index 00000000..cf613638 --- /dev/null +++ b/analysis/kernel/dashboard/frontend/src/models/btf.ts @@ -0,0 +1,143 @@ +import { Db } from './db'; + +import Btf_getStructsByStructName from '../../db/sql/btf-kernelctf-6_1_111.db/query-Btf_getStructsByStructName.sql'; +import Btf_getStructsByAllocation from '../../db/sql/btf-kernelctf-6_1_111.db/query-Btf_getStructsByAllocation.sql'; +import Btf_getFieldsByStructName from '../../db/sql/btf-kernelctf-6_1_111.db/query-Btf_getFieldsByStructName.sql'; +import Btf_getAccessByCache from '../../db/sql/btf-kernelctf-6_1_111.db/query-Btf_getAccessByCache.sql'; +import Btf_getAccessByStruct from '../../db/sql/btf-kernelctf-6_1_111.db/query-Btf_getAccessByStruct.sql'; + +import { ACCESS_RESULTS, STRUCT_RESULTS } from '../types'; + +export class Btf { + private static DB_PATH: string = '../db/btf-kernelctf-6_1_111.db'; + + private constructor( + private db: Db + ) { } + + static async init() { + return new Btf( + await Db.init(Btf.DB_PATH)); + } + + async getStructsByStructName($struct_name: string): Promise { + let rows = await this.db.exec(Btf_getStructsByStructName, { + $struct_name + }); + return rows.map(row => ({ + struct_name: Db.getColumn(row, 'struct_name'), + struct_size: Db.getColumn(row, 'struct_size'), + allocSizeMax: Db.getColumn(row, 'allocSizeMax'), + allocSizeMin: Db.getColumn(row, 'allocSizeMin'), + allocSize: Db.getColumn(row, 'allocSize'), + call_startLine: Db.getColumn(row, 'call_startLine'), + call_uri: Db.getColumn(row, 'call_uri'), + call_value: Db.getColumn(row, 'call_value'), + depth: Db.getColumn(row, 'depth'), + flagsMax: Db.getColumn(row, 'flagsMax'), + flagsMin: Db.getColumn(row, 'flagsMin'), + flags: Db.getColumn(row, 'flags'), + function: Db.getColumn(row, 'function'), + function_start_line: Db.getColumn(row, 'function_start_line'), + function_end_line: Db.getColumn(row, 'function_end_line'), + syscalls_num: Db.getColumn(row, 'syscalls_num'), + kmalloc_bucket_name: Db.getColumn(row, 'kmalloc_bucket_name'), + kmalloc_cgroup_name: Db.getColumn(row, 'kmalloc_cgroup_name'), + kmalloc_dyn: Db.getColumn(row, 'kmalloc_dyn') + })); + } + + async getStructsByAllocation( + $kmalloc_bucket_name: string, + $kmalloc_cgroup_name: string|null, + $offset = 0, + $limit = 100 + ): Promise { + let rows = await this.db.exec(Btf_getStructsByAllocation, { + $kmalloc_bucket_name, $kmalloc_cgroup_name, + $limit, $offset + }); + return rows.map(row => ({ + struct_name: Db.getColumn(row, 'struct_name'), + struct_size: Db.getColumn(row, 'struct_size'), + allocSizeMax: Db.getColumn(row, 'allocSizeMax'), + allocSizeMin: Db.getColumn(row, 'allocSizeMin'), + allocSize: Db.getColumn(row, 'allocSize'), + call_startLine: Db.getColumn(row, 'call_startLine'), + call_uri: Db.getColumn(row, 'call_uri'), + call_value: Db.getColumn(row, 'call_value'), + depth: Db.getColumn(row, 'depth'), + flagsMax: Db.getColumn(row, 'flagsMax'), + flagsMin: Db.getColumn(row, 'flagsMin'), + flags: Db.getColumn(row, 'flags'), + function: Db.getColumn(row, 'function'), + function_start_line: Db.getColumn(row, 'function_start_line'), + function_end_line: Db.getColumn(row, 'function_end_line'), + syscalls_num: Db.getColumn(row, 'syscalls_num'), + kmalloc_bucket_name: Db.getColumn(row, 'kmalloc_bucket_name'), + kmalloc_cgroup_name: Db.getColumn(row, 'kmalloc_cgroup_name'), + kmalloc_dyn: Db.getColumn(row, 'kmalloc_dyn') + })); + } + + async getFieldsByStructName($struct_name: string) { + let rows = await this.db.exec(Btf_getFieldsByStructName, { + $struct_name + }); + return rows.map(row => ({ + bits_offset: Db.getColumn(row, 'bits_offset'), + bits_end: Db.getColumn(row, 'bits_end'), + type: Db.getColumn(row, 'type'), + parent_type: Db.getColumn(row, 'parent_type'), + name: Db.getColumn(row, 'name') + })); + } + + async getAccessByCache( + $kmalloc_bucket_name: string, + $kmalloc_cgroup_name: string|null, + $overlap_start: number, + $overlap_end: number, + $offset = 0, + $limit = 100 + ): Promise { + let rows = await this.db.exec(Btf_getAccessByCache, { + $kmalloc_bucket_name, $kmalloc_cgroup_name, $overlap_start, $overlap_end, + $limit, $offset + }); + return rows.map(row => ({ + struct_name: Db.getColumn(row, 'struct_name'), + type: Db.getColumn(row, 'type'), + full_field_name: Db.getColumn(row, 'full_field_name'), + bits_offset: Db.getColumn(row, 'bits_offset'), + bits_end: Db.getColumn(row, 'bits_end'), + field_access_type: Db.getColumn(row, 'field_access_type'), + field_access_start_line: Db.getColumn(row, 'field_access_start_line'), + function_file_path: Db.getColumn(row, 'function_file_path'), + function_start_line: Db.getColumn(row, 'function_start_line'), + function_end_line: Db.getColumn(row, 'function_end_line'), + syscalls_num: Db.getColumn(row, 'syscalls_num') + })); + } + + + async getAccessByStruct($struct_name: string, $overlap_start: number, $overlap_end: number, $offset: number, $limit = 100): Promise { + let rows = await this.db.exec(Btf_getAccessByStruct, { + $struct_name, $overlap_start, $overlap_end, + $limit, $offset + }); + return rows.map(row => ({ + struct_name: Db.getColumn(row, 'struct_name'), + type: Db.getColumn(row, 'type'), + full_field_name: Db.getColumn(row, 'full_field_name'), + bits_offset: Db.getColumn(row, 'bits_offset'), + bits_end: Db.getColumn(row, 'bits_end'), + field_access_type: Db.getColumn(row, 'field_access_type'), + field_access_start_line: Db.getColumn(row, 'field_access_start_line'), + function_file_path: Db.getColumn(row, 'function_file_path'), + function_start_line: Db.getColumn(row, 'function_start_line'), + function_end_line: Db.getColumn(row, 'function_end_line'), + syscalls_num: Db.getColumn(row, 'syscalls_num') + })); + } +} diff --git a/analysis/kernel/dashboard/frontend/src/models/code.ts b/analysis/kernel/dashboard/frontend/src/models/code.ts new file mode 100644 index 00000000..5dc2abd9 --- /dev/null +++ b/analysis/kernel/dashboard/frontend/src/models/code.ts @@ -0,0 +1,30 @@ +import { Db } from './db'; +import { Snippets } from '../views/snippets/snippets'; + +import Code_getSourceByFileLineSql from '../../db/sql/git_blame-6.1.111.db/query-Code_getSourceByFileLine.sql'; + +export class Code { + private static DB_PATH: string = '../db/git_blame-6.1.111.db'; + + private constructor( + private snippets: Snippets, + private db: Db + ) {} + + static async init(snippets: Snippets) { + return new Code( + snippets, + await Db.init(Code.DB_PATH)); + } + + async getSourceByFileLine($file_path: string, $start_line: number, $end_line: number) { + let rows = await this.db.exec(Code_getSourceByFileLineSql, { + $file_path, $start_line, $end_line + }); + return rows.map(row => ({ + file_path: Db.getColumn(row, 'file_path'), + line_no: Db.getColumn(row, 'line_no'), + data: Db.getColumn(row, 'data'), + })); + } +} \ No newline at end of file diff --git a/analysis/kernel/dashboard/frontend/src/models/db.ts b/analysis/kernel/dashboard/frontend/src/models/db.ts new file mode 100644 index 00000000..54a3f7f3 --- /dev/null +++ b/analysis/kernel/dashboard/frontend/src/models/db.ts @@ -0,0 +1,22 @@ +import { createSQLiteHTTPPool, SQLiteHTTPPool } from 'sqlite-wasm-http'; + +export class Db { + static getColumn(row: ({ row: string, columnNames: string[] }), columnName: string) { + return row.row[row.columnNames.indexOf(columnName)]; + } + + private constructor(private pool: SQLiteHTTPPool) { } + + static async init(dbPath: string) { + const pool = await createSQLiteHTTPPool({}); + pool.open(dbPath); + return new Db(pool); + } + + async exec(query: string, params: Record) { + console.debug(query, params); + const result = this.pool.exec(query, params); + console.debug(await result); + return result; + } +} \ No newline at end of file diff --git a/analysis/kernel/dashboard/frontend/src/models/paths.ts b/analysis/kernel/dashboard/frontend/src/models/paths.ts new file mode 100644 index 00000000..66ad1a56 --- /dev/null +++ b/analysis/kernel/dashboard/frontend/src/models/paths.ts @@ -0,0 +1,102 @@ +import { Db } from './db'; +import { CHILD_EDGE_LOCATIONS } from '../types'; + +import Paths_getChildEdgesByFileLine from '../../db/sql/codeql_data-6.1.db/query-Paths_getChildEdgesByFileLine.sql'; +import Paths_getParentEdgesByFileLine from '../../db/sql/codeql_data-6.1.db/query-Paths_getParentEdgesByFileLine.sql'; +import Paths_getSyscallsByFileLine from '../../db/sql/codeql_data-6.1.db/query-Paths_getSyscallsByFileLine.sql'; +import Paths_getSyscallParentEdgesByFileLine from '../../db/sql/codeql_data-6.1.db/query-Paths_getSyscallParentEdgesByFileLine.sql'; +import Paths_getAllSyscalls from '../../db/sql/codeql_data-6.1.db/query-Paths_getAllSyscalls.sql'; +import Paths_getConditionsByFileLine from '../../db/sql/codeql_data-6.1.db/query-Paths_getConditionsByFileLine.sql'; +import Paths_getAllEdgesFromFileLine from '../../db/sql/codeql_data-6.1.db/query-Paths_getAllEdgesFromFileLine.sql'; +export class Paths { + private static DB_PATH: string = '../db/codeql_data-6.1.db'; + + private constructor( + private db: Db + ) {} + + static async init() { + return new Paths( + await Db.init(Paths.DB_PATH)); + } + + async getChildEdgesByFileLine($file_path: string, $start_line: number, $end_line: number): Promise { + let rows = await this.db.exec(Paths_getChildEdgesByFileLine, { + $file_path, $start_line, $end_line + }); + return rows.map(row => ({ + source_message: Db.getColumn(row, 'source_message'), + target_startLine: Db.getColumn(row, 'target_startLine'), + target_uri: Db.getColumn(row, 'target_uri'), + target_function_end_line: Db.getColumn(row, 'target_function_end_line') + })); + } + + async getParentEdgesByFileLine($file_path: string, $start_line: number, $end_line: number) { + let rows = await this.db.exec(Paths_getParentEdgesByFileLine, { + $file_path, $start_line, $end_line + }); + return rows.map(row => ({ + source_uri: Db.getColumn(row, 'source_uri'), + source_function_start_line: Db.getColumn(row, 'source_function_start_line'), + source_function_end_line: Db.getColumn(row, 'source_function_end_line'), + source_function_names: Db.getColumn(row, 'source_function_names'), + syscalls: Db.getColumn(row, 'syscalls'), + })); + } + + async getSyscallsByFileLine($file_path: string, $start_line: number, $end_line: number) { + let rows = await this.db.exec(Paths_getSyscallsByFileLine, { + $file_path, $start_line, $end_line + }); + return rows.map(row => ({ + syscall: Db.getColumn(row, 'syscall') + })); + } + + async getConditionsByFileLine($file_path: string, $start_line: number, $end_line: number) { + let rows = await this.db.exec(Paths_getConditionsByFileLine, { + $file_path, $start_line, $end_line + }); + return rows.map(row => ({ + syscall: Db.getColumn(row, 'syscall'), + condition_type: Db.getColumn(row, 'condition_type'), + condition_argument: Db.getColumn(row, 'condition_argument'), + function_call_file_path: Db.getColumn(row, 'function_call_file_path'), + function_call_start_line: Db.getColumn(row, 'function_call_start_line'), + function_call_end_line: Db.getColumn(row, 'function_call_end_line') + })); + } + + async getAllSyscalls() { + let rows = await this.db.exec(Paths_getAllSyscalls, {}); + return rows.map(row => ({ + syscall: Db.getColumn(row, 'syscall') + })); + } + + async getSyscallParentEdgesByFileLine($syscall: string, $file_path: string, $start_line: number, $end_line: number, $depth = 3, $limit = 1000, $offset = 0) { + let rows = await this.db.exec(Paths_getSyscallParentEdgesByFileLine, { + $syscall, $file_path, $end_line, $depth, $limit, $offset + }); + return rows.map(row => ({ + caller_uri: Db.getColumn(row, 'caller_uri'), + caller_endLine: Db.getColumn(row, 'caller_endLine'), + callee_uri: Db.getColumn(row, 'callee_uri'), + callee_endLine: Db.getColumn(row, 'callee_endLine') + })); + } + + async getAllEdgesFromFileLine($file_path: string, $start_line: number) { + let rows = await this.db.exec(Paths_getAllEdgesFromFileLine, { + $file_path, $start_line + }); + return rows.map(row => ({ + edge_type: Db.getColumn(row, 'edge_type'), + identifier: Db.getColumn(row, 'identifier'), + file_path: Db.getColumn(row, 'file_path'), + start_line: Db.getColumn(row, 'start_line'), + end_line: Db.getColumn(row, 'end_line') + })); + } +} diff --git a/analysis/kernel/dashboard/frontend/src/models/syzkaller.ts b/analysis/kernel/dashboard/frontend/src/models/syzkaller.ts new file mode 100644 index 00000000..5ee61e1e --- /dev/null +++ b/analysis/kernel/dashboard/frontend/src/models/syzkaller.ts @@ -0,0 +1,53 @@ +import { Db } from './db'; + +import Syzkaller_getCoverageByFileLine from '../../db/sql/syzkaller-6.1.111.db/query-Syzkaller_getCoverageByFileLine.sql'; +import Syzkaller_getSyscallsByFileLine from '../../db/sql/syzkaller-6.1.111.db/query-Syzkaller_getSyscallsByFileLine.sql'; +import Syzkaller_getAllSyscalls from '../../db/sql/syzkaller-6.1.111.db/query-Syzkaller_getAllSyscalls.sql'; +import Syzkaller_getProgramById from '../../db/sql/syzkaller-6.1.111.db/query-Syzkaller_getProgramById.sql'; +export class Syzkaller { + private static DB_PATH: string = '../db/syzkaller-6.1.111.db'; + + private constructor( + private db: Db + ) {} + + static async init() { + return new Syzkaller( + await Db.init(Syzkaller.DB_PATH)); + } + + async getCoverageByFileLine($file_path: string, $start_line: number, $end_line: number) { + let rows = await this.db.exec(Syzkaller_getCoverageByFileLine, { + $file_path, $start_line, $end_line + }); + return rows.map(row => ({ + file_path: Db.getColumn(row, 'file_path'), + code_line_no: Db.getColumn(row, 'code_line_no'), + prog_id: Db.getColumn(row, 'prog_id'), + })); + } + + async getSyscallsByFileLine($file_path: string, $start_line: number, $end_line: number) { + let rows = await this.db.exec(Syzkaller_getSyscallsByFileLine, { + $file_path, $start_line, $end_line + }); + return rows.map(row => ({ + syscall: Db.getColumn(row, 'syscall'), + prog_id: Db.getColumn(row, 'prog_id'), + })); + } + + async getAllSyscalls() { + let rows = await this.db.exec(Syzkaller_getAllSyscalls, {}); + return rows.map(row => ({ + syscall: Db.getColumn(row, 'syscall') + })); + } + + async getProgramById($prog_ids: string[]) { + let rows = await this.db.exec(Syzkaller_getProgramById, {$prog_ids: JSON.stringify($prog_ids)}); + return rows.map(row => ({ + prog_code: Db.getColumn(row, 'prog_code') + })); + } +} diff --git a/analysis/kernel/dashboard/frontend/src/types.ts b/analysis/kernel/dashboard/frontend/src/types.ts new file mode 100644 index 00000000..a15b8d46 --- /dev/null +++ b/analysis/kernel/dashboard/frontend/src/types.ts @@ -0,0 +1,90 @@ +export type CHILD_EDGE_LOCATIONS = { + source_message: string; + target_startLine: string; + target_uri: string; + target_function_end_line: string; +}; + +export type PARENT_EDGE_LOCATIONS = { + source_uri: string; + source_function_start_line: string; + source_function_end_line: string; + source_function_names: string; + syscalls: string; +}; + +export type COVERAGE_PROGRAMS = { + file_path: string; + code_line_no: string; + prog_id: string; +}; + +export type SYSCALL_NAMES = { + syscall: string; +}; + +export type SYZKALL_NAMES = { + syscall: string; + prog_id: string; +}; + +export type STRUCT_RESULTS = { + struct_name: string; + struct_size: string; + allocSizeMax: string; + allocSizeMin: string; + allocSize: string; + call_startLine: string; + call_uri: string; + call_value: string; + depth: string; + flagsMax: string; + flagsMin: string; + flags: string; + function: string; + function_start_line: string; + function_end_line: string; + syscalls_num: string; + kmalloc_bucket_name: string; + kmalloc_cgroup_name: string; + kmalloc_dyn: string; +}; + +export type FIELDS_RESULTS = { + bits_offset: string; + bits_end: string; + type: string; + parent_type: string; + name: string; +}; + +export type ACCESS_RESULTS = { + struct_name: string; + type: string; + full_field_name: string; + bits_offset: string; + bits_end: string; + field_access_type: string; + field_access_start_line: string; + function_file_path: string; + function_start_line: string; + function_end_line: string; + syscalls_num: string; +}; + +export type CONDITION_LOCATIONS = { + syscall: string; + condition_type: string; + condition_argument: string; + function_call_file_path: string; + function_call_start_line: string; + function_call_end_line: string; +}; + +export type ALL_EDGES_LOCATIONS = { + edge_type: string; + identifier: string; + file_path: string; + start_line: string; + end_line: string; +}; \ No newline at end of file diff --git a/analysis/kernel/dashboard/frontend/src/views/access/access-call.html b/analysis/kernel/dashboard/frontend/src/views/access/access-call.html new file mode 100644 index 00000000..bfedb21f --- /dev/null +++ b/analysis/kernel/dashboard/frontend/src/views/access/access-call.html @@ -0,0 +1,7 @@ +
type
+
+
+ ( syscalls) +
\ No newline at end of file diff --git a/analysis/kernel/dashboard/frontend/src/views/access/access-field.html b/analysis/kernel/dashboard/frontend/src/views/access/access-field.html new file mode 100644 index 00000000..a59679f6 --- /dev/null +++ b/analysis/kernel/dashboard/frontend/src/views/access/access-field.html @@ -0,0 +1,13 @@ +
+ +
+ struct_name field +
(type) +
+ +
+
\ No newline at end of file diff --git a/analysis/kernel/dashboard/frontend/src/views/access/access-list.html b/analysis/kernel/dashboard/frontend/src/views/access/access-list.html new file mode 100644 index 00000000..3b49033e --- /dev/null +++ b/analysis/kernel/dashboard/frontend/src/views/access/access-list.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/analysis/kernel/dashboard/frontend/src/views/access/access.ts b/analysis/kernel/dashboard/frontend/src/views/access/access.ts new file mode 100644 index 00000000..bef7ce3f --- /dev/null +++ b/analysis/kernel/dashboard/frontend/src/views/access/access.ts @@ -0,0 +1,73 @@ +import { View } from "../view"; +import { Heap } from "../../controllers/heap"; +import { interpolateHtml } from "../html"; + +import accessListHtml from "./access-list.html"; +import accessFieldHtml from "./access-field.html"; +import accessCallHtml from "./access-call.html"; + +import { ACCESS_RESULTS } from "../../types"; + +export class Access { + private static ROOT_NODE: HTMLDivElement | null = null; + private static ROOT_NODE_CLASS_NAME: string = 'access-root-node'; + + static async getRootNode(): Promise { + return Access.ROOT_NODE = await View.getRootNode(Access.ROOT_NODE, Access.ROOT_NODE_CLASS_NAME); + } + constructor(private reachability: Heap) { } + + static generateAccessList() { + return interpolateHtml(accessListHtml, new Map())!; + } + + static generateAccessField(entry: ACCESS_RESULTS, size: number, kmalloc: string) { + return interpolateHtml(accessFieldHtml, new Map([ + [`//*[@class="access-field-struct-name"]/text()`, `${entry.struct_name}`], + [`//*[@class="access-field-struct-name"]/@href`, `#!heap/${kmalloc}/${entry.struct_name}`], + [`//*[@class="access-field-full-name"]/text()`, `${entry.full_field_name}`], + [`//*[@class="access-field-type"]/text()`, `${entry.type}`], + [`//*[@class="access-field-bit-range-link"]/@href`, `#!heap/${kmalloc}/${entry.struct_name}/${entry.bits_offset}..${entry.bits_end}`], + [`//*[@class="access-field-bits-offset"]/text()`, `${entry.bits_offset}`], + [`//*[@class="access-field-bits-end"]/text()`, `${entry.bits_end}`], + [`//*[@class="access-field-calls-size"]/text()`, `${size}`] + ]))!; + } + + static generateAccessCall(entry: ACCESS_RESULTS) { + return interpolateHtml(accessCallHtml, new Map([ + [`//*[@class="access-call-type"]/text()`, entry.field_access_type], + [`//*[@class="access-call-link"]/@href`, `#${entry.function_file_path}:${entry.function_start_line}:${entry.function_end_line}`], + [`//*[@class="access-call-link"]/text()`, `${entry.function_file_path}#${entry.field_access_start_line}`], + [`//*[@class="access-call-syscalls-num"]/text()`, entry.syscalls_num] + ]))!; + } + + displayAccess(parentNode: HTMLDivElement, results: ACCESS_RESULTS[], kmalloc: string, offset: number) { + let accessListFragment = Access.generateAccessList(); + let accessList = accessListFragment.querySelector('.access-list')!; + parentNode.replaceChildren(accessListFragment); + + results.reduce((map, result) => { + let key = JSON.stringify((({ + struct_name, full_field_name, bits_offset, bits_end, type + }) => ({ + struct_name, full_field_name, bits_offset, bits_end, type + }))(result)); + if (!map.has(key)) { + map.set(key, new Set); + } + map.get(key)!.add(result); + return map; + }, new Map>).forEach((accesses, key) => { + let accessFieldFragment = Access.generateAccessField( + accesses.entries().next().value[0], accesses.size, kmalloc); + let accessCalls = accessFieldFragment.querySelector('.access-calls')!; + accessList.appendChild(accessFieldFragment); + accesses.forEach(access => { + let accessCallFragment = Access.generateAccessCall(access); + accessCalls.appendChild(accessCallFragment); + }); + }); + } +} \ No newline at end of file diff --git a/analysis/kernel/dashboard/frontend/src/views/dialog/dialog-conditions-description.html b/analysis/kernel/dashboard/frontend/src/views/dialog/dialog-conditions-description.html new file mode 100644 index 00000000..74b8ad5b --- /dev/null +++ b/analysis/kernel/dashboard/frontend/src/views/dialog/dialog-conditions-description.html @@ -0,0 +1 @@ +
  • \ No newline at end of file diff --git a/analysis/kernel/dashboard/frontend/src/views/dialog/dialog-conditions.html b/analysis/kernel/dashboard/frontend/src/views/dialog/dialog-conditions.html new file mode 100644 index 00000000..17b2655d --- /dev/null +++ b/analysis/kernel/dashboard/frontend/src/views/dialog/dialog-conditions.html @@ -0,0 +1,5 @@ + +

    syscall conditions

    +
      +
      +
      \ No newline at end of file diff --git a/analysis/kernel/dashboard/frontend/src/views/dialog/dialog-edge-selection-description.html b/analysis/kernel/dashboard/frontend/src/views/dialog/dialog-edge-selection-description.html new file mode 100644 index 00000000..bb73f182 --- /dev/null +++ b/analysis/kernel/dashboard/frontend/src/views/dialog/dialog-edge-selection-description.html @@ -0,0 +1 @@ +
    • \ No newline at end of file diff --git a/analysis/kernel/dashboard/frontend/src/views/dialog/dialog-edge-selection.html b/analysis/kernel/dashboard/frontend/src/views/dialog/dialog-edge-selection.html new file mode 100644 index 00000000..26be4b9b --- /dev/null +++ b/analysis/kernel/dashboard/frontend/src/views/dialog/dialog-edge-selection.html @@ -0,0 +1,7 @@ + +

      calls

      +
      +
        + + +
        \ No newline at end of file diff --git a/analysis/kernel/dashboard/frontend/src/views/dialog/dialog-syzkaller-program.html b/analysis/kernel/dashboard/frontend/src/views/dialog/dialog-syzkaller-program.html new file mode 100644 index 00000000..c44a5105 --- /dev/null +++ b/analysis/kernel/dashboard/frontend/src/views/dialog/dialog-syzkaller-program.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/analysis/kernel/dashboard/frontend/src/views/dialog/dialog-syzkaller.html b/analysis/kernel/dashboard/frontend/src/views/dialog/dialog-syzkaller.html new file mode 100644 index 00000000..4c874ac5 --- /dev/null +++ b/analysis/kernel/dashboard/frontend/src/views/dialog/dialog-syzkaller.html @@ -0,0 +1,5 @@ + +

        syzkaller programs

        +
        +
        +
        \ No newline at end of file diff --git a/analysis/kernel/dashboard/frontend/src/views/dialog/dialog.css b/analysis/kernel/dashboard/frontend/src/views/dialog/dialog.css new file mode 100644 index 00000000..0e0d705b --- /dev/null +++ b/analysis/kernel/dashboard/frontend/src/views/dialog/dialog.css @@ -0,0 +1,32 @@ +:is(.dialog-syzkaller, .dialog-conditions, .dialog-edge-selection) { + color: #f08d49; + padding: 2px; + background: hsla(24, 20%, 20%, 1); + box-shadow: 0 0 0.5em #999; + width: min-content; + min-width: 20vw; + overflow: auto; + font-size: small; + h3 { + text-align: center; + } + code { + display: block; + white-space: pre-wrap; + padding: 5px; + margin: 2px; + background: #222; + color: #ccc; + } + a { + color: white; + } + input, button { + color: #f08d49; + background: hsla(24, 20%, 50%, 0.4); + } + + &::backdrop { + background: #22222280; + } +} \ No newline at end of file diff --git a/analysis/kernel/dashboard/frontend/src/views/dialog/dialog.ts b/analysis/kernel/dashboard/frontend/src/views/dialog/dialog.ts new file mode 100644 index 00000000..5d2a2e61 --- /dev/null +++ b/analysis/kernel/dashboard/frontend/src/views/dialog/dialog.ts @@ -0,0 +1,157 @@ +import { View } from "../view"; +import { Reachability } from "../../controllers/reachability"; + +import { interpolateHtml } from "../html"; +import dialogSyzkallerHtml from "./dialog-syzkaller.html"; +import dialogSyzkallerProgramHtml from "./dialog-syzkaller-program.html"; +import dialogConditionsHtml from "./dialog-conditions.html"; +import dialogConditionsDecriptionHtml from "./dialog-conditions-description.html"; +import dialogEdgeSelectionHtml from "./dialog-edge-selection.html"; +import dialogEdgeSelectionDescriptionHtml from "./dialog-edge-selection-description.html"; + +import "./dialog.css"; +import { ALL_EDGES_LOCATIONS, CONDITION_LOCATIONS } from "../../types"; + +export class Dialog { + private static ROOT_NODE: HTMLDivElement | null = null; + private static ROOT_NODE_CLASS_NAME: string = 'dialog-root-node'; + + static async getRootNode(): Promise { + return Dialog.ROOT_NODE = await View.getRootNode(Dialog.ROOT_NODE, Dialog.ROOT_NODE_CLASS_NAME); + } + constructor(private reachability: Reachability) { } + + static generateSyzkallerDialog() { + return interpolateHtml(dialogSyzkallerHtml, new Map()); + } + + static generateConditionsDialog() { + return interpolateHtml(dialogConditionsHtml, new Map()); + } + + static generateSyzkallerDialogProgram(code: string) { + return interpolateHtml(dialogSyzkallerProgramHtml, new Map([ + [`//code/text()`, code] + ])); + } + + static generateConditionDecription(condition: string, file_path: string, start_line: string, end_line: string) { + return interpolateHtml(dialogConditionsDecriptionHtml, new Map([ + [`//a/text()`, condition], + [`//a/@href`, `#${file_path}:${start_line}:${end_line}`] + ])); + } + + static generateEdgeSelectionDialog() { + return interpolateHtml(dialogEdgeSelectionHtml, new Map()); + } + + static generateEdgeSelectionDescription(type: string, identifier: string) { + return interpolateHtml(dialogEdgeSelectionDescriptionHtml, new Map([ + [`//button/text()`, `${type}: ${identifier}`] + ])); + } + + displaySyzkallerPrograms(parentNode: HTMLDivElement, programs: { prog_code: string }[]) { + let dialogFragment = Dialog.generateSyzkallerDialog(); + let syzkallerPrograms = dialogFragment.querySelector('.syzkaller-programs') as HTMLDivElement; + let dialog = dialogFragment.querySelector('dialog'); + for (let program of programs) { + syzkallerPrograms.appendChild(Dialog.generateSyzkallerDialogProgram(program.prog_code)); + } + parentNode.replaceChildren(dialogFragment); + dialog!.showModal() + } + + displayConditions(parentNode: HTMLDivElement, conditions: CONDITION_LOCATIONS[]) { + let dialogFragment = Dialog.generateConditionsDialog(); + let syscallConditions = dialogFragment.querySelector('.syscall-conditions') as HTMLDivElement; + let dialog = dialogFragment.querySelector('dialog'); + for (let condition of conditions) { + syscallConditions.appendChild(Dialog.generateConditionDecription( + `${condition.condition_type}(${ + condition.condition_type.match(/capable/)? + this.bitMaskToNames(condition.condition_argument): + condition.condition_argument + })`, + condition.function_call_file_path, + condition.function_call_start_line, + condition.function_call_end_line + )); + } + parentNode.replaceChildren(dialogFragment); + dialog!.showModal(); + } + + displayEdgeSelection(parentNode: HTMLDivElement, edges: ALL_EDGES_LOCATIONS[], functionName: string, node: Node, childEdges: HTMLElement) { + let dialogFragment = Dialog.generateEdgeSelectionDialog(); + let edgeSelection = dialogFragment.querySelector('.edge-selection') as HTMLUListElement; + let dialog = dialogFragment.querySelector('dialog'); + for (let edge of edges) { + let edgeNode; + if (edge.identifier == functionName || edge.identifier == 'call to ' + functionName) { + edgeNode = Dialog.generateEdgeSelectionDescription( + edge.edge_type, + edge.identifier + ); + edgeNode.querySelector('button')!.onclick = () => { + console.log('yikes'); + this.reachability.showFileLine(childEdges, edge.file_path, Number(edge.start_line), Number(edge.end_line), edge.identifier, node); + }; + edgeSelection.appendChild(edgeNode); + } else { + console.log('refused to link', edge.identifier, 'and', functionName); + } + } + parentNode.replaceChildren(dialogFragment); + dialog!.showModal(); + } + + bitMaskToNames(capability: string) { + if (!capability.match(/^\d+$/)) return capability; + const BIT_TO_CAP = new Map([ + [0, 'CAP_CHOWN'], + [1, 'CAP_DAC_OVERRIDE'], + [2, 'CAP_DAC_READ_SEARCH'], + [3, 'CAP_FOWNER'], + [4, 'CAP_FSETID'], + [5, 'CAP_KILL'], + [6, 'CAP_SETGID'], + [7, 'CAP_SETUID'], + [8, 'CAP_SETPCAP'], + [9, 'CAP_LINUX_IMMUTABLE'], + [10, 'CAP_NET_BIND_SERVICE'], + [11, 'CAP_NET_BROADCAST'], + [12, 'CAP_NET_ADMIN'], + [13, 'CAP_NET_RAW'], + [14, 'CAP_IPC_LOCK'], + [15, 'CAP_IPC_OWNER'], + [16, 'CAP_SYS_MODULE'], + [17, 'CAP_SYS_RAWIO'], + [18, 'CAP_SYS_CHROOT'], + [19, 'CAP_SYS_PTRACE'], + [20, 'CAP_SYS_PACCT'], + [21, 'CAP_SYS_ADMIN'], + [22, 'CAP_SYS_BOOT'], + [23, 'CAP_SYS_NICE'], + [24, 'CAP_SYS_RESOURCE'], + [25, 'CAP_SYS_TIME'], + [26, 'CAP_SYS_TTY_CONFIG'], + [27, 'CAP_MKNOD'], + [28, 'CAP_LEASE'], + [29, 'CAP_AUDIT_WRITE'], + [30, 'CAP_AUDIT_CONTROL'], + [31, 'CAP_SETFCAP'], + [32, 'CAP_MAC_OVERRIDE'], + [33, 'CAP_MAC_ADMIN'], + [34, 'CAP_SYSLOG'], + [35, 'CAP_WAKE_ALARM'], + [36, 'CAP_BLOCK_SUSPEND'], + [37, 'CAP_AUDIT_READ'], + [38, 'CAP_PERFMON'], + [39, 'CAP_BPF'], + [40, 'CAP_CHECKPOINT_RESTORE'], + ]); + return BIT_TO_CAP.get(Number(capability)) || "unknown"; + } +} \ No newline at end of file diff --git a/analysis/kernel/dashboard/frontend/src/views/fields/fields.ts b/analysis/kernel/dashboard/frontend/src/views/fields/fields.ts new file mode 100644 index 00000000..1e3a544a --- /dev/null +++ b/analysis/kernel/dashboard/frontend/src/views/fields/fields.ts @@ -0,0 +1,68 @@ +import { View } from "../view"; +import { Heap } from "../../controllers/heap"; +import { FIELDS_RESULTS } from "../../types"; +import TreeView, { TreeViewItem } from "js-treeview"; + + +export class Fields { + private static ROOT_NODE: HTMLDivElement | null = null; + private static ROOT_NODE_CLASS_NAME: string = 'fields-root-node'; + + static async getRootNode(): Promise { + return Fields.ROOT_NODE = await View.getRootNode(Fields.ROOT_NODE, Fields.ROOT_NODE_CLASS_NAME); + } + constructor(private heap: Heap) { } + + displayStructs(parentNode: HTMLDivElement, fieldResults: FIELDS_RESULTS[], struct: string, kmalloc: string) { + let root = { name: '', children: [] } as TreeViewItem; + let fullName = false; + + fieldResults.forEach(field => { + let pathParts = `${struct}.${field.name}`.split(/[.]|[/][*]|[*][/]/); + let leaf = root; + + pathParts.forEach((part, index) => { + if (part == "") { + part = `union`; + } + let nextLeaf: TreeViewItem = leaf.children.find(e => e.name == part); + if (!nextLeaf) { + nextLeaf = { + expanded: true, + get name() { + if (fullName) { + return `[${this.data!.bits_offset}..${this.data!.bits_end}] ${part} (${this.data!.type})`; + } + return part; + }, + children: [], + data: { + bits_offset: field.bits_offset, + bits_end: field.bits_end, + name: field.name, + parent_type: '?', + type: part == 'union' ? '...' : 'struct' + } + }; + leaf.children.push(nextLeaf); + } else if (parseInt(nextLeaf.data!.bits_end) < parseInt(field.bits_end)) { + nextLeaf.data!.bits_end = field.bits_end + } + if (index == pathParts.length - 1) { + nextLeaf.data!.type = field.type; + nextLeaf.data!.parent_type = field.parent_type; + } else if (index == pathParts.length - 2) { + nextLeaf.data!.type = field.parent_type; + } + leaf = nextLeaf; + }); + }); + fullName = true; + parentNode.replaceChildren(); + const treeview = new TreeView(root.children, parentNode); + treeview.on('select', e => { + let field = e.data.data!; + location.hash = `!heap/${kmalloc}/${struct}/${field.bits_offset}..${field.bits_end}`; + }); + } +} \ No newline at end of file diff --git a/analysis/kernel/dashboard/frontend/src/views/html.ts b/analysis/kernel/dashboard/frontend/src/views/html.ts new file mode 100644 index 00000000..11a63922 --- /dev/null +++ b/analysis/kernel/dashboard/frontend/src/views/html.ts @@ -0,0 +1,14 @@ +export function interpolateHtml(html: string, rules: Map) { + let fragment = document.createDocumentFragment(); + let doc = new DOMParser().parseFromString(html, "text/html"); + let changes: { next: Node; value: string; }[] = []; + rules.forEach((value, xpath) => { + let results = document.evaluate(xpath, doc, null, XPathResult.ANY_TYPE, null); + for (let next = results.iterateNext(); next; next = results.iterateNext()) { + changes.push({next, value}); + } + }); + changes.forEach(({next, value}) => next.nodeValue = value); + [...doc.body.childNodes].forEach(node => fragment.appendChild(node)); + return fragment; +}; \ No newline at end of file diff --git a/analysis/kernel/dashboard/frontend/src/views/navigation/navigation.css b/analysis/kernel/dashboard/frontend/src/views/navigation/navigation.css new file mode 100644 index 00000000..1d5c0307 --- /dev/null +++ b/analysis/kernel/dashboard/frontend/src/views/navigation/navigation.css @@ -0,0 +1,14 @@ +.leader-line { + z-index: 1; +} + +.navigation-panning { + transform: scale(var(--navigation-panning-scale)) !important; + transform-origin: var(--navigation-panning-x) var(--navigation-panning-y) !important; + transition: all 0.1s linear !important; + & * { + /* override sticky positioning on children */ + --panning-sticky: static !important; + --panning-visible: hidden !important; + } +} diff --git a/analysis/kernel/dashboard/frontend/src/views/navigation/navigation.ts b/analysis/kernel/dashboard/frontend/src/views/navigation/navigation.ts new file mode 100644 index 00000000..3ebb8c7d --- /dev/null +++ b/analysis/kernel/dashboard/frontend/src/views/navigation/navigation.ts @@ -0,0 +1,165 @@ +import LeaderLine from "leader-line-new"; +import { Reachability } from "../../controllers/reachability"; + +import "./navigation.css"; + +export class Navigation { + lines: Map> = new Map(); + + constructor(private reachability: Reachability) { } + + createLine(from: Node, to: Node) { + const line = new LeaderLine(from, to, { + outline: true, + color: 'transparent', + endPlugOutline: true, + hide: true + }); + line.show('draw'); + return line; + } + + addLine(from: Node, to: Node) { + if(this.lines.has(from)) { + let fromMap = this.lines.get(from); + if (!fromMap!.has(to)) { + fromMap!.set(to, this.createLine(from, to)); + } + } else { + let fromMap = new Map(); + fromMap.set(to, this.createLine(from, to)); + this.lines.set(from, fromMap); + } + } + + updateLinesPositions() { + [...this.lines.values()].flatMap( + fromMap => [...fromMap.values()] + ).forEach(line => { + if ( + (line.start as any).checkVisibility() && + (line.end as any).checkVisibility() + ) { + line.position(); + line.show(); + } else { + line.hide(); + } + }); + } + + initNavigation() { + let element = document.documentElement; + let initialPosition = { top: 0, left: 0, x: 0, y: 0 }; + let lastPosition = {x: 0, y: 0}; + let panPosition = {x: 0, y: 0, scrollLeft: 0, scrollTop: 0}; + let panScale = 1; + + const stopSelection = (e: Event) => { + e.preventDefault(); + }; + + const maybeStopDraggingCtrlKey = (e: KeyboardEvent) => { + if (!e.ctrlKey) { stopDragging(e); } + }; + + const startDragging = (e: MouseEvent) => { + if (e.ctrlKey) { + initialPosition.left = element.scrollLeft; + initialPosition.top = element.scrollTop; + initialPosition.x = e.clientX; + initialPosition.y = e.clientY; + element.addEventListener('mousemove', whileDragging, {passive: true}); + element.addEventListener('mouseup', stopDragging); + element.addEventListener('mouseleave', stopDragging); + element.addEventListener('dragend', stopDragging); + element.addEventListener('keyup', maybeStopDraggingCtrlKey); + element.addEventListener('selectstart', stopSelection); + } + }; + + const whileDragging = (e: MouseEvent) => { + if (e.ctrlKey) { + const deltaX = e.clientX - initialPosition.x; + const deltaY = e.clientY - initialPosition.y; + element.scrollLeft = initialPosition.left - deltaX; + element.scrollTop = initialPosition.top - deltaY; + } else { + stopDragging(e); + } + }; + + const stopDragging = (e: Event) => { + element.removeEventListener('mousemove', whileDragging); + element.removeEventListener('mouseup', stopDragging); + element.removeEventListener('mouseleave', stopDragging); + element.removeEventListener('dragend', stopDragging); + element.removeEventListener('keyup', maybeStopDraggingCtrlKey); + element.removeEventListener('selectstart', stopSelection); + }; + + const stopClicks = (e: MouseEvent) => { + e.preventDefault(); + e.stopImmediatePropagation(); + return false; + }; + + const whilePanning = (e: MouseEvent) => { + lastPosition = {x: e.clientX, y: e.clientY}; + }; + + const maybeStopPanningAltKey = (e: KeyboardEvent) => { + if (!e.altKey) { + stopPanning(); + } + }; + + const startPanning = (e: KeyboardEvent) => { + if (e.altKey) { + const widthProp = (screen.availWidth) / (element.scrollWidth + element.scrollLeft + lastPosition.x); + const heightProp = (screen.availHeight) / (element.scrollHeight + element.scrollTop + lastPosition.y); + panScale = Math.min(1, widthProp, heightProp); + panPosition = {...lastPosition, scrollLeft: element.scrollLeft, scrollTop: element.scrollTop}; + console.log('panPosition', panPosition); + + element.style.setProperty( + '--navigation-panning-scale', + String(panScale) + ); + element.style.setProperty( + '--navigation-panning-x', + String(element.scrollLeft + lastPosition.x) + "px" + ); + element.style.setProperty( + '--navigation-panning-y', + String(element.scrollTop + lastPosition.y) + "px" + ); + element.classList.add('navigation-panning'); + element.addEventListener('keyup', maybeStopPanningAltKey); + element.addEventListener('click', stopClicks, {capture: true}); + addEventListener('blur', stopPanning); + element.removeEventListener('keydown', startPanning); + } + }; + + const stopPanning = () => { + const leftDelta = panPosition.scrollLeft - element.scrollLeft; + const topDelta = panPosition.scrollTop - element.scrollTop; + const xDelta = lastPosition.x - panPosition.x; + const yDelta = lastPosition.y - panPosition.y; + element.classList.remove('navigation-panning'); + element.scrollBy({ + left: (xDelta - leftDelta) / panScale - (xDelta - leftDelta), + top: (yDelta - topDelta) / panScale - (yDelta - topDelta), + }); + element.removeEventListener('keyup', maybeStopPanningAltKey); + element.removeEventListener('click', stopClicks, {capture: true}); + removeEventListener('blur', stopPanning); + element.addEventListener('keydown', startPanning); + }; + + element.addEventListener('mousedown', startDragging); + element.addEventListener('mousemove', whilePanning, {passive: true}); + element.addEventListener('keydown', startPanning); + } +} \ No newline at end of file diff --git a/analysis/kernel/dashboard/frontend/src/views/prism.ts b/analysis/kernel/dashboard/frontend/src/views/prism.ts new file mode 100644 index 00000000..cf8eb685 --- /dev/null +++ b/analysis/kernel/dashboard/frontend/src/views/prism.ts @@ -0,0 +1,11 @@ +import Prism from 'prismjs'; +Prism.manual = true; + +import 'prismjs/themes/prism-tomorrow.css'; +import 'prismjs/components/prism-c'; +import 'prismjs/plugins/line-numbers/prism-line-numbers'; +import 'prismjs/plugins/line-numbers/prism-line-numbers.css'; +import 'prismjs/plugins/line-highlight/prism-line-highlight'; +import 'prismjs/plugins/line-highlight/prism-line-highlight.css'; + +export default Prism; diff --git a/analysis/kernel/dashboard/frontend/src/views/snippets/code-file.html b/analysis/kernel/dashboard/frontend/src/views/snippets/code-file.html new file mode 100644 index 00000000..a79cbb89 --- /dev/null +++ b/analysis/kernel/dashboard/frontend/src/views/snippets/code-file.html @@ -0,0 +1,6 @@ +
        +
        +
        +
        +
        +
        \ No newline at end of file diff --git a/analysis/kernel/dashboard/frontend/src/views/snippets/code-filter-syscall.html b/analysis/kernel/dashboard/frontend/src/views/snippets/code-filter-syscall.html new file mode 100644 index 00000000..b8eec765 --- /dev/null +++ b/analysis/kernel/dashboard/frontend/src/views/snippets/code-filter-syscall.html @@ -0,0 +1,6 @@ +
        +
        + +
        +
        +
        \ No newline at end of file diff --git a/analysis/kernel/dashboard/frontend/src/views/snippets/code-filter-table.html b/analysis/kernel/dashboard/frontend/src/views/snippets/code-filter-table.html new file mode 100644 index 00000000..22aa3a2e --- /dev/null +++ b/analysis/kernel/dashboard/frontend/src/views/snippets/code-filter-table.html @@ -0,0 +1,38 @@ +
        +
        + +
        +
        + +
        +
        + +
        +
        + +
        +
        \ No newline at end of file diff --git a/analysis/kernel/dashboard/frontend/src/views/snippets/code-snippet.html b/analysis/kernel/dashboard/frontend/src/views/snippets/code-snippet.html new file mode 100644 index 00000000..cd2f97c2 --- /dev/null +++ b/analysis/kernel/dashboard/frontend/src/views/snippets/code-snippet.html @@ -0,0 +1,9 @@ +
        + +
        +
        +
        +
        +
         
        +
        +
        diff --git a/analysis/kernel/dashboard/frontend/src/views/snippets/filter.ts b/analysis/kernel/dashboard/frontend/src/views/snippets/filter.ts new file mode 100644 index 00000000..c000f2c9 --- /dev/null +++ b/analysis/kernel/dashboard/frontend/src/views/snippets/filter.ts @@ -0,0 +1,171 @@ +import { Reachability } from "../../controllers/reachability"; +import { CONDITION_LOCATIONS, SYSCALL_NAMES, SYZKALL_NAMES } from "../../types"; +import { interpolateHtml } from "../html"; + +import codeFilterTableHtml from "./code-filter-table.html"; +import codeFilterSyscallHtml from "./code-filter-syscall.html"; + +export class Filter { + private static changeAbortControllers = new WeakMap(); + + private static uidCounter = 0; + + static generateCodeFilterSyscall(syscall: string, coverage: string, conditions: string): any { + let uid = `uid-${Filter.uidCounter++}`; + return interpolateHtml(codeFilterSyscallHtml, new Map([ + [`//div[@data-filtered-syscall]/@data-filtered-syscall`, syscall], + [`//div[@data-filtered-coverage]/@data-filtered-coverage`, `any ${coverage}`], + [`//div[@data-filtered-conditions]/@data-filtered-conditions`, `any ${conditions}`], + [`//div[@class="code-filter-syscall-checkbox"]/input/@id`, uid], + [`//div[@class="code-filter-syscall-checkbox"]/input/@value`, syscall], + [`//label[@class="code-filter-syscall-name"]/@for`, uid], + [`//label[@class="code-filter-syscall-name"]/text()`, syscall], + [`//div[@class="code-filter-syscall-coverage"]/text()`, coverage], + [`//div[@class="code-filter-syscall-conditions"]/text()`, conditions], + ])); + } + + static generateCodeFilterTable() { + let uid = `uid-${Filter.uidCounter++}`; + return interpolateHtml(codeFilterTableHtml, new Map([ + [`//input/@id`, uid], + [`//label/@for`, uid] + ])); + } + + constructor(private reachability: Reachability) { } + + async displaySyscalls( + syscalls: SYSCALL_NAMES[], syskallsPromise: Promise, conditionsPromise: Promise, + codeFilter: HTMLDivElement + ) { + let syzkalls = await syskallsPromise; + let codeBody = codeFilter.closest('.code-body') as HTMLDivElement; + let outerCodeBody = codeBody.closest('.code-body.filter-syscall[data-filter-syscall]') as HTMLDivElement | null; + let defaultSelectedSyscallsFromParent = null; + if (outerCodeBody) { + defaultSelectedSyscallsFromParent = new Set(outerCodeBody.dataset.filterSyscall?.split(' ')); + } + let defaultUnselectedSyscallsFromChildren = new Set(); + codeBody.querySelectorAll('.code-body .code-filter-syscall-checkbox-input:not(:checked)').forEach( + input => defaultUnselectedSyscallsFromChildren.add((input as HTMLInputElement).value)); + let syscallMap = new Map(); + + const updateCheckboxes = () => { + codeBody.dataset.filterSyscall = [...syscallMap.entries()].filter(v => + v[1].checked && (v[1] as any).checkVisibility() + ).map(v => v[0]).join(' '); + }; + + const changeHandler = (e: Event) => { + let eventElement = e.target as HTMLInputElement; + let checkboxElement = syscallMap.get(eventElement.value); + if (checkboxElement) { + checkboxElement.checked = eventElement.checked; + } + updateCheckboxes(); + }; + + if (codeFilter.children.length) { + this.clearSyscallFilter(codeBody, codeFilter); + } else { + this.applySyscallFilter( + syscalls, syzkalls, await conditionsPromise, + codeBody, codeFilter, + syscallMap, + updateCheckboxes, changeHandler, + defaultSelectedSyscallsFromParent, defaultUnselectedSyscallsFromChildren + ); + } + } + + private clearSyscallFilter(codeBody: HTMLDivElement, codeFilter: HTMLDivElement) { + codeBody.classList.remove('filter-syscall'); + codeFilter.replaceChildren(); + Filter.changeAbortControllers.get(codeBody)?.abort(); + } + + private applySyscallFilter( + syscalls: SYSCALL_NAMES[], + syzkalls: SYZKALL_NAMES[], + conditions: CONDITION_LOCATIONS[], + codeBody: HTMLDivElement, + codeFilter: HTMLDivElement, + syscallMap: Map, + updateCheckboxes: () => void, + changeHandler: (e: Event) => void, + defaultSelectedSyscallsFromParent: Set | null, + defaultUnselectedSyscallsFromChildren: Set + ) { + codeBody.classList.add('filter-syscall'); + let codeFilterTableFragment = Filter.generateCodeFilterTable(); + let codeFilterTable = codeFilterTableFragment.querySelector('.code-filter-table') as HTMLDivElement; + codeFilter.replaceChildren(codeFilterTableFragment); + + codeFilterTable.querySelectorAll('select').forEach(select => + select.addEventListener('change', (e: Event) => { + codeFilterTable.setAttribute(`data-filter-${select.name}`, `${select.value}`); + updateCheckboxes(); + }) + ); + + codeFilterTable.querySelector('.code-filter-header input')?.addEventListener('change', (e) => { + let master = e.target as HTMLInputElement; + codeFilterTable.querySelectorAll('.code-filter-syscall-checkbox-input').forEach(elem => { + const input = elem as HTMLInputElement; + if ((elem as any).checkVisibility()) { + input.checked = master.checked; + } + }); + updateCheckboxes(); + }); + + for (let syscall of syscalls) { + const hasCoverage = syzkalls.some(s => s.syscall == syscall.syscall); + const syzPrograms = syzkalls.filter(s => s.syscall == syscall.syscall).map(s => s.prog_id); + const filteredConditions = conditions.filter(s => s.syscall == syscall.syscall); + let conditionSummarySet = new Set; + filteredConditions.forEach((entry) => { + conditionSummarySet.add(entry.condition_type[0]); + }); + let conditionSummary = [...conditionSummarySet]; + + let syscallFragment = Filter.generateCodeFilterSyscall( + syscall.syscall, + hasCoverage ? 'yes' : 'no', + conditionSummary.join(' '), + ); + let syscallCheckbox = syscallFragment.querySelector('input') as HTMLInputElement; + let syzkallerLink = syscallFragment.querySelector('.code-filter-syscall-coverage') as HTMLDivElement; + let conditionLink = syscallFragment.querySelector('.code-filter-syscall-conditions') as HTMLDivElement; + syscallMap.set(syscall.syscall, syscallCheckbox); + codeFilterTable.appendChild(syscallFragment); + + if (defaultSelectedSyscallsFromParent) { + syscallCheckbox.checked = defaultSelectedSyscallsFromParent.has(syscall.syscall); + } + syscallCheckbox.checked = syscallCheckbox.checked && !defaultUnselectedSyscallsFromChildren.has(syscall.syscall); + + if (hasCoverage) { + syzkallerLink.classList.add('code-filter-syscall-coverage-link'); + syzkallerLink.addEventListener('click', e => { + this.reachability.displaySyzkallerPrograms(syzPrograms); + }); + } + + if (conditionSummary.length) { + conditionLink.classList.add('code-filter-syscall-condition-link'); + conditionLink.addEventListener('click', e => { + this.reachability.displayConditions(filteredConditions); + }); + } + } + + updateCheckboxes(); + let abortController = new AbortController; + Filter.changeAbortControllers.set(codeBody, abortController); + codeBody.addEventListener('change', changeHandler, { + signal: abortController.signal + }); + } +} \ No newline at end of file diff --git a/analysis/kernel/dashboard/frontend/src/views/snippets/snippets.css b/analysis/kernel/dashboard/frontend/src/views/snippets/snippets.css new file mode 100644 index 00000000..73970998 --- /dev/null +++ b/analysis/kernel/dashboard/frontend/src/views/snippets/snippets.css @@ -0,0 +1,284 @@ +label.collapse-button { + display: block; + margin: 5px; + padding: 0 5px; + order: 3; + font-size: small; + width: 1em; + z-index: 10; + background-color: inherit; + &:hover { + outline: 1px outset white; + } + &:active { + outline: 1px inset white; + } + &::before { + content: "🗕"; + color: white; + } + &:has(:checked)::before { + content: "🗖"; + } + & input[type="checkbox"] { + appearance: none; + position: absolute; + } +} + +.snippets-root-node { + /* Each file below the other */ + display: flex; + flex-direction: column; + width: fit-content; + --code-file-box-shadow: transparent; + --panning-sticky: sticky; /* can be overwritten during panning */ + & .code-file { + /* Put the children to the right of snippets */ + display: flex; + flex-direction: row; + box-shadow: var(--code-file-box-shadow) 0/*x*/ 0/*y*/ 20px/*blur*/ 0/*spread*/; + padding: 20px 0 20px 20px; + margin-top: 10px; + width: fit-content; + border-radius: 5px; + & .code-snippets { + /* Each snippet below the prev */ + display: flex; + flex-direction: column; + margin: 10px; + &:has(> .code-file-path-container :checked) > .code-body { + display: none; + } + & .code-file-path-container { + background: #2d2d2d; + color: #ccc; + padding: 10px 0 10px 10px; + font-size: larger; + position: var(--panning-sticky); + top: 0px; + border: 1px solid #888; + z-index: 1; + display: flex; + flex-direction: row; + border-radius: 5px; + & .code-file-path { + position: var(--panning-sticky); + right: 10px; + display: block; + width: fit-content; + margin: auto 10px auto auto; + z-index: 2; + } + } + & .code-body { + display: flex; + flex-direction: column; + margin: 2px 0; + align-self: var(--code-body-alignment); + &:has(> .code-metadata :checked) > .code-snippet { + display: none; + } + & .code-metadata { + display: flex; + justify-content: flex-end; + background: #2d2d2d; + position: var(--panning-sticky); + top: 50px; + border: 1px solid #888; + z-index: 1; + & .code-title { + font-size: medium; + color: white; + margin: 0 0 0 auto; + position: var(--panning-sticky); + display: flex; + flex-direction: column; + justify-content: center; + right: 0; + padding-right: 10px; + order: 2; + z-index: 2; + background: inherit; + } + & .label { + display: inline-block; + padding: 10px; + border-radius: 10%; + color: white; + font-size: small; + outline: 1px solid #888; + } + & .label-syscalls { + background-color: hsla(24, 20%, 50%,.4); + cursor: pointer; + color: #f08d49; + outline: 1px solid #f08d49; + position: sticky; + left: 0; + order: 1; + z-index: 1; + } + } + & .code-filter-table { + display: grid; + grid-template-columns: auto 1fr auto auto; + border: 1px solid #f08d49; + border-radius: 10px; + font-size: small; + position: sticky; + left: 0px; + max-width: 50vw; + width: fit-content; + overflow: auto; + scrollbar-color: #f08d49 #2d2d2d ; + max-height: 200px; + height: fit-content; + background: #222; + color: #ccc; + --filtered-syscall-appearance: auto; + --filtered-syscall-display-hit: grid; + --filtered-syscall-display: none; + + &[data-filter-coverage~="any"] [data-filtered-coverage]:not([data-filtered-coverage~="any"]), + &[data-filter-coverage~="yes"] [data-filtered-coverage]:not([data-filtered-coverage~="yes"]), + &[data-filter-coverage~="no"] [data-filtered-coverage]:not([data-filtered-coverage~="no"]) { + display: var(--filtered-syscall-display); + } + + &[data-filter-conditions~="c"] [data-filtered-conditions]:not([data-filtered-conditions~="c"]), + &[data-filter-conditions~="n"] [data-filtered-conditions]:not([data-filtered-conditions~="n"]), + &[data-filter-conditions~="m"] [data-filtered-conditions]:not([data-filtered-conditions~="m"]), + &[data-filter-conditions~="s"] [data-filtered-conditions]:not([data-filtered-conditions~="s"]), + &[data-filter-conditions~="none"] [data-filtered-conditions]:not([data-filtered-conditions~="none"]), + &[data-filter-conditions~="any"] [data-filtered-conditions]:not([data-filtered-conditions~="any"]) { + display: var(--filtered-syscall-display); + } + + .code-filter-header { + background: hsla(24, 20%, 20%); + color: #f08d49; + display: flex; + align-items: center; + position: sticky; + top: 0; + & select { + background-color: #2d2d2d; + color: #ccc; + } + & input[type="checkbox"] { + accent-color: #f08d49; + } + } + + .code-filter-syscall { + display: grid; + grid-template-columns: subgrid; + grid-column: 1 / 5; + padding: 10px; + & input[type="checkbox"] { + accent-color: #f08d49; + &:not(:checked) { + --filtered-syscall-appearance-hit: none; + appearance: var(--filtered-syscall-appearance); + } + } + &[for]:hover { + color: #f08d49; + } + .code-filter-syscall-coverage-link, .code-filter-syscall-condition-link { + text-decoration: underline; + color: #f08d49; + cursor: pointer; + } + } + } + & .code-snippet { + flex-direction: row; + display: flex; + width: max-content; + height: fit-content; + & .parent-files { + margin: 10px; + &:empty { + margin: 0; + max-width: 0; + } + flex: 1 0 auto; + display: flex; + flex-direction: column; + align-items: flex-end; + --code-body-alignment: flex-end; + --code-file-box-shadow: rgba(0, 0, 0, 0.25); + & > .code-file { + background-color: hsla(24, 20%, 50%,.4); + } + } + & .parent-browser { + background-color: #ffffff40; + min-width: 250px; + max-width: 300px; + height: fit-content; + flex: 1 0 0; + padding: 0 1em 1ex 1em; + font-size: small; + & div { + scrollbar-width: none; + &::-webkit-scrollbar { + display: none; + } + &.filtered-syscall { + --filtered-syscall-color-hit: #753911; + --filtered-syscall-highlight-hit: #ffffff80; + background-color: var(--filtered-syscall-highlight); + color: var(--filtered-syscall-color); + } + overflow: auto; + } + &::before { + content: '☎️ callers'; + display: block; + padding: 10px; + background: #0000007f; + color: white; + margin: 5px 0 20px; + font-size: small; + } + &[data-loading-finished="true"]:empty { + &::after { + content: 'empty 📵'; + font-size: x-small; + display: block; + margin: auto; + text-align: center; + } + } + } + & pre { + overflow: hidden; + align-self: flex-start; + margin: 0; + } + } + } + } + & .child-files { + margin: 10px; + --code-body-alignment: flex-start; + --code-file-box-shadow: #cccccc60 inset; + & > .code-file { + background-color: #0000007f; + clip-path: inset(-20px 0 0 -20px); + } + } + } + .navigation-edge, .navigation-struct { + text-decoration: underline; + cursor: pointer; + } + .navigation-missing-edge { + text-decoration: underline; + text-decoration-style: dotted; + cursor: context-menu; + } +} \ No newline at end of file diff --git a/analysis/kernel/dashboard/frontend/src/views/snippets/snippets.ts b/analysis/kernel/dashboard/frontend/src/views/snippets/snippets.ts new file mode 100644 index 00000000..468fa252 --- /dev/null +++ b/analysis/kernel/dashboard/frontend/src/views/snippets/snippets.ts @@ -0,0 +1,322 @@ +import { View } from "../view"; +import { Reachability } from "../../controllers/reachability"; +import { COVERAGE_PROGRAMS, CHILD_EDGE_LOCATIONS, PARENT_EDGE_LOCATIONS, SYSCALL_NAMES, SYZKALL_NAMES, CONDITION_LOCATIONS } from '../../types'; + +import Prism from "../prism"; + +import "./snippets.css"; +import codeSnippetHtml from "./code-snippet.html"; +import codeFileHtml from "./code-file.html"; +import { interpolateHtml } from "../html"; + +import TreeView, { TreeViewItem } from 'js-treeview'; +import '../treeview.css'; +import { Filter } from "./filter"; + +export class Snippets { + private static ROOT_NODE: HTMLDivElement | null = null; + private static ROOT_NODE_CLASS_NAME: string = 'snippets-root-node'; + + static async getRootNode(): Promise { + return Snippets.ROOT_NODE = await View.getRootNode(Snippets.ROOT_NODE, Snippets.ROOT_NODE_CLASS_NAME); + } + + static generateCodeBody(startLine: string, endLine: string, linesOfCode: string[], title: string = "") { + return interpolateHtml( + codeSnippetHtml, + new Map([ + [`//div[@class="code-body"]/@data-start-line`, startLine], + [`//div[@class="code-body"]/@data-end-line`, endLine], + [`//div[@class="code-metadata"]/div[@class="code-title"]/text()`, title], + [`//div[@class="code-snippet"]/pre/@data-start`, startLine], + [`//div[@class="code-snippet"]/pre/@data-line-offset`, startLine], + [`//div[@class="code-snippet"]/pre/code/text()`, linesOfCode.join('')] + ]) + )!; + } + + static generateCodeFile(filePath: string) { + return interpolateHtml(codeFileHtml, new Map([ + [`//div[@class="code-file"]/@data-file-path`, filePath], + [`//div[@class="code-file-path"]/text()`, filePath] + ])); + } + + private filter: Filter = new Filter(this.reachability); + constructor(private reachability: Reachability) { } + + displayLoading(message: string) { + console.log("Loading", message, this.reachability); + } + + createSyscallFilters(syscalls: string[]) { + let style = document.createElement('style'); + document.documentElement.appendChild(style); + syscalls.forEach(s => { + style.sheet?.insertRule( + `.filter-syscall[data-filter-syscall~=${JSON.stringify(s)}] .filtered-syscall[data-filtered-syscall~=${JSON.stringify(s)}] { + --filtered-syscall-highlight: var(--filtered-syscall-highlight-hit); + --filtered-syscall-color: var(--filtered-syscall-color-hit); + --filtered-syscall-outline: var(--filtered-syscall-outline-hit); + --filtered-syscall-opacity: var(--filtered-syscall-opacity-hit); + --filtered-syscall-appearance: var(--filtered-syscall-appearance-hit); + --filtered-syscall-display: var(--filtered-syscall-display-hit); + }`); + }); + } + + async displaySnippet( + parentNode: HTMLElement, filePath: string, linesOfCode: string[], + startLine = "", title = "", + childEdgesPromise: Promise | null = null, + parentEdgesPromise: Promise | null = null, + coveragePromise: Promise | null = null, + syscallsPromise: Promise | null = null, + syzkallsPromise: Promise | null = null, + conditionsPromise: Promise | null = null + ) { + let fileNode = parentNode.querySelector(`& > .code-file[data-file-path="${filePath}"]`) as HTMLElement; + let endLine = String(Number(startLine) + linesOfCode.length - 1); + + if (!fileNode) { + fileNode = this.createNewFileNode(filePath, parentNode); + } + + let fileCodeSnippets = fileNode.querySelector('& > .code-snippets')!; + + let surroundings = this.getSurroundingSnippets( + startLine, [...fileCodeSnippets.querySelectorAll('& > .code-body')]); + + if (surroundings.existingElement) { + return this.selectExistingElement(surroundings.existingElement); + } + + let codyBodyFragment = Snippets.generateCodeBody(startLine, endLine, linesOfCode, title); + let parentFiles = codyBodyFragment.querySelector('.parent-files') as HTMLDivElement; + let parentBrowser = codyBodyFragment.querySelector('.parent-browser') as HTMLDivElement; + let codeSnippet = codyBodyFragment.querySelector('.code-snippet pre') as HTMLPreElement; + let codeMetadata = codyBodyFragment.querySelector('.code-metadata') as HTMLDivElement; + let codeFilter = codyBodyFragment.querySelector('.code-filter') as HTMLDivElement; + + if (coveragePromise) { + let coverage = await coveragePromise; + codeSnippet.dataset.line = [... new Set(coverage.map(c => c.code_line_no))].join(','); + } + + fileCodeSnippets.insertBefore( + codyBodyFragment, surroundings.nextInSequence); + + Prism.highlightElement(codeSnippet.querySelector('code')!); + + this.focusElement(codeSnippet); + + if (childEdgesPromise) { + this.displayChildEdges(childEdgesPromise, fileNode, codeSnippet, filePath, startLine); + } + + if (parentEdgesPromise) { + this.displayParentEdges(parentEdgesPromise, parentFiles, parentBrowser); + } + + if (syscallsPromise && syzkallsPromise && conditionsPromise) { + this.displaySyscalls(syscallsPromise, syzkallsPromise, conditionsPromise, codeMetadata, codeFilter); + } + + return codeSnippet; + } + + addLabel(container: HTMLDivElement, className: string, value: string) { + let labelNode = container.appendChild(document.createElement('div')); + labelNode.className = 'label ' + className; + labelNode.appendChild(document.createTextNode(value)); + return labelNode; + } + + createNewFileNode(filePath: string, parentNode: Element) { + let newFileFragment = Snippets.generateCodeFile(filePath); + let fileNode = newFileFragment.querySelector('.code-file') as HTMLElement; + fileNode.addEventListener('change', (e) => { + this.focusElement(e.target as Element); + this.reachability.updateViews(); + }); + parentNode.appendChild(newFileFragment); + return fileNode; + } + + selectExistingElement(existingElement: HTMLDivElement) { + const snippetCssPath = '> .code-metadata :checked'; + const fileCssPath = '> .code-snippets > .code-file-path-container :checked'; + let collapsedSnippet, collapsedFile; + do { + collapsedSnippet = existingElement.closest(`.code-body:has(${snippetCssPath})`); + collapsedFile = existingElement.closest(`.code-file:has(${fileCssPath})`); + console.log(collapsedSnippet, collapsedFile); + if (collapsedSnippet) { + (collapsedSnippet.querySelector(`& ${snippetCssPath}`) as HTMLInputElement).checked = false; + } + if (collapsedFile) { + (collapsedFile.querySelector(`& ${fileCssPath}`) as HTMLInputElement).checked = false; + } + } while(collapsedSnippet || collapsedFile); + let codeSnippet = existingElement.querySelector('& > .code-snippet > pre')!; + this.focusElement(codeSnippet); + return codeSnippet; + } + + async displayChildEdges(childEdgesPromise: Promise, fileNode: HTMLElement, codeSnippet: HTMLPreElement, filePath: string, startLine: string) { + let childEdges = await childEdgesPromise; + this.decorateChildEdges( + fileNode!.querySelector('& > .child-files')!, + codeSnippet, + childEdges, + filePath, + startLine + ); + this.reachability.updateViews(); + } + + async displayParentEdges(parentEdgesPromise: Promise, parentFiles: HTMLElement, parentBrowser: HTMLDivElement) { + let parentEdges = await parentEdgesPromise; + this.showParentEdges( + parentFiles, + parentBrowser, + parentEdges + ); + parentBrowser.dataset.loadingFinished = "true"; + this.reachability.updateViews(); + } + + async displaySyscalls( + syscallsPromise: Promise, syskallsPromise: Promise, conditionsPromise: Promise, + codeMetadata: HTMLDivElement, codeFilter: HTMLDivElement + ) { + let syscalls = await syscallsPromise; + let syscallsLabel = this.addLabel( + codeMetadata, + syscalls.length ? 'label-syscalls' : '', + `syscalls:${syscalls.length}` + ); + if (syscalls.length) { + syscallsLabel.addEventListener('click', async () => { + await this.filter.displaySyscalls(syscalls, syskallsPromise, conditionsPromise, codeFilter); + this.reachability.updateViews(); + }); + } + } + + focusElement(codeSnippet: Element) { + codeSnippet.scrollIntoView({ + behavior: "smooth", + block: "nearest", + inline: "nearest" + }); + } + + + getSurroundingSnippets(targetLine: string, codeSnippets: Element[]) { + let nextInSequence = null; + let lastInSequence = null; + let existingElement = null; + for (let elem of codeSnippets) { + let htmlNode = elem as HTMLDivElement; + if ( + htmlNode.dataset.startLine && Number(htmlNode.dataset.startLine) <= Number(targetLine) && + htmlNode.dataset.endLine && Number(htmlNode.dataset.endLine) >= Number(targetLine) + ) { + existingElement = htmlNode; + break; + } + if (htmlNode.dataset.startLine && Number(htmlNode.dataset.startLine) >= Number(targetLine)) { + nextInSequence = htmlNode; + break; + } + lastInSequence = htmlNode; + } + return { existingElement, lastInSequence, nextInSequence }; + } + + decorateChildEdges(childFiles: HTMLElement, snippet: HTMLPreElement, edges: CHILD_EDGE_LOCATIONS[], filePath: string, startLine: string) { + [...snippet.querySelectorAll('.token.function')].forEach(node => { + let functionName = node.textContent; + let matchingEdges = edges.filter(edge => edge.source_message == "call to " + functionName); + if (matchingEdges.length == 1) { + const edge = matchingEdges[0]; + node.classList.add('navigation-edge'); + node.addEventListener('click', async () => { + this.reachability.showFileLine( + childFiles!, + edge!.target_uri, + Number(edge!.target_startLine), + Number(edge!.target_function_end_line), + functionName!, + node + ); + }); + } else { + node.classList.add('navigation-missing-edge'); + node.addEventListener('click', async () => { + const range = document.createRange(); + range.setStartBefore(snippet); + range.setEndBefore(node); + const lineOffset = range.cloneContents().textContent!.split(/\n/).length - 1; + const lineNumber = Number(startLine) + lineOffset; + this.reachability.displayEdgeSelection(functionName!, filePath, lineNumber, node, childFiles!); + }); + } + }); + [...snippet.querySelectorAll('.token.class-name')].forEach(node => { + let structName = node.textContent; + node.classList.add('navigation-struct'); + node.addEventListener('click', async () => { + location.hash = `#!heap/*/${structName}`; + }); + }); + } + + showParentEdges(parentFiles: HTMLElement, parentBrowser: Element, edges: PARENT_EDGE_LOCATIONS[]) { + type parentEdgeData = {edge?: PARENT_EDGE_LOCATIONS, syscalls: string[]}; + let root = {name: '', children: []} as TreeViewItem; + let expanded = edges.length < 30; + edges.forEach(edge => { + if (!edge.source_function_names) return; + let pathParts = [...edge.source_uri.split('/'), edge.source_function_names]; + let leaf = root; + pathParts.forEach(part => { + let nextLeaf = leaf.children.find(e => e.name == part); + if (!nextLeaf) { + nextLeaf = { + expanded: expanded, + name: part.replace(/,/g, '\n'), + children: [], + data: {syscalls: []} + }; + leaf.children.push(nextLeaf); + } + leaf = nextLeaf; + leaf.data = {syscalls: [...new Set(leaf.data?.syscalls), ...edge.syscalls.split(' ')]}; + }); + leaf.data = {edge, syscalls: [...edge.syscalls.split(' ')]}; + }); + const treeview = new TreeView(root.children, parentBrowser); + treeview.on('expand', () => this.reachability.updateViews()); + treeview.on('collapse', () => this.reachability.updateViews()); + treeview.on('select', event => { + const edge = event.data.data!.edge; + if (!edge) return; + this.reachability.showFileLine( + parentFiles, + edge.source_uri, + Number(edge.source_function_start_line), + Number(edge.source_function_end_line), + event.data.name, + null + ); + }); + parentBrowser.querySelectorAll('.tree-leaf-content').forEach(node => { + let nodeDiv = node as HTMLDivElement; + let nodeJson = JSON.parse(nodeDiv.dataset.item || '{}'); + nodeDiv.dataset.filteredSyscall = nodeJson.data.syscalls.join(' '); + nodeDiv.classList.add('filtered-syscall'); + }); + } +} diff --git a/analysis/kernel/dashboard/frontend/src/views/struct/struct-alloc.html b/analysis/kernel/dashboard/frontend/src/views/struct/struct-alloc.html new file mode 100644 index 00000000..43913e49 --- /dev/null +++ b/analysis/kernel/dashboard/frontend/src/views/struct/struct-alloc.html @@ -0,0 +1,6 @@ + +
        + + \ No newline at end of file diff --git a/analysis/kernel/dashboard/frontend/src/views/struct/struct-entry.html b/analysis/kernel/dashboard/frontend/src/views/struct/struct-entry.html new file mode 100644 index 00000000..c6eff7c8 --- /dev/null +++ b/analysis/kernel/dashboard/frontend/src/views/struct/struct-entry.html @@ -0,0 +1,8 @@ + +
        \ No newline at end of file diff --git a/analysis/kernel/dashboard/frontend/src/views/struct/struct-list.html b/analysis/kernel/dashboard/frontend/src/views/struct/struct-list.html new file mode 100644 index 00000000..49f85d9e --- /dev/null +++ b/analysis/kernel/dashboard/frontend/src/views/struct/struct-list.html @@ -0,0 +1 @@ +
        \ No newline at end of file diff --git a/analysis/kernel/dashboard/frontend/src/views/struct/struct.css b/analysis/kernel/dashboard/frontend/src/views/struct/struct.css new file mode 100644 index 00000000..c71deac0 --- /dev/null +++ b/analysis/kernel/dashboard/frontend/src/views/struct/struct.css @@ -0,0 +1,170 @@ +.struct-explorer { + display: flex; + flex-direction: column; + flex: 1 0 0; + + width: min-content; + z-index: 10; + + background: #2d2d2dcc; + + .struct-explorer-top { + display: flex; + flex-direction: row; + min-width: 0; + + label { + white-space: nowrap; + width: 0; + input { + margin: 0; + } + span { + width: 100vw; + display: inline-block; + } + } + } + + .struct-root-node { + overflow: hidden; + flex: 0 1 50%; + max-height: max-content; + + box-shadow: inset white 0 0 5px; + + .struct-list { + display: grid; + grid-template-columns: repeat(4, auto); + align-items: start; + align-content: flex-start; + + padding: 0; + height: 100%; + overflow: auto; + border: 1px solid black; + box-shadow: 0 0 0.5em #999; + scrollbar-color: hsla(24, 20%, 50%,.4) #2d2d2d; + + font-size: 0.8em; + color: #eee; + + & :is(a, a:visited) { + color: inherit; + } + + .struct-entry { + display: grid; + grid-template-columns: auto 1fr; + grid-column: 1 / 5; + + column-gap: 1ex; + margin: 20px 0 0 0; + padding: 1ex; + + box-shadow: 0 0 0.5em #999; + background: hsla(24, 20%, 50%,.4); + + & input { + visibility: hidden; + position: absolute; + } + &:not(:has(:checked)) + .struct-alloc { + visibility: hidden; + height: 0; + } + } + .struct-alloc { + display: grid; + grid-column: 1 / 5; + grid-template-columns: subgrid; + + & > * { + padding: 1ex; + border-bottom: 1px dashed #ccc; + } + .struct-alloc-cache { + margin-left: 3em; + white-space: nowrap; + } + .struct-alloc-call { + text-align: end; + } + .struct-alloc-metadata { + white-space: nowrap; + } + } + } + } + & .field-explorer { + display: flex; + flex-direction: row; + flex: 1 1 50%; + max-height: max-content; + overflow: hidden; + background: hsla(24, 20%, 50%,.4); + a { + color: inherit; + } + .fields-root-node { + flex: 0 1 50%; + min-width: min-content; + max-width: max-content; + overflow-y: auto; + overflow-x: hidden; + scrollbar-color: #bbb #2d2d2d; + max-height: 100%; + font-size: 0.8em; + background: #2d2d2d88; + padding: 0; + white-space: nowrap; + box-shadow: inset white 0 0 5px; + border-radius: 5px; + & > .tree-leaf > .tree-leaf-content { + font-weight: bold; + background: black; + position: sticky; + top: 0; + z-index: 1; + } + .tree-leaf-content { + overflow-y: scroll; + scrollbar-width: none; + &::-webkit-scrollbar { + display: none; + } + } + } + .access-root-node { + flex: 1 1 100%; + &:not(:empty) { + overflow-y: scroll; + } + scrollbar-color: #bbb transparent; + .access-list { + display: grid; + grid-template-columns: auto 1fr auto; + white-space: nowrap; + font-size: 0.8em; + .access-field { + display: grid; + grid-column: 1/4; + grid-template-columns: subgrid; + + background: #2d2d2d88; + padding-top: 10px; + } + .access-calls { + display: grid; + grid-column: 1/4; + grid-template-columns: subgrid; + + background: hsla(24, 20%, 50%,.4); + .access-call-type { + margin-left: auto; + } + } + } + } + } +} \ No newline at end of file diff --git a/analysis/kernel/dashboard/frontend/src/views/struct/struct.ts b/analysis/kernel/dashboard/frontend/src/views/struct/struct.ts new file mode 100644 index 00000000..c7bb6187 --- /dev/null +++ b/analysis/kernel/dashboard/frontend/src/views/struct/struct.ts @@ -0,0 +1,63 @@ +import { View } from "../view"; +import { Heap } from "../../controllers/heap"; +import { STRUCT_RESULTS } from "../../types"; +import { interpolateHtml } from "../html"; + +import structListHtml from "./struct-list.html"; +import structEntryHtml from "./struct-entry.html"; +import structAllocHtml from "./struct-alloc.html"; + +import "./struct.css"; + +export class Struct { + private static ROOT_NODE: HTMLDivElement | null = null; + private static ROOT_NODE_CLASS_NAME: string = 'struct-root-node'; + + static async getRootNode(): Promise { + return Struct.ROOT_NODE = await View.getRootNode(Struct.ROOT_NODE, Struct.ROOT_NODE_CLASS_NAME); + } + constructor(private heap: Heap) { } + + displayStructs(parentNode: HTMLDivElement, structResults: STRUCT_RESULTS[], kmalloc: string, struct: string, offset: number) { + let structListFragment = interpolateHtml(structListHtml, new Map()); + let structList = structListFragment.querySelector('.struct-list') as HTMLDivElement; + structResults.reduce((map, result) => { + if (!map.has(result.struct_name)) { + map.set(result.struct_name, new Set); + } + if (result.kmalloc_bucket_name) { + map.get(result.struct_name)!.add(result); + } + return map; + }, new Map>).forEach((allocs, struct_name) => { + let structEntryFragment = interpolateHtml(structEntryHtml, new Map([ + [`//a[@class="struct-name"]/@href`, `#!heap/${kmalloc}/${struct_name}`], + [`//a[@class="struct-name"]/text()`, struct_name], + [`//span[@class="struct-alloc-num"]/text()`, String(allocs.size)] + ])); + let structEntry = structEntryFragment.querySelector('.struct-entry'); + let structAlloc = structEntryFragment.querySelector('.struct-alloc'); + structList.appendChild(structEntryFragment); + allocs.forEach(alloc => { + const kmalloc = `kmalloc-${ + alloc.kmalloc_cgroup_name?"cg-":"" + }${ + alloc.kmalloc_bucket_name + }${ + alloc.kmalloc_dyn?"-dyn":"" + }`; + let structAllocFragment = interpolateHtml(structAllocHtml, new Map([ + [`//a[@class="struct-alloc-cache"]/@href`, `#!heap/${kmalloc}/${struct}`], + [`//a[@class="struct-alloc-cache"]/text()`, kmalloc], + [`//div[@class="struct-alloc-call"]/text()`, `${alloc.call_value}`], + [`//a[@class="struct-alloc-link"]/@href`, `#${alloc.call_uri}:${alloc.function_start_line}:${alloc.function_end_line}`], + [`//a[@class="struct-alloc-link"]/text()`, `${alloc.call_uri}#${alloc.call_startLine}`], + [`//a[@class="struct-alloc-link"]/@title`, `${alloc.function}`], + [`//span[@class="struct-alloc-syscalls-num"]/text()`, `${alloc.syscalls_num}`] + ])); + structAlloc!.appendChild(structAllocFragment); + }); + });; + parentNode.replaceChildren(structListFragment); + } +} \ No newline at end of file diff --git a/analysis/kernel/dashboard/frontend/src/views/treeview.css b/analysis/kernel/dashboard/frontend/src/views/treeview.css new file mode 100644 index 00000000..27a252e5 --- /dev/null +++ b/analysis/kernel/dashboard/frontend/src/views/treeview.css @@ -0,0 +1,54 @@ +.tree-leaf { + position: relative; +} + +.tree-leaf .tree-child-leaves { + display: block; + margin-left: 15px; +} + +.tree-leaf .hidden { + display: none; +} + +.tree-leaf .tree-expando { + background: #ddd; + border-radius: 3px; + cursor: pointer; + float: left; + height: 10px; + line-height: 10px; + position: relative; + text-align: center; + top: 5px; + width: 10px; +} + +.tree-leaf .hidden { + visibility: hidden; +} + +.tree-leaf .tree-expando:hover { + background: #aaa; +} + +.tree-leaf .tree-leaf-text { + cursor: pointer; + float: left; + margin-left: 5px; +} + +.tree-leaf .tree-leaf-content:before, +.tree-leaf .tree-leaf-content:after { + content: " "; + display: table; +} + +.tree-leaf .tree-leaf-content:after { + clear: both; +} + +.tree-leaf-text { + text-decoration: underline; + margin: 0.25ex 0; +} \ No newline at end of file diff --git a/analysis/kernel/dashboard/frontend/src/views/view.ts b/analysis/kernel/dashboard/frontend/src/views/view.ts new file mode 100644 index 00000000..f937f383 --- /dev/null +++ b/analysis/kernel/dashboard/frontend/src/views/view.ts @@ -0,0 +1,23 @@ +export class View { + static async getRootNode(rootNode: HTMLDivElement | null, rootClassName: string): Promise { + if (!rootNode) { + rootNode = document.createElement('div'); + rootNode.className = rootClassName; + if (document.readyState != "complete") { + return new Promise(res => { + document.onreadystatechange = (e) => { + res(View.getRootNode(rootNode, rootClassName)); + }; + }); + } + } + if (rootNode.parentNode == null) { + let placeholder = document.getElementsByClassName(rootClassName)[0]; + if (!placeholder) { + throw new Error('Could not initialize view, ' + rootClassName + ' missing.'); + } + placeholder.parentNode?.replaceChild(rootNode, placeholder); + } + return rootNode; + } +} \ No newline at end of file diff --git a/analysis/kernel/dashboard/frontend/tsconfig.json b/analysis/kernel/dashboard/frontend/tsconfig.json new file mode 100644 index 00000000..3a668cf3 --- /dev/null +++ b/analysis/kernel/dashboard/frontend/tsconfig.json @@ -0,0 +1,101 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Projects */ + // "incremental": true, /* Enable incremental compilation */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2020", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */ + // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + + /* Modules */ + "module": "es2020", /* Specify what module code is generated. */ + // "rootDir": "./", /* Specify the root folder within your source files. */ + "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "resolveJsonModule": true, /* Enable importing .json files */ + // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */ + // "outDir": "./", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */ + // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */ + // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +} \ No newline at end of file diff --git a/analysis/kernel/dashboard/frontend/webpack.config.js b/analysis/kernel/dashboard/frontend/webpack.config.js new file mode 100644 index 00000000..60c704cb --- /dev/null +++ b/analysis/kernel/dashboard/frontend/webpack.config.js @@ -0,0 +1,47 @@ +module.exports = { + mode: "development", + entry: "./src/index.ts", + devtool: "source-map", + output: { + filename: "bundle.js", + }, + module: { + rules: [ + { + test: /\.tsx?$/, + use: "ts-loader", + exclude: /node_modules/, + }, + { + test: /\.css$/i, + use: ["style-loader", "css-loader"], + }, + { + test: /\.html$/i, + use: "html-loader", + }, + { + test: /\.sql$/i, + use: "raw-loader", + }, + ], + }, + resolve: { + extensions: [".tsx", ".ts", ".js"], + }, + output: { + filename: "bundle.js", + }, + devServer: { + headers: { + 'Cross-Origin-Embedder-Policy': 'require-corp', + 'Cross-Origin-Opener-Policy': 'same-origin' + }, + devMiddleware: {writeToDisk: true}, + static: { + directory: "./", + publicPath: "/", + serveIndex: true, + } + }, +}; diff --git a/analysis/kernel/dashboard/queries/all-calls.ql b/analysis/kernel/dashboard/queries/all-calls.ql new file mode 100644 index 00000000..4ae22fc8 --- /dev/null +++ b/analysis/kernel/dashboard/queries/all-calls.ql @@ -0,0 +1,114 @@ +/** + * @name all-calls + * @id callgraph-all + * @kind path-problem + * @severity warning + */ + +import cpp +import semmle.code.cpp.pointsto.CallGraph +import semmle.code.cpp.ir.dataflow.ResolveCall +import semmle.code.cpp.dataflow.new.TaintTracking + +module IndirectFlowConfiguration implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { + source.asConvertedExpr() instanceof FunctionAccess or + source.asIndirectConvertedExpr() instanceof FunctionAccess + } + + predicate isSink(DataFlow::Node sink) { + exists(ExprCall ec | + sink.asConvertedExpr() = ec.getExpr() or + sink.asIndirectConvertedExpr() = ec.getExpr() + ) + } +} + +IndirectFlow::PathNode getPathNode(Element e) { + exists(IndirectFlow::PathNode pn, DataFlow::Node n | + result = pn and + pn.getNode() = n and + n.getLocation() = e.getLocation() + ) +} + +module IndirectFlow = TaintTracking::Global; + +class ExprTargetCallEdge extends AdditionalControlFlowEdge { + ExprTargetCallEdge() { exists(Function fun | mkElement(this) = fun) } + + override ControlFlowNode getAnEdgeTarget() { + exists(ExprCall e | e.getEnclosingFunction() = mkElement(this) and result = e.getExpr()) + } +} + +cached +class ExprSourceCallEdge extends AdditionalControlFlowEdge { + cached + ExprSourceCallEdge() { exists(ExprCall expc | mkElement(this) = expc.getExpr()) } + + cached + override ControlFlowNode getAnEdgeTarget() { + exists(IndirectFlow::PathNode target | + IndirectFlow::flowPath(target, getPathNode(mkElement(this))) and + target.getNode().asConvertedExpr() = mkElement(result).(Function).getAnAccess() + or + target.getNode().asIndirectConvertedExpr() = mkElement(result).(Function).getAnAccess() + ) + } +} + + +cached predicate exprCallEdge(ExprCall a, Function b) { + a.getExpr().(TargetPointsToExpr).pointsTo() = b and + a.getExpr().(TargetPointsToExpr).confidence() >= 0.2 and + // Get the number of parameters of the function + exists(int numParams | + numParams = count(b.getParameter(_)) and + // Iterate over each parameter + forall(int i | i in [0 .. numParams - 1] | + exists(Parameter p | p = b.getParameter(i) | + // Get the type of the parameter + exists(Type paramType | paramType = p.getType() | + // Get the argument at the corresponding index + exists(Expr arg | arg = a.getArgument(i) | + // Check if the argument's type is compatible with the parameter's type + arg.getType().(PointerType).getBaseType() = paramType + or + arg.getType() = paramType + ) + ) + ) + ) + ) +} + +predicate notInteresting(Function fun) { + fun.getName().matches("__compiletime_assert_%") or + fun.getBlock().isEmpty() +} + +query predicate edges(ControlFlowNode a, ControlFlowNode b) { + // ExprCall to Function + exprCallEdge(a, b) + or + // Function to ExprCall + a = b.(ExprCall).getEnclosingFunction() and not notInteresting(a) + or + // Function to FunctionCall + a = b.(FunctionCall).getEnclosingFunction() and not notInteresting(a) + or + // FunctionCall to Function + a.(FunctionCall).getTarget() = b and not notInteresting(b) or + + // Fallback to indirect calls query in case we missed something + a.(ExprTargetCallEdge).(Function) = b.(Call).getEnclosingFunction() + or + a.(ExprSourceCallEdge).getAnEdgeTarget() = b.(ExprTargetCallEdge) or + // Maybe it's a more complex call than just function -> call + b = resolveCall(a) +} + +from ControlFlowNode nodeFrom, ControlFlowNode nodeTo +where edges(nodeFrom, nodeTo) +select nodeTo, nodeFrom, nodeTo, "callgraph-all" diff --git a/analysis/kernel/dashboard/queries/allocations.ql b/analysis/kernel/dashboard/queries/allocations.ql new file mode 100644 index 00000000..8efac623 --- /dev/null +++ b/analysis/kernel/dashboard/queries/allocations.ql @@ -0,0 +1,77 @@ +/** + * @name Find interesting objects for kernel heap exploitation + * @id cpp/kernel-interesting-objects + * @description Finds interesting objects for kernel heap exploitation + * @kind problem + * @precision low + * @tags security kernel + * @problem.severity error + */ + +import cpp + + +class FlexibleArrayMember extends Field { + FlexibleArrayMember() { + exists(Struct s | + this = s.getCanonicalMember(max(int j | s.getCanonicalMember(j) instanceof Field | j)) + ) and + this.getUnspecifiedType() instanceof ArrayType and + ( + this.getUnspecifiedType().(ArrayType).getArraySize() <= 1 or + not this.getUnspecifiedType().(ArrayType).hasArraySize() + ) + } +} + +class KmallocCall extends FunctionCall { + KmallocCall() { this.getTarget().hasName(["kmalloc", "kzalloc", "kvmalloc"]) } + + Expr getSizeArg() { result = this.getArgument(0) } + + string getFlag() { + result = + concat(Expr flag | + flag = this.getArgument(1).getAChild*() and flag.getValueText().matches("%GFP%") + | + flag.getValueText(), "|" + ) + } + + string getSize() { + if this.getSizeArg().isConstant() + then result = this.getSizeArg().getValue() + else result = "unknown" + } + + Type sizeofParam(Expr e) { + result = e.(SizeofExprOperator).getExprOperand().getFullyConverted().getType() + or + result = e.(SizeofTypeOperator).getTypeOperand() + } + + Struct getStruct() { + exists(Expr sof | + this.getSizeArg().getAChild*() = sof and + this.sizeofParam(sof) = result + ) or + result = this.getFullyConverted().getType().stripType() + } + + string isFlexible() { + this.getSize() = "unknown" and + this.getStruct().getAField() instanceof FlexibleArrayMember and + result = "true" + or + not this.getSize() = "unknown" and + not this.getStruct().getAField() instanceof FlexibleArrayMember and + result = "false" + } +} + +from KmallocCall kfc, Struct s +where + s = kfc.getStruct() and + not kfc.getSizeArg().isAffectedByMacro() +select kfc.getLocation(), kfc, s, s.getLocation(), s.getSize(), kfc.getFlag(), kfc.getSize(), + kfc.getArgument(0), kfc.isFlexible() diff --git a/analysis/kernel/dashboard/queries/controled-field-writes.ql b/analysis/kernel/dashboard/queries/controled-field-writes.ql new file mode 100644 index 00000000..fddeea94 --- /dev/null +++ b/analysis/kernel/dashboard/queries/controled-field-writes.ql @@ -0,0 +1,44 @@ +/** + * @id controlled-field-writes + * @kind path-problem + * @severity error + */ + +import cpp +import semmle.code.cpp.dataflow.new.TaintTracking + +abstract class UserControlled extends DataFlow::Node { + UserControlled() { any() } +} + +class CopyFromUser extends UserControlled { + CopyFromUser() { + exists(FunctionCall usercopy | + this.asDefiningArgument() = usercopy.getArgument(0) and + usercopy.getTarget().hasName("copy_from_user") + ) + } +} + +class SysCallArg extends UserControlled { + SysCallArg() { + exists(Function fun | + fun.getAParameter() = this.asParameter() and + fun.getName().matches("__do_sys_%") + ) + } +} + +module FieldWriteFlowConfiguration implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source instanceof UserControlled } + + predicate isSink(DataFlow::Node sink) { exists(FieldAccess fa | fa = sink.asExpr()) } +} + +module FieldWriteFlow = TaintTracking::Global; + +import FieldWriteFlow::PathGraph + +from FieldWriteFlow::PathNode source, FieldWriteFlow::PathNode sink +where FieldWriteFlow::flowPath(source, sink) +select sink, source, sink, "user-controlled to field-write" diff --git a/analysis/kernel/dashboard/queries/controlled-expression-calls.ql b/analysis/kernel/dashboard/queries/controlled-expression-calls.ql new file mode 100644 index 00000000..d8f395f8 --- /dev/null +++ b/analysis/kernel/dashboard/queries/controlled-expression-calls.ql @@ -0,0 +1,44 @@ +/** + * @id controlled-exprcall-args + * @kind path-problem + * @severity error + */ + +import cpp +import semmle.code.cpp.dataflow.new.TaintTracking + +abstract class UserControlled extends DataFlow::Node { + UserControlled() { any() } +} + +class CopyFromUser extends UserControlled { + CopyFromUser() { + exists(FunctionCall usercopy | + this.asDefiningArgument() = usercopy.getArgument(0) and + usercopy.getTarget().hasName("copy_from_user") + ) + } +} + +class SysCallArg extends UserControlled { + SysCallArg() { + exists(Function fun | + fun.getAParameter() = this.asParameter() and + fun.getName().matches("__do_sys_%") + ) + } +} + +module ExprCallFlowConfiguration implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source instanceof UserControlled } + + predicate isSink(DataFlow::Node sink) { exists(ExprCall ec | sink.asExpr() = ec.getAnArgument()) } +} + +module ExprCallFlow = TaintTracking::Global; + +import ExprCallFlow::PathGraph + +from ExprCallFlow::PathNode source, ExprCallFlow::PathNode sink +where ExprCallFlow::flowPath(source, sink) +select sink, source, sink, "user-controlled to exprcall" diff --git a/analysis/kernel/dashboard/queries/field-free.ql b/analysis/kernel/dashboard/queries/field-free.ql new file mode 100644 index 00000000..8b32df7e --- /dev/null +++ b/analysis/kernel/dashboard/queries/field-free.ql @@ -0,0 +1,24 @@ +/** + * @id field-access-free + * @kind path-problem + * @severity error + */ + +import cpp +import semmle.code.cpp.dataflow.new.TaintTracking + +module FieldFreeFlowConfiguration implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source.asExpr() instanceof FieldAccess } + + predicate isSink(DataFlow::Node sink) { + exists(FunctionCall fc | fc.getTarget().hasName("kfree") and fc.getArgument(0) = sink.asExpr()) + } +} + +module FieldFreeFlow = TaintTracking::Global; + +import FieldFreeFlow::PathGraph + +from FieldFreeFlow::PathNode source, FieldFreeFlow::PathNode sink +where FieldFreeFlow::flowPath(source, sink) +select sink, source, sink, "field-access to free" diff --git a/analysis/kernel/dashboard/queries/field-leaks.ql b/analysis/kernel/dashboard/queries/field-leaks.ql new file mode 100644 index 00000000..9efe9252 --- /dev/null +++ b/analysis/kernel/dashboard/queries/field-leaks.ql @@ -0,0 +1,50 @@ +/** + * @id field-access-leak + * @kind path-problem + * @severity error + */ + +import cpp +import semmle.code.cpp.dataflow.new.TaintTracking + +abstract class LeakNode extends DataFlow::Node { + LeakNode() { any() } +} + +class PrintkArgs extends LeakNode { + PrintkArgs() { + exists(FunctionCall fc | + fc.getAnArgument() = this.asExpr() and fc.getTarget().hasName("printk") + ) + } +} + +class CopyToUserOut extends LeakNode { + CopyToUserOut() { + exists(FunctionCall fc | + fc.getTarget().hasName("copy_to_user") and fc.getArgument(1) = this.asExpr() + ) + } +} + +class PutUser extends LeakNode { + PutUser() { + exists(FunctionCall fc | + fc.getTarget().hasName("put_user") and fc.getArgument(0) = this.asExpr() + ) + } +} + +module LeakFlowConfiguration implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source.asExpr() instanceof FieldAccess } + + predicate isSink(DataFlow::Node sink) { sink instanceof LeakNode } +} + +module LeakFlow = TaintTracking::Global; + +import LeakFlow::PathGraph + +from LeakFlow::PathNode source, LeakFlow::PathNode sink +where LeakFlow::flowPath(source, sink) +select sink, source, sink, "field-access to leak" diff --git a/analysis/kernel/dashboard/queries/functions.ql b/analysis/kernel/dashboard/queries/functions.ql new file mode 100644 index 00000000..00fba917 --- /dev/null +++ b/analysis/kernel/dashboard/queries/functions.ql @@ -0,0 +1,12 @@ +import cpp + +// Used for the dashboard to see which syzkaller repro's trigger this function +from Function fun, string file, BlockStmt block, int startLine, int endLine +where + not fun.getName().matches("%assert%") and + fun.getLocation().getFile().toString() = file and + fun.getBlock() = block and + block.getLocation().getFile().toString() = file and + block.getLocation().getStartLine() = startLine and + block.getLocation().getEndLine() = endLine +select fun.getName(), file, startLine, endLine diff --git a/analysis/kernel/dashboard/queries/kernel-configs-needed.ql b/analysis/kernel/dashboard/queries/kernel-configs-needed.ql new file mode 100644 index 00000000..595bac03 --- /dev/null +++ b/analysis/kernel/dashboard/queries/kernel-configs-needed.ql @@ -0,0 +1,22 @@ +import cpp + +class FunctionConfig extends Function { + FunctionConfig() { not this.getName().matches("__compiletime_assert_%") } + + PreprocessorBranch getAGuard() { + exists(PreprocessorEndif e, int line | + result.getEndIf() = e and + e.getFile() = this.getFile() and + result.getFile() = this.getFile() and + line = this.getLocation().getStartLine() and + result.getLocation().getStartLine() < line and + line < e.getLocation().getEndLine() + ) + } +} + +from FunctionConfig enc, string guard +where + guard = enc.getAGuard().getHead() and + guard.matches("CONFIG%") +select enc, guard diff --git a/analysis/kernel/dashboard/queries/root-leaf-nodes.ql b/analysis/kernel/dashboard/queries/root-leaf-nodes.ql new file mode 100644 index 00000000..d27e7bd3 --- /dev/null +++ b/analysis/kernel/dashboard/queries/root-leaf-nodes.ql @@ -0,0 +1,119 @@ +/** + * @name root-leaf-nodes + * @id root-leaf-nodes + * @kind path-problem + * @severity warning + */ + + import cpp + import semmle.code.cpp.pointsto.CallGraph + import semmle.code.cpp.ir.dataflow.ResolveCall + import semmle.code.cpp.dataflow.new.TaintTracking + + module IndirectFlowConfiguration implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { + source.asConvertedExpr() instanceof FunctionAccess or + source.asIndirectConvertedExpr() instanceof FunctionAccess + } + + predicate isSink(DataFlow::Node sink) { + exists(ExprCall ec | + sink.asConvertedExpr() = ec.getExpr() or + sink.asIndirectConvertedExpr() = ec.getExpr() + ) + } + } + + IndirectFlow::PathNode getPathNode(Element e) { + exists(IndirectFlow::PathNode pn, DataFlow::Node n | + result = pn and + pn.getNode() = n and + n.getLocation() = e.getLocation() + ) + } + + module IndirectFlow = TaintTracking::Global; + + class ExprTargetCallEdge extends AdditionalControlFlowEdge { + ExprTargetCallEdge() { exists(Function fun | mkElement(this) = fun) } + + override ControlFlowNode getAnEdgeTarget() { + exists(ExprCall e | e.getEnclosingFunction() = mkElement(this) and result = e.getExpr()) + } + } + + cached + class ExprSourceCallEdge extends AdditionalControlFlowEdge { + cached + ExprSourceCallEdge() { exists(ExprCall expc | mkElement(this) = expc.getExpr()) } + + cached + override ControlFlowNode getAnEdgeTarget() { + exists(IndirectFlow::PathNode target | + IndirectFlow::flowPath(target, getPathNode(mkElement(this))) and + target.getNode().asConvertedExpr() = mkElement(result).(Function).getAnAccess() + or + target.getNode().asIndirectConvertedExpr() = mkElement(result).(Function).getAnAccess() + ) + } + } + + + cached predicate exprCallEdge(ExprCall a, Function b) { + a.getExpr().(TargetPointsToExpr).pointsTo() = b and + a.getExpr().(TargetPointsToExpr).confidence() >= 0.2 and + // Get the number of parameters of the function + exists(int numParams | + numParams = count(b.getParameter(_)) and + // Iterate over each parameter + forall(int i | i in [0 .. numParams - 1] | + exists(Parameter p | p = b.getParameter(i) | + // Get the type of the parameter + exists(Type paramType | paramType = p.getType() | + // Get the argument at the corresponding index + exists(Expr arg | arg = a.getArgument(i) | + // Check if the argument's type is compatible with the parameter's type + arg.getType().(PointerType).getBaseType() = paramType + or + arg.getType() = paramType + ) + ) + ) + ) + ) + } + + predicate notInteresting(Function fun) { + //fun.isCompilerGenerated() or + fun.getName().matches("__compiletime_assert_%") or + //exists(Function overload | overload = fun.getAnOverload()) or + fun.getBlock().isEmpty() + //fun.getAnAttribute().hasName("weak") + } + + query predicate edges(ControlFlowNode a, ControlFlowNode b) { + // ExprCall to Function + exprCallEdge(a, b) + or + // Function to ExprCall + a = b.(ExprCall).getEnclosingFunction() and not notInteresting(a) + or + // Function to FunctionCall + a = b.(FunctionCall).getEnclosingFunction() and not notInteresting(a) + or + // FunctionCall to Function + a.(FunctionCall).getTarget() = b and not notInteresting(b) or + a.(ExprTargetCallEdge).(Function) = b.(Call).getEnclosingFunction() + or + a.(ExprSourceCallEdge).getAnEdgeTarget() = b.(ExprTargetCallEdge) or + a = b.(Call).getEnclosingFunction() + or + b = resolveCall(a) + } + + from ControlFlowNode nodeFrom, ControlFlowNode nodeTo + where edges+(nodeFrom, nodeTo) and + not edges(_, nodeFrom) and + not edges(nodeTo, _) + select nodeTo, nodeFrom, nodeTo, "root-leaf-nodes" + \ No newline at end of file
        " + coverage_pos = pos + len('"') + len(prefix) + if html_data[prefix_pos:coverage_pos] != prefix: + # error + continue + pos = html_data.find("