Maki K. Habib

76 papers A* 2A 4B 3C 14Misc 1Journal 36Unranked 15
YearRankTypeTitle / Venue / Authors
2024 conf
IIAI-AAI
Fusaomi Nagata, Singo Sakata, Hirohisa Kato, Keigo Watanabe, Maki K. Habib
2024 J jnl
Artif. Life Robotics
Tatsuki Shimizu, Fusaomi Nagata, Koki Arima, Kohei Miki, Hirohisa Kato, Akimasa Otsuka, Keigo Watanabe, Maki K. Habib
2023 J jnl
Artif. Life Robotics
Koki Arima, Fusaomi Nagata, Tatsuki Shimizu, Akimasa Otsuka, Hirohisa Kato, Keigo Watanabe, Maki K. Habib
2023 J jnl
J. Ambient Intell. Humaniz. Comput.
Edison Pignaton de Freitas, Joanna Isabelle Olszewska, Joel Luís Carbonera, Sandro Rama Fiorini, Alaa M. Khamis, S. Veera Ragavan, Marcos E. Barreto, Edson Prestes, Maki K. Habib, Signe A. Redfield, Abdelghani Chibani, Paulo J. S. Gonçalves, Julita Bermejo-Alonso, Ricardo Sanz, Elisa Tosello, Alberto Olivares Alarcos, Andréa Aparecida Konzen, João Quintas, Howard Li
2022 J jnl
Artif. Life Robotics
Kohei Miki, Fusaomi Nagata, Kei Furuta, Koki Arima, Tatsuki Shimizu, Takeshi Ikeda, Hirohisa Kato, Keigo Watanabe, Maki K. Habib
2022 J jnl
Int. J. Mechatronics Autom.
Fusaomi Nagata, Kei Furuta, Kohei Miki, Maki K. Habib, Keigo Watanabe
2021 C conf
ISM
Maki K. Habib, Chukwuemeka Chimsom I
2021 J jnl
Artif. Life Robotics
Kento Nakashima, Fusaomi Nagata, Akimasa Otsuka, Keigo Watanabe, Maki K. Habib
2021 J jnl
Int. J. Mechatronics Autom.
Fusaomi Nagata, Maki K. Habib, Keigo Watanabe
2021 J jnl
Artif. Life Robotics
Kento Nakashima, Fusaomi Nagata, Hiroaki Ochi, Akimasa Otsuka, Takeshi Ikeda, Keigo Watanabe, Maki K. Habib
2021 conf
ISIE
Samuel A. Ayankoso, Maki K. Habib
2021 J jnl
Artif. Life Robotics
Kohei Miki, Fusaomi Nagata, Takeshi Ikeda, Keigo Watanabe, Maki K. Habib
2021 ed.
RobCE
Dan Zhang, Maki K. Habib, Giuseppe Carbone
2021 J jnl
Int. J. Mechatronics Autom.
Fusaomi Nagata, Maki K. Habib, Keigo Watanabe
2020 J jnl
Knowl. Eng. Rev.
Alberto Olivares Alarcos, Daniel Beßler, Alaa M. Khamis, Paulo Jorge Sequeira Gonçalves, Maki K. Habib, Julita Bermejo-Alonso, Marcos Barreto, Mohammed Diab, Jan Rosell, João Quintas, Joanna Isabelle Olszewska, Hirenkumar Nakawala, Edison Pignaton, Amelie Gyrard, Stefano Borgo, Guillem Alenyà, Michael Beetz, Howard Li
2020 conf
ICMRE
Chimsom I. Chukwuemeka, Maki K. Habib
2020 J jnl
Artif. Life Robotics
Lu Shao, Fusaomi Nagata, Hiroaki Ochi, Akimasa Otsuka, Takeshi Ikeda, Keigo Watanabe, Maki K. Habib
2019 J jnl
Knowl. Eng. Rev.
Alberto Olivares Alarcos, Daniel Beßler, Alaa M. Khamis, Paulo Jorge Sequeira Gonçalves, Maki K. Habib, Julita Bermejo-Alonso, Marcos Barreto, Mohammed Diab, Jan Rosell, João Quintas, Joanna Isabelle Olszewska, Hirenkumar Nakawala, Edison Pignaton de Freitas, Amelie Gyrard, Stefano Borgo, Guillem Alenyà, Michael Beetz, Howard Li
2019 J jnl
Artif. Life Robotics
Fusaomi Nagata, Kenta Tokuno, Kazuki Mitarai, Akimasa Otsuka, Takeshi Ikeda, Hiroaki Ochi, Keigo Watanabe, Maki K. Habib
2019 J jnl
Knowl. Eng. Rev.
Sampath Kumar Veera Ragavan, Alaa M. Khamis, Sandro Rama Fiorini, Joel Luís Carbonera, Alberto Olivares Alarcos, Maki K. Habib, Paulo Jorge Sequeira Gonçalves, Howard Li, Joanna Isabelle Olszewska
2018 J jnl
J. Robotics
Liwei Shi, Maki K. Habib, Nan Xiao, Huosheng Hu
2018 B conf
SMC
Fusaomi Nagata, Kenta Tokuno, Keigo Watanabe, Maki K. Habib
2018 conf
DMBD
Fusaomi Nagata, Kenta Tokuno, Akimasa Otsuka, Takeshi Ikeda, Hiroaki Ochi, Hisami Tamano, Hitoshi Nakamura, Keigo Watanabe, Maki K. Habib
2018 J jnl
Artif. Life Robotics
Fusaomi Nagata, Yusaku Yamane, Yudai Okada, Takamasa Kusano, Keigo Watanabe, Maki K. Habib
2018 J jnl
Artif. Life Robotics
Fusaomi Nagata, Akimasa Otsuka, Takeshi Ikeda, Hiroaki Ochi, Keigo Watanabe, Maki K. Habib
2018 J jnl
Int. J. Autom. Comput.
Fusaomi Nagata, Keigo Watanabe, Maki K. Habib
2018 C conf
IECON
Amr M. Elhennawy, Maki K. Habib
2018 C conf
IECON
Hashem Rizk, Maki K. Habib
2017 C conf
IECON
Khaled Abouelsoud, Maki K. Habib
2017 C conf
IECON
Fusaomi Nagata, Norifumi Horie, Hiroaki Ochi, Keigo Watanabe, Maki K. Habib
2017 B conf
RO-MAN
Joanna Isabelle Olszewska, Marcos E. Barreto, Julita Bermejo-Alonso, Joel Luis Carbonera, Abdelghani Chibani, Sandro Rama Fiorini, Paulo Jorge Sequeira Gonçalves, Maki K. Habib, Alaa M. Khamis, Alberto Olivares Alarcos, Edison Pignaton de Freitas, Edson Prestes, S. Veera Ragavan, Signe A. Redfield, Ricardo Sanz, Bruce Spencer, Howard Li
2017 C conf
IECON
Amr M. Elhennawy, Maki K. Habib
2017 J jnl
Artif. Life Robotics
Zeming Lu, Fusaomi Nagata, Keigo Watanabe, Maki K. Habib
2016 conf
ICIRA (1)
Fusaomi Nagata, Shingo Yoshimoto, Kazuo Kiguchi, Keigo Watanabe, Maki K. Habib
2016 C conf
IECON
Keigo Watanabe, Keisuke Kinoshita, Isaku Nagai, Maki K. Habib
2016 J jnl
Ind. Robot
Behzad Bayat, Julita Bermejo-Alonso, Joel Luis Carbonera, Tullio Facchinetti, Sandro Rama Fiorini, Paulo Jorge Sequeira Gonçalves, Vitor A. M. Jorge, Maki K. Habib, Alaa M. Khamis, Kamilo Melo, Bao Nguyen, Joanna Isabelle Olszewska, Liam Paull, Edson Prestes, S. Veera Ragavan, Sajad Saeedi G., Ricardo Sanz, Mae L. Seto, Bruce Spencer, Amirkhosro Vosughi, Howard Li
2016 J jnl
Artif. Life Robotics
Fusaomi Nagata, Kazuhiro Takeshita, Shingo Yoshimoto, Akira Yoshinaga, Shingo Kurita, Akimasa Otsuka, Keigo Watanabe, Maki K. Habib
2015 C conf
IECON
Akimasa Otsuka, Fusaomi Nagata, Maki K. Habib, Keigo Watanabe
2015 J jnl
J. Robotics
Liwei Shi, Maki K. Habib, Nan Xiao, Huosheng Hu
2015 J jnl
Int. J. Mechatronics Autom.
Fusaomi Nagata, Akimasa Otsuka, Keigo Watanabe, Maki K. Habib
2015 conf
RiTA
Fusaomi Nagata, Shohei Hayashi, Shingo Yoshimoto, Akimasa Otsuka, Keigo Watanabe, Maki K. Habib
2015 C conf
IECON
Fusaomi Nagata, Shohei Hayashi, Tomoya Nagatomi, Akimasa Otsuka, Keigo Watanabe, Maki K. Habib
2015 J jnl
Artif. Life Robotics
Fusaomi Nagata, Maki K. Habib, Akimasa Otsuka, Shohei Hayashi, Tomoya Nagatomi, Keigo Watanabe
2013 J jnl
Artif. Life Robotics
Fusaomi Nagata, Naoki Kitahara, Akimasa Otsuka, Kaoru Sakakibara, Keigo Watanabe, Maki K. Habib
2013 J jnl
Robotics Auton. Syst.
Tamás Haidegger, Marcos E. Barreto, Paulo J. S. Gonçalves, Maki K. Habib, Sampath Kumar Veera Ragavan, Howard Li, Alberto Vaccarella, Roberta Perrone, Edson Prestes
2013 C conf
IECON
Akimasa Otsuka, Fusaomi Nagata, Maki K. Habib, Keigo Watanabe
2013 conf
ISIE
Fusaomi Nagata, Akimasa Otsuka, Keigo Watanabe, Kaoru Sakakibara, Maki K. Habib
2013 conf
ROSE
Tamás Haidegger, Marcos E. Barreto, Paulo J. S. Gonçalves, Maki K. Habib, S. Veera Ragavan, Howard Li, Alberto Vaccarella, Roberta Perrone, Edson Prestes
2013 J jnl
Robotics Auton. Syst.
Edson Prestes, Joel Luis Carbonera, Sandro Rama Fiorini, Vitor A. M. Jorge, Mara Abel, Raj Madhavan, Angela Locoro, Paulo J. S. Gonçalves, Marcos E. Barreto, Maki K. Habib, Abdelghani Chibani, Sébastien Gérard, Yacine Amirat, Craig Schlenoff
2012 J jnl
Artif. Life Robotics
Maki K. Habib, Keigo Watanabe, Fusaomi Nagata
2012 conf
ISIE
Fusaomi Nagata, Sho Yoshitake, Akimasa Otsuka, Maki K. Habib, Keigo Watanabe
2012 conf
RiTA
Fusaomi Nagata, Akimasa Otsuka, Sakakibara Kaoru, Keigo Watanabe, Maki K. Habib
2012 Misc conf
UbiComp
Tamás Haidegger, Marcos E. Barreto, Paulo J. S. Gonçalves, Maki K. Habib, S. Veera Ragavan, Craig Schlenoff, Alberto Vaccarella, Edson Prestes
2012 A conf
IROS
Liam Paull, Gaëtan Séverac, Guilherme V. Raffo, Julian Mauricio Angel, Harold Boley, Phillip J. Durst, Wendell Gray, Maki K. Habib, Bao Nguyen, S. Veera Ragavan, Sajad Saeedi G., Ricardo Sanz, Mae L. Seto, Aleksandar Stefanovski, Michael Trentini, Howard Li
2011 J jnl
Int. J. Robotics Autom.
Fusaomi Nagata, Takanori Mizobuchi, Keigo Watanabe, Maki K. Habib, Tetsuo Hase, Zenku Haga, Masaaki Omoto
2010 J jnl
Artif. Life Robotics
Fusaomi Nagata, Takanori Mizobuchi, Tetsuo Hase, Zenku Haga, Keigo Watanabe, Maki K. Habib
2010 A* conf
ICRA
Fusaomi Nagata, Takanori Mizobuchi, Shintaro Tani, Tetsuo Hase, Zenku Haga, Keigo Watanabe, Maki K. Habib, Kazuo Kiguchi
2010 J jnl
Artif. Life Robotics
Fusaomi Nagata, Takanori Mizobuchi, Shintaro Tani, Tetsuo Hase, Zenku Haga, Keigo Watanabe, Maki K. Habib
2009 conf
CIRA
Maki K. Habib
2008 J jnl
Artif. Life Robotics
Guang Lei Liu, Maki K. Habib, Keigo Watanabe, Kiyotaka Izumi
2007 J jnl
J. Adv. Comput. Intell. Intell. Informatics
Guang Lei Liu, Maki K. Habib, Keigo Watanabe, Kiyotaka Izumi
2007 J jnl
Adv. Robotics
Chee Pin Tan, Maki K. Habib
2006 C conf
ICARCV
Chee Pin Tan, Maki K. Habib
2004 conf
RAM
Chee Pin Tan, Maki K. Habib
2004 conf
RAM
Maki K. Habib, J. I. Inayat-Hussain
2004 C conf
CCA
Chee Pin Tan, Maki K. Habib
2003 conf
CIRA
Maki K. Habib, Jawaid I. Inayat-Hussain
1995 conf
IROS (1)
Jean-Daniel Nicoud, Maki K. Habib
1992 A conf
IROS
Maki K. Habib, Hajime Asama, Yoshiki Ishida, Akihiro Matsumoto, Isao Endo
1991 A conf
IROS
Maki K. Habib, Hajime Asama
1991 A* conf
ICRA
Hajime Asama, Maki K. Habib, Isao Endo, Koichi Ozaki, Akihiro Matsumoto, Yoshiki Ishida
1991 J jnl
Robotics Auton. Syst.
Shooji Suzuki, Maki K. Habib, Jun'ichi Iijima, Shin'ichi Yuta
1989 C conf
IAS
Shooji Suzuki, Maki K. Habib, Jun'ichi Iijima, Shin'ichi Yuta
1989 C conf
IAS
Maki K. Habib, Shin'ichi Yuta
1989 B conf
SMC
Maki K. Habib, Shin'ichi Yuta
1988 A conf
IROS
Maki K. Habib, Shin'ichi Yuta
redb/extractors/js_extractors/js_patterns.py
← Index redb/extractors/js_extractors/js_patterns.py python
"""Canonical, compiled JavaScript regex patterns shared across JS extractors.

All suspicious-API patterns and the few feature-only patterns live here so each
expression is compiled exactly once per Python process and so any pattern that
was previously duplicated across `js_features.py` and `js_suspicious_apis.py`
now resolves to a single shared compiled object.

JavaScript is case-sensitive at runtime, but every suspicious-API pattern matches
either a literal-case identifier (`\\beval\\s*\\(`, `String\\.fromCharCode`, etc.)
or a string-quoted token (`"powershell"`). Compiling them with `re.IGNORECASE`
matches the historical behaviour of `JSSuspiciousAPIsExtractor` and is safe for
the patterns that historically came from `JSFeaturesExtractor` — those literals
are spelled in real-world JS exactly as written.

`scan_source()` is the entry point used by extractors: it walks the source once
per pattern using the pre-compiled regexes and returns a flat
`{name: {"count": N, "lines": [unique_line_numbers_sorted]}}` dict. Both
`JSFeaturesExtractor` and `JSSuspiciousAPIsExtractor` consume the same dict so
the per-pattern × per-line loops they used to run independently collapse to a
single shared scan.
"""

