2626
2727# Local selector cache at module level
2828LOCAL_SELECTORS = {}
29+ # Whether to fall back to 4byte.directory for selectors absent from the local
30+ # cache. Opt-in only — set from --online-selectors in main() so that running
31+ # the decoder over a captured APDU trace does not silently disclose its
32+ # function selectors to a third-party host (CWE-201).
33+ ALLOW_ONLINE_SELECTOR_LOOKUP = False
2934CACHE_FILE = Path (__file__ ).parent / "function_selectors.json"
3035
3136logger = logging .getLogger (__name__ )
@@ -49,6 +54,15 @@ def init_parser() -> argparse.ArgumentParser:
4954 parser = argparse .ArgumentParser (description = "Decode APDU replay file to extract transaction details." )
5055 parser .add_argument ("--input" , "-i" , required = True , help = "Input apdu replay file." )
5156 parser .add_argument ("--verbose" , "-v" , action = 'store_true' , help = "Verbose mode" )
57+ parser .add_argument (
58+ "--online-selectors" ,
59+ action = "store_true" ,
60+ help = (
61+ "Allow unknown function selectors to be looked up online against "
62+ "4byte.directory. By default the decoder works offline and leaks "
63+ "no replay data over the network (CWE-201)."
64+ ),
65+ )
5266 return parser
5367
5468
@@ -123,7 +137,12 @@ def decode_function_selector(selector: str) -> str:
123137 logger .debug (f"Found selector { selector } in local cache" )
124138 return LOCAL_SELECTORS [selector ]
125139
126- # 2. Try online API as fallback
140+ # 2. Try online API as fallback — only when the user explicitly asked for
141+ # it. Without the opt-in we never leak the selector to 4byte.directory.
142+ if not ALLOW_ONLINE_SELECTOR_LOOKUP :
143+ logger .debug (f"Selector { selector } not in cache; online lookup disabled" )
144+ return f"Unknown (0x{ selector } )"
145+
127146 logger .debug (f"Selector { selector } not in cache, querying API..." )
128147 try :
129148 url = f"https://www.4byte.directory/api/v1/signatures/?hex_signature=0x{ selector } "
@@ -1024,11 +1043,21 @@ def parse_apdu_line(line: str) -> Optional[bytes]:
10241043# Main entry
10251044# ===============================================================================
10261045def main () -> None :
1046+ global ALLOW_ONLINE_SELECTOR_LOOKUP
1047+
10271048 parser = init_parser ()
10281049 args = parser .parse_args ()
10291050
10301051 set_logging (args .verbose )
10311052
1053+ if args .online_selectors :
1054+ ALLOW_ONLINE_SELECTOR_LOOKUP = True
1055+ logger .warning (
1056+ "Online selector lookup enabled: unknown function selectors from "
1057+ "the replay will be sent to https://www.4byte.directory/. Do not "
1058+ "use this flag on sensitive traces."
1059+ )
1060+
10321061 # Load selector cache at startup
10331062 load_selector_cache ()
10341063 logger .debug (f"Loaded { len (LOCAL_SELECTORS )} function selectors from cache" )
0 commit comments