From dda3d9ac96fcae47278a7f9b4214617bb1007256 Mon Sep 17 00:00:00 2001 From: abisheakp197 Date: Wed, 30 Sep 2026 17:21:20 +0530 Subject: [PATCH 1/4] feat: Add NTI Security Wrapper for post-quantum agent governance --- .../agent_framework/security/nti_security.py | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 python/packages/core/agent_framework/security/nti_security.py diff --git a/python/packages/core/agent_framework/security/nti_security.py b/python/packages/core/agent_framework/security/nti_security.py new file mode 100644 index 00000000000..8d7cfe703b4 --- /dev/null +++ b/python/packages/core/agent_framework/security/nti_security.py @@ -0,0 +1,37 @@ +import json +import uuid +from ube_foundation import TrustEngine, PqcKeyPair + +class NTISecurityWrapper: + """ + NTI Security Wrapper for Microsoft Agent Framework. + Verifies message exchanges and tool calls with post-quantum signatures. + """ + def __init__(self, agent_id: str): + self.agent_id = agent_id + self.engine = TrustEngine() + self.pqc_key = PqcKeyPair.generate() + + def grant_capability(self, capability: str): + self.engine.grant(self.agent_id, capability) + + def verify_message(self, capability: str, message: dict) -> bool: + req = { + "id": f"req-{uuid.uuid4()}", + "actor": self.agent_id, + "capability": capability, + "action": "message_exchange", + "input": message, + "signature": None, + "pqc_signature": None, + "public_key": None, + "pqc_public_key": None, + "token": None, + "identity_claim": None + } + req_bytes = json.dumps(req, sort_keys=True).encode('utf-8') + req["pqc_signature"] = self.pqc_key.sign(req_bytes) + req["pqc_public_key"] = self.pqc_key.public_key_hex() + + decision = json.loads(self.engine.evaluate(json.dumps(req))) + return decision.get("decision") == "Allow" From 6f5e05342e5e94d4b4e6328ef647d6e93b0fead0 Mon Sep 17 00:00:00 2001 From: abisheakp197 Date: Wed, 30 Sep 2026 17:48:23 +0530 Subject: [PATCH 2/4] Delete python/packages/core/agent_framework/security/nti_security.py --- .../agent_framework/security/nti_security.py | 37 ------------------- 1 file changed, 37 deletions(-) delete mode 100644 python/packages/core/agent_framework/security/nti_security.py diff --git a/python/packages/core/agent_framework/security/nti_security.py b/python/packages/core/agent_framework/security/nti_security.py deleted file mode 100644 index 8d7cfe703b4..00000000000 --- a/python/packages/core/agent_framework/security/nti_security.py +++ /dev/null @@ -1,37 +0,0 @@ -import json -import uuid -from ube_foundation import TrustEngine, PqcKeyPair - -class NTISecurityWrapper: - """ - NTI Security Wrapper for Microsoft Agent Framework. - Verifies message exchanges and tool calls with post-quantum signatures. - """ - def __init__(self, agent_id: str): - self.agent_id = agent_id - self.engine = TrustEngine() - self.pqc_key = PqcKeyPair.generate() - - def grant_capability(self, capability: str): - self.engine.grant(self.agent_id, capability) - - def verify_message(self, capability: str, message: dict) -> bool: - req = { - "id": f"req-{uuid.uuid4()}", - "actor": self.agent_id, - "capability": capability, - "action": "message_exchange", - "input": message, - "signature": None, - "pqc_signature": None, - "public_key": None, - "pqc_public_key": None, - "token": None, - "identity_claim": None - } - req_bytes = json.dumps(req, sort_keys=True).encode('utf-8') - req["pqc_signature"] = self.pqc_key.sign(req_bytes) - req["pqc_public_key"] = self.pqc_key.public_key_hex() - - decision = json.loads(self.engine.evaluate(json.dumps(req))) - return decision.get("decision") == "Allow" From 987bf2a432457263a4fb1e213bba6c404bf35fa3 Mon Sep 17 00:00:00 2001 From: abisheakp197 Date: Wed, 30 Sep 2026 17:50:20 +0530 Subject: [PATCH 3/4] feat: Add NTI Security Wrapper for post-quantum agent governance --- .../core/agent_framework/nti_security.py | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 python/packages/core/agent_framework/nti_security.py diff --git a/python/packages/core/agent_framework/nti_security.py b/python/packages/core/agent_framework/nti_security.py new file mode 100644 index 00000000000..958954221e2 --- /dev/null +++ b/python/packages/core/agent_framework/nti_security.py @@ -0,0 +1,49 @@ +# Copyright (c) Microsoft. All rights reserved. + +import json +import uuid +from typing import Any + +from agent_framework._experimental import experimental +from ube_foundation import TrustEngine, PqcKeyPair + +@experimental +class NTISecurityWrapper: + """ + NTI Security Wrapper for Microsoft Agent Framework. + Verifies message exchanges and tool calls with post-quantum signatures. + """ + def __init__(self, agent_id: str) -> None: + self.agent_id = agent_id + self.engine = TrustEngine() + self.pqc_key = PqcKeyPair.generate() + + def grant_capability(self, capability: str) -> None: + self.engine.grant(self.agent_id, capability) + + def verify_message( + self, + capability: str, + message: dict[str, Any], + sender_public_key: str, + sender_signature: str + ) -> bool: + """ + Verifies an incoming message from a remote agent using their public key and signature. + """ + req = { + "id": f"req-{uuid.uuid4()}", + "actor": self.agent_id, + "capability": capability, + "action": "message_exchange", + "input": message, + "signature": None, + "pqc_signature": sender_signature, + "public_key": None, + "pqc_public_key": sender_public_key, + "token": None, + "identity_claim": None + } + + decision = json.loads(self.engine.evaluate(json.dumps(req))) + return decision.get("decision") == "Allow" From a230b665ff88f4c5b123c6bd69ff6955d0cf03dc Mon Sep 17 00:00:00 2001 From: abisheakp197 Date: Wed, 30 Sep 2026 18:06:16 +0530 Subject: [PATCH 4/4] test: add unit tests for NTISecurityWrapper Add unit tests for NTISecurityWrapper methods including initialization and message verification. --- .../packages/core/tests/test_nti_security.py | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 python/packages/core/tests/test_nti_security.py diff --git a/python/packages/core/tests/test_nti_security.py b/python/packages/core/tests/test_nti_security.py new file mode 100644 index 00000000000..47b5ca6ea6d --- /dev/null +++ b/python/packages/core/tests/test_nti_security.py @@ -0,0 +1,55 @@ +# Copyright (c) Microsoft. All rights reserved. + +from unittest.mock import MagicMock, patch +import pytest +from agent_framework.nti_security import NTISecurityWrapper + +@patch("agent_framework.nti_security.TrustEngine") +@patch("agent_framework.nti_security.PqcKeyPair") +def test_nti_security_wrapper_init(mock_pqc, mock_trust): + wrapper = NTISecurityWrapper(agent_id="test_agent") + assert wrapper.agent_id == "test_agent" + mock_trust.assert_called_once() + mock_pqc.generate.assert_called_once() + +@patch("agent_framework.nti_security.TrustEngine") +@patch("agent_framework.nti_security.PqcKeyPair") +def test_verify_message_allowed(mock_pqc, mock_trust): + # Setup mocks + mock_engine_instance = mock_trust.return_value + mock_engine_instance.evaluate.return_value = '{"decision": "Allow"}' + + wrapper = NTISecurityWrapper(agent_id="test_agent") + + result = wrapper.verify_message( + capability="test_cap", + message={"data": "test"}, + sender_public_key="pub_key", + sender_signature="sig" + ) + + assert result is True + mock_engine_instance.evaluate.assert_called_once() + + # Verify the payload sent to the Rust engine contains the sender's info + call_args = mock_engine_instance.evaluate.call_args[0][0] + assert "test_cap" in call_args + assert "pub_key" in call_args + assert "sig" in call_args + +@patch("agent_framework.nti_security.TrustEngine") +@patch("agent_framework.nti_security.PqcKeyPair") +def test_verify_message_denied(mock_pqc, mock_trust): + mock_engine_instance = mock_trust.return_value + mock_engine_instance.evaluate.return_value = '{"decision": "Deny"}' + + wrapper = NTISecurityWrapper(agent_id="test_agent") + + result = wrapper.verify_message( + capability="test_cap", + message={"data": "test"}, + sender_public_key="pub_key", + sender_signature="sig" + ) + + assert result is False