import bisect
import re
from typing import Dict, Iterable, List, Mapping

_FLAGS = re.IGNORECASE

# Canonical compiled patterns, keyed by their human-readable name. The name is
# also the value emitted into `redb_js_suspicious_apis.api_name`.
PATTERNS = {
    # ---- code execution ----
    "eval": re.compile(r"\beval\s*\(", _FLAGS),
    "Function constructor": re.compile(r"\bnew\s+Function\s*\(", _FLAGS),
    "execScript": re.compile(r"\bexecScript\s*\(", _FLAGS),
    "document.write": re.compile(r"\bdocument\.write(?:ln)?\s*\(", _FLAGS),
    "innerHTML assignment": re.compile(r"\.innerHTML\s*=", _FLAGS),
    "outerHTML assignment": re.compile(r"\.outerHTML\s*=", _FLAGS),
    "insertAdjacentHTML": re.compile(r"\.insertAdjacentHTML\s*\(", _FLAGS),
    # ---- network ----
    "XMLHttpRequest": re.compile(r"\bnew\s+XMLHttpRequest\b", _FLAGS),
    "fetch": re.compile(r"\bfetch\s*\(", _FLAGS),
    "WebSocket": re.compile(r"\bnew\s+WebSocket\s*\(", _FLAGS),
    "navigator.sendBeacon": re.compile(r"\bnavigator\.sendBeacon\s*\(", _FLAGS),
    "ActiveXObject XMLHTTP": re.compile(
        r"ActiveXObject\s*\(\s*[\"\'](?:MSXML2\.XMLHTTP|Microsoft\.XMLHTTP)", _FLAGS
    ),
    "require network module": re.compile(
        r"require\s*\(\s*[\"\'](?:http|https|net|dgram)[\"\']", _FLAGS
    ),
    "axios": re.compile(r"\baxios\b", _FLAGS),
    # ---- filesystem ----
    "require fs": re.compile(r"require\s*\(\s*[\"\']fs[\"\']", _FLAGS),
    "require path": re.compile(r"require\s*\(\s*[\"\']path[\"\']", _FLAGS),
    "FileSystemObject": re.compile(r"Scripting\.FileSystemObject", _FLAGS),
    "ADODB.Stream": re.compile(r"ADODB\.Stream", _FLAGS),
    "Shell.Application": re.compile(r"Shell\.Application", _FLAGS),
    "WScript.CreateObject": re.compile(r"WScript\.CreateObject", _FLAGS),
    # ---- process ----
    "require child_process": re.compile(r"require\s*\(\s*[\"\']child_process[\"\']", _FLAGS),
    "child_process exec": re.compile(r"child_process\.(?:exec|spawn|execFile|fork)\s*\(", _FLAGS),
    "WScript.Shell": re.compile(r"WScript\.Shell", _FLAGS),
    "WScript.Shell.Run": re.compile(r"\.Run\s*\(", _FLAGS),
    "WScript.Shell.Exec": re.compile(r"\.Exec\s*\(", _FLAGS),
    "ShellExecute": re.compile(r"\bShellExecute\b", _FLAGS),
    "PowerShell reference": re.compile(r"[\"\']powershell[\"\']", _FLAGS),
    "cmd.exe reference": re.compile(r"[\"\']cmd\.exe[\"\']", _FLAGS),
    "require os": re.compile(r"require\s*\(\s*[\"\']os[\"\']", _FLAGS),
    # ---- registry ----
    "RegRead": re.compile(r"\.RegRead\s*\(", _FLAGS),
    "RegWrite": re.compile(r"\.RegWrite\s*\(", _FLAGS),
    "RegDelete": re.compile(r"\.RegDelete\s*\(", _FLAGS),
    "StdRegProv": re.compile(r"StdRegProv", _FLAGS),
    # ---- crypto / encoding ----
    "atob": re.compile(r"\batob\s*\(", _FLAGS),
    "btoa": re.compile(r"\bbtoa\s*\(", _FLAGS),
    "String.fromCharCode": re.compile(r"String\.fromCharCode\s*\(", _FLAGS),
    "unescape": re.compile(r"\bunescape\s*\(", _FLAGS),
    "decodeURIComponent": re.compile(r"\bdecodeURIComponent\s*\(", _FLAGS),
    "Buffer.from": re.compile(r"Buffer\.from\s*\(", _FLAGS),
    "crypto module": re.compile(r"crypto\.create(?:Cipher|Decipher|Hash|Hmac)", _FLAGS),
    # ---- DOM manipulation ----
    "document.forms": re.compile(r"document\.forms", _FLAGS),
    "document.cookie": re.compile(r"document\.cookie", _FLAGS),
    "querySelector sensitive input": re.compile(
        r"document\.querySelector\s*\([^)]*(?:password|credit|card|cvv|ssn)", _FLAGS
    ),
    "submit event listener": re.compile(r"addEventListener\s*\(\s*[\"\']submit", _FLAGS),
    "createElement script/iframe": re.compile(
        r"\.createElement\s*\(\s*[\"\'](?:script|iframe)", _FLAGS
    ),
    "dynamic script src": re.compile(r"\.src\s*=\s*[\"\'](?:https?://|//)", _FLAGS),
}

