@@ -341,3 +341,77 @@ async def test_web_envelopes():
341341
342342 tiny_base64 = BaseEnvelope .detect ("YWJi" )
343343 assert isinstance (tiny_base64 , TextEnvelope )
344+
345+
346+ async def test_web_envelope_pack_value ():
347+ """
348+ Test pack_value() - encodes a value through the envelope chain without modifying internal state.
349+ """
350+ import base64
351+ import json
352+
353+ from bbot .core .helpers .web .envelopes import BaseEnvelope
354+
355+ # Text envelope (singleton, transparent)
356+ text_envelope = BaseEnvelope .detect ("original_text" )
357+ assert text_envelope .pack_value ("new_text" ) == "new_text"
358+ assert text_envelope .get_subparam () == "original_text"
359+
360+ # Hex envelope (singleton chain: hex -> text)
361+ hex_envelope = BaseEnvelope .detect ("706172616d" ) # "param" in hex
362+ packed = hex_envelope .pack_value ("modified" )
363+ assert packed == "modified" .encode ().hex ()
364+ assert hex_envelope .get_subparam () == "param"
365+
366+ # Base64 envelope (singleton chain: base64 -> text)
367+ b64_envelope = BaseEnvelope .detect ("cGFyYW0=" ) # "param" in base64
368+ packed = b64_envelope .pack_value ("modified" )
369+ assert packed == base64 .b64encode (b"modified" ).decode ()
370+ assert b64_envelope .get_subparam () == "param"
371+
372+ # Nested hex -> base64 -> text chain
373+ nested_envelope = BaseEnvelope .detect ("634746795957303d" ) # hex(base64("param"))
374+ packed = nested_envelope .pack_value ("modified" )
375+ expected = base64 .b64encode (b"modified" ).decode ().encode ().hex ()
376+ assert packed == expected
377+ assert nested_envelope .get_subparam () == "param"
378+
379+ # URL envelope (singleton chain: url -> text)
380+ url_envelope = BaseEnvelope .detect ("a%20b%20c" )
381+ packed = url_envelope .pack_value ("x y z" )
382+ assert packed == "x%20y%20z"
383+ assert url_envelope .get_subparam () == "a b c"
384+
385+ # JSON inside base64 (non-singleton: base64 -> json) - only the selected subparam is substituted in the output
386+ b64_json = BaseEnvelope .detect ("eyJwYXJhbTEiOiAidmFsMSIsICJwYXJhbTIiOiB7InBhcmFtMyI6ICJ2YWwzIn19" )
387+ b64_json .selected_subparam = ["param2" , "param3" ]
388+ packed = b64_json .pack_value ("new_val3" )
389+ decoded_json = json .loads (base64 .b64decode (packed ).decode ())
390+ assert decoded_json ["param1" ] == "val1"
391+ assert decoded_json ["param2" ]["param3" ] == "new_val3"
392+ assert b64_json .get_subparam () == "val3"
393+ assert b64_json .get_subparam (["param1" ]) == "val1"
394+
395+ # Repeated calls do not accumulate - each starts from the original state
396+ hex_envelope = BaseEnvelope .detect ("706172616d" )
397+ hex_envelope .pack_value ("first_modification" )
398+ hex_envelope .pack_value ("second_modification" )
399+ hex_envelope .pack_value ("third_modification" )
400+ assert hex_envelope .get_subparam () == "param"
401+
402+ # Multiple callers sharing the same envelope each produce correct output independently
403+ shared_envelope = BaseEnvelope .detect ("706172616d" ) # "param" in hex
404+
405+ probe_a = shared_envelope .pack_value ("param' OR 1=1--" )
406+ assert probe_a == "param' OR 1=1--" .encode ().hex ()
407+ assert shared_envelope .get_subparam () == "param"
408+
409+ probe_b = shared_envelope .pack_value ("param| echo 1234 |" )
410+ assert probe_b == "param| echo 1234 |" .encode ().hex ()
411+ assert shared_envelope .get_subparam () == "param"
412+
413+ probe_c = shared_envelope .pack_value ("../../etc/passwd" )
414+ assert probe_c == "../../etc/passwd" .encode ().hex ()
415+
416+ assert shared_envelope .get_subparam () == "param"
417+ assert shared_envelope .pack () == "706172616d"
0 commit comments