diff --git a/python/samples/02-agents/security/README.md b/python/samples/02-agents/security/README.md index 982cbe997a..7e8e08f61a 100644 --- a/python/samples/02-agents/security/README.md +++ b/python/samples/02-agents/security/README.md @@ -1,10 +1,8 @@ -# FIDES security samples +# Security samples -This folder contains two runnable FIDES samples that use -`agent_framework.foundry.FoundryChatClient`. Keep this README as the quick -entry point for choosing and running a sample; use -[FIDES_DEVELOPER_GUIDE.md](FIDES_DEVELOPER_GUIDE.md) for the architecture, -security model, middleware behavior, and API reference. +This folder contains runnable security samples. For FIDES architecture, security +model, middleware behavior, and API reference see +[FIDES_DEVELOPER_GUIDE.md](FIDES_DEVELOPER_GUIDE.md). ## What each sample demonstrates @@ -12,6 +10,7 @@ security model, middleware behavior, and API reference. |--------|-------|--------------| | `email_security_example.py` | Prompt injection defense | `SecureAgentConfig`, Foundry-backed email handling, `quarantined_llm`, and approval on policy violations | | `repo_confidentiality_example.py` | Data exfiltration prevention | Confidentiality labels, Foundry-backed repository access, `max_allowed_confidentiality`, and approval before leaking private data | +| `hdp_provenance.py` | Delegation provenance | Cryptographic audit trail from authorising human to every agent action, verifiable offline with a single public key | ## Prerequisites @@ -72,6 +71,44 @@ What to look for: - Reading private content taints the context as private - Posting private data to a public destination triggers an approval request +### `hdp_provenance.py` + +This sample attaches HDP (Human Delegation Provenance) to an agent-framework `Agent`. +Every chat call is recorded as a signed Ed25519 hop; the full chain is verifiable +offline with a single public key. + +Prerequisites: + +```bash +pip install "agent-framework-foundry" "hdp-agent-framework" "azure-identity" python-dotenv +``` + +Generate a signing key once and export it: + +```bash +python -c " +from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey +import base64; k = Ed25519PrivateKey.generate() +print('HDP_SIGNING_KEY=' + base64.urlsafe_b64encode(k.private_bytes_raw()).decode()) +" +export HDP_SIGNING_KEY= +``` + +Run it with: + +```bash +uv run samples/02-agents/security/hdp_provenance.py +``` + +What to look for: + +- `HDP chain valid: True` printed after the agent run +- `Hops recorded: N` showing how many agent turns were captured + +References: [helixar.ai/about/labs/hdp/](https://helixar.ai/about/labs/hdp/) · [arXiv:2604.04522](https://arxiv.org/abs/2604.04522) · [PyPI](https://pypi.org/project/hdp-agent-framework/) + +--- + ## Where to find the details For the full FIDES design and API details, see diff --git a/python/samples/02-agents/security/hdp_provenance.py b/python/samples/02-agents/security/hdp_provenance.py new file mode 100644 index 0000000000..7d1e0fcce4 --- /dev/null +++ b/python/samples/02-agents/security/hdp_provenance.py @@ -0,0 +1,124 @@ +# Copyright (c) Microsoft. All rights reserved. +# SPDX-License-Identifier: MIT +# +# This sample requires: +# pip install "agent-framework-foundry" "hdp-agent-framework" "azure-identity" python-dotenv +# +# Generate an Ed25519 signing key once: +# python -c " +# from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey +# import base64; k = Ed25519PrivateKey.generate() +# print('HDP_SIGNING_KEY=' + base64.urlsafe_b64encode(k.private_bytes_raw()).decode()) +# " +# export HDP_SIGNING_KEY= +# +# Reference: https://helixar.ai/about/labs/hdp/ +# Package: https://pypi.org/project/hdp-agent-framework/ + +""" +HDP Delegation Provenance - agent-framework integration + +Attaches a cryptographic audit trail to an agent-framework Agent. +Every chat call is recorded as a signed delegation hop verifiable +offline with a single public key. +""" + +import asyncio +import base64 +import os +import sys + +from agent_framework import Agent +from agent_framework.foundry import FoundryChatClient +from azure.identity import AzureCliCredential +from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey +from dotenv import load_dotenv + +# HDP middleware - pip install hdp-agent-framework +# Docs: https://helixar.ai/about/labs/hdp/ +from hdp_agent_framework import HdpMiddleware, HdpPrincipal, ScopePolicy, verify_chain + +load_dotenv() + + +def _load_signing_key() -> Ed25519PrivateKey: + raw_b64 = os.getenv("HDP_SIGNING_KEY") + if not raw_b64: + print("ERROR: HDP_SIGNING_KEY not set. See comment at top of file.") + sys.exit(1) + padding = (4 - len(raw_b64) % 4) % 4 + return Ed25519PrivateKey.from_private_bytes( + base64.urlsafe_b64decode(raw_b64 + "=" * padding) + ) + + +async def main() -> None: + private_key = _load_signing_key() + + # 1. Declare what the human is authorising + middleware = HdpMiddleware( + signing_key=private_key.private_bytes_raw(), + session_id="analysis-session-2026", + principal=HdpPrincipal(id="analyst@example.com", id_type="email"), + scope=ScopePolicy( + intent="Analyse sales data and produce a written summary", + authorized_tools=["fetch_data", "write_report"], + max_hops=5, + ), + ) + + # 2. Build agent as normal + credential = AzureCliCredential() + agent = Agent( + client=FoundryChatClient(credential=credential), + name="sales_analyst", + instructions="You are a sales analyst. Fetch data, then write a summary.", + ) + + # 3. Attach HDP - one line, zero agent changes + middleware.configure(agent) + + # 4. Run + result = await agent.run("Analyse Q1 EMEA sales and write a one-page summary.") + print(result.text) + + # 5. Verify delegation chain offline + token = middleware.export_token() + verification = verify_chain(token, private_key.public_key()) + + print(f"\nHDP chain valid: {verification.valid}") + print(f"Hops recorded: {verification.hop_count}") + if verification.violations: + print(f"Violations: {verification.violations}") + + # --- Tamper resistance --- + # Mutating any hop signature causes verify_chain to return valid=False. + # The first invalid hop is flagged; subsequent hops are not re-checked + # (each is verified against the cumulative chain, so later results are + # unreliable once an earlier hop is broken). + if verification.hop_count > 0: + tampered = dict(token) + tampered["chain"] = [dict(h) for h in token["chain"]] + tampered["chain"][0]["hop_signature"] = "AAAA" # simulate attacker modifying chain + tampered_result = verify_chain(tampered, private_key.public_key()) + print(f"\nTampered chain valid: {tampered_result.valid}") # False + print(f"Tampered violations: {tampered_result.violations}") + + # --- max_hops --- + # ScopePolicy(max_hops=N) caps delegation depth. verify_chain adds a + # violation when len(chain) > max_hops, returning valid=False. + # Example: scope=ScopePolicy(intent="...", max_hops=2) + + # --- Strict vs audit mode --- + # strict=False (default) - scope violations are recorded in the token + # for post-hoc audit; the agent continues running. + # strict=True - raises HDPScopeViolationError immediately on + # any out-of-scope tool call. + # Example: HdpMiddleware(..., strict=True) + + # Full spec: https://helixar.ai/about/labs/hdp/ + # arXiv paper: https://arxiv.org/abs/2604.04522 + + +if __name__ == "__main__": + asyncio.run(main())