# Pattern name -> category (one of code_execution / network / filesystem /
# process / registry / crypto_encoding / dom_manipulation).
CATEGORIES = {
    "eval": "code_execution",
    "Function constructor": "code_execution",
    "execScript": "code_execution",
    "document.write": "code_execution",
    "innerHTML assignment": "code_execution",
    "outerHTML assignment": "code_execution",
    "insertAdjacentHTML": "code_execution",
    "XMLHttpRequest": "network",
    "fetch": "network",
    "WebSocket": "network",
    "navigator.sendBeacon": "network",
    "ActiveXObject XMLHTTP": "network",
    "require network module": "network",
    "axios": "network",
    "require fs": "filesystem",
    "require path": "filesystem",
    "FileSystemObject": "filesystem",
    "ADODB.Stream": "filesystem",
    "Shell.Application": "filesystem",
    "WScript.CreateObject": "filesystem",
    "require child_process": "process",
    "child_process exec": "process",
    "WScript.Shell": "process",
    "WScript.Shell.Run": "process",
    "WScript.Shell.Exec": "process",
    "ShellExecute": "process",
    "PowerShell reference": "process",
    "cmd.exe reference": "process",
    "require os": "process",
    "RegRead": "registry",
    "RegWrite": "registry",
    "RegDelete": "registry",
    "StdRegProv": "registry",
    "atob": "crypto_encoding",
    "btoa": "crypto_encoding",
    "String.fromCharCode": "crypto_encoding",
    "unescape": "crypto_encoding",
    "decodeURIComponent": "crypto_encoding",
    "Buffer.from": "crypto_encoding",
    "crypto module": "crypto_encoding",
    "document.forms": "dom_manipulation",
    "document.cookie": "dom_manipulation",
    "querySelector sensitive input": "dom_manipulation",
    "submit event listener": "dom_manipulation",
    "createElement script/iframe": "dom_manipulation",
    "dynamic script src": "dom_manipulation",
}

# Patterns consumed only by JSFeaturesExtractor (no category, never surfaced as
# a suspicious-API row). Kept here so every JS regex is compiled in one place.
FEATURE_PATTERNS = {
    "hex_escape": re.compile(r"\\x[0-9a-fA-F]{2}"),
    "unicode_escape": re.compile(r"\\u[0-9a-fA-F]{4}"),
    "base64_string": re.compile(r"[A-Za-z0-9+/]{40,}={0,2}"),
    # decodeURI matches BOTH decodeURI and decodeURIComponent. The latter is also
    # a suspicious-API pattern in PATTERNS; this broader form is what the
    # `decodeuri_count` feature column has historically counted.
    "decodeURI": re.compile(r"\b(?:decodeURI|decodeURIComponent)\s*\(", _FLAGS),
    "settimeout_setinterval": re.compile(r"\b(?:setTimeout|setInterval)\s*\(", _FLAGS),
    "function_decl": re.compile(r"\bfunction\s+\w+\s*\(|\bfunction\s*\("),
    "var_decl": re.compile(r"\b(?:var|let|const)\s+"),
    "string_concat": re.compile(r"[\"\'][\s]*\+[\s]*[\"\']"),
    "comment": re.compile(r"//.*?$|/\*[\s\S]*?\*/", re.MULTILINE),
    "long_string": re.compile(r"[\"\']([^\"\']{256,})[\"\']"),
    "array_function_call": re.compile(r"\[(?:0x[0-9a-f]+|[\d]+)\]\s*\(", _FLAGS),
}

# Patterns consumed only by JSStringsExtractor for encoded-string discovery.
# Scoped to *hidden* strings only — patterns whose decoded form is not visible
# to a substring search over the raw text. Plain long literals are not
# extracted here because they're already preserved in code_text_content and
# scraped by the IOC pipeline over text_raw / text_normalized.
#
# Distinct from FEATURE_PATTERNS even where the names rhyme:
#   FEATURE_PATTERNS["hex_escape"] / ["unicode_escape"]   -> single escape
#   STRING_PATTERNS["hex_escape_seq"] / ["unicode_escape_seq"] -> 4+ / 3+ in a row
#   FEATURE_PATTERNS["base64_string"]                     -> bare base64 token
#   STRING_PATTERNS["base64_quoted"]                      -> base64 inside JS quotes
# These do not share match objects with the suspicious-API or feature scans, so
# they are not folded into JSContext.scan; the strings extractor walks them
# itself (one finditer per pattern, with shared line-offset bisect in #4b).
STRING_PATTERNS = {
    "hex_escape_seq": re.compile(r"(?:\\x[0-9a-fA-F]{2}){4,}"),
    "unicode_escape_seq": re.compile(r"(?:\\u[0-9a-fA-F]{4}){3,}"),
    "charcode_call": re.compile(r"String\.fromCharCode\s*\(\s*([\d,\s]+)\s*\)"),
    "base64_quoted": re.compile(r"[\"\']([A-Za-z0-9+/]{40,}={0,2})[\"\']"),
    "concat_chain": re.compile(r"(?:[\"\'][^\"\']+[\"\']\s*\+\s*){3,}[\"\'][^\"\']+[\"\']"),
}


def line_offsets(source: str) -> List[int]:
    """Sorted list of byte offsets for every newline in `source`, plus a final
    sentinel of len(source). Used to translate match offsets into 1-indexed
    line numbers via bisect.
    """
    offsets = [-1]  # so that bisect_right of offset 0 returns line 1
    push = offsets.append
    idx = source.find("\n")
    while idx != -1:
        push(idx)
        idx = source.find("\n", idx + 1)
    return offsets


def _scan_one(
    pattern: "re.Pattern[str]", source: str, offsets: List[int]
) -> Dict[str, object]:
    """Run a single compiled pattern over `source` and return count + unique lines."""
    count = 0
    seen_lines: "set[int]" = set()
    for m in pattern.finditer(source):
        count += 1
        seen_lines.add(bisect.bisect_right(offsets, m.start()))
    if not count:
        return None  # type: ignore[return-value]
    return {"count": count, "lines": sorted(seen_lines)}


def scan_source(
    source: str,
    patterns: Iterable[Mapping[str, "re.Pattern[str]"]] = (PATTERNS, FEATURE_PATTERNS),
) -> Dict[str, Dict[str, object]]:
    """Scan `source` against every compiled pattern in `patterns`.

    Returns a dict keyed by pattern name. Each entry has:
        "count": total number of matches in the source
        "lines": sorted list of unique 1-indexed line numbers where the pattern
                 matched (deduplicated — multiple matches on the same line
                 collapse to one entry, preserving the historical
                 line-set semantics of JSSuspiciousAPIsExtractor)
    Patterns with zero matches are absent from the dict; callers should default
    to {"count": 0, "lines": []}.
    """
    if not source:
        return {}
    offsets = line_offsets(source)
    results: Dict[str, Dict[str, object]] = {}
    for table in patterns:
        for name, pat in table.items():
            entry = _scan_one(pat, source, offsets)
            if entry is not None:
                results[name] = entry
    return results