@macaw-ai/client 0.0.0-stage → 0.9.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +339 -0
- package/bin/install.js +422 -0
- package/package.json +37 -4
- package/payload/README.md +56 -0
- package/payload/secureAI/LICENSE +190 -0
- package/payload/secureAI/NOTICE +5 -0
- package/payload/secureAI/demos/policy-controls/1_uncontrolled.py +139 -0
- package/payload/secureAI/demos/policy-controls/2_with_macaw.py +139 -0
- package/payload/secureAI/demos/policy-controls/3_with_user_policies.py +162 -0
- package/payload/secureAI/demos/policy-controls/credentials.txt +12 -0
- package/payload/secureAI/demos/policy-controls/malicious_report.txt +21 -0
- package/payload/secureAI/demos/policy-controls/sales_report.txt +20 -0
- package/payload/secureAI/demos/tutorial-1-rbac/README.md +143 -0
- package/payload/secureAI/demos/tutorial-1-rbac/app.py +147 -0
- package/payload/secureAI/demos/tutorial-1-rbac/authprompts_demo.py +306 -0
- package/payload/secureAI/demos/tutorial-1-rbac/config/claims-config.yaml +152 -0
- package/payload/secureAI/demos/tutorial-1-rbac/demo_secureopenai_with_policies.py +150 -0
- package/payload/secureAI/demos/tutorial-1-rbac/policies/bu_Analytics.json +8 -0
- package/payload/secureAI/demos/tutorial-1-rbac/policies/company_FinTech_Corp.json +70 -0
- package/payload/secureAI/demos/tutorial-1-rbac/policies/llm-base.json +69 -0
- package/payload/secureAI/demos/tutorial-1-rbac/policies/team_Reporting.json +8 -0
- package/payload/secureAI/demos/tutorial-1-rbac/policies/user_alice.json +32 -0
- package/payload/secureAI/demos/tutorial-1-rbac/policies/user_bob.json +28 -0
- package/payload/secureAI/demos/tutorial-1-rbac/policies/user_carol.json +34 -0
- package/payload/secureAI/demos/tutorial-1-rbac/requirements.txt +6 -0
- package/payload/secureAI/demos/tutorial-1-rbac/setup/auth0_complete_setup.sh +392 -0
- package/payload/secureAI/demos/tutorial-1-rbac/setup/keycloak_complete_setup.sh +320 -0
- package/payload/secureAI/demos/tutorial-2-intent/Policies/alation-remote-proxy.json +155 -0
- package/payload/secureAI/demos/tutorial-2-intent/Policies/bu.json +22 -0
- package/payload/secureAI/demos/tutorial-2-intent/Policies/company.json +17 -0
- package/payload/secureAI/demos/tutorial-2-intent/Policies/user_alice.json +48 -0
- package/payload/secureAI/demos/tutorial-2-intent/Policies/user_bob.json +83 -0
- package/payload/secureAI/demos/tutorial-2-intent/README.md +213 -0
- package/payload/secureAI/demos/tutorial-2-intent/script.py +188 -0
- package/payload/secureAI/demos/tutorial-2-intent/setup/alation_verifier.py +177 -0
- package/payload/secureAI/demos/tutorial-2-intent/setup/get_alation_user_token.py +180 -0
- package/payload/secureAI/examples/README.md +244 -0
- package/payload/secureAI/examples/anthropic/anthropic_1a_dropin_simple.py +103 -0
- package/payload/secureAI/examples/anthropic/anthropic_1b_multiuser_bind.py +190 -0
- package/payload/secureAI/examples/anthropic/anthropic_1b_multiuser_bind_streaming.py +190 -0
- package/payload/secureAI/examples/anthropic/anthropic_1c_a2a_invoke.py +156 -0
- package/payload/secureAI/examples/attestations/1a_trade_alice.py +215 -0
- package/payload/secureAI/examples/attestations/1a_trade_bob.py +164 -0
- package/payload/secureAI/examples/attestations/1b_grant_admin.py +170 -0
- package/payload/secureAI/examples/attestations/1b_grant_researcher.py +231 -0
- package/payload/secureAI/examples/attestations/README.md +128 -0
- package/payload/secureAI/examples/langchain/langchain_1a_dropin_simple.py +199 -0
- package/payload/secureAI/examples/langchain/langchain_1b_multiuser.py +262 -0
- package/payload/secureAI/examples/langchain/langchain_1c_orchestration.py +332 -0
- package/payload/secureAI/examples/langchain/langchain_1d_llm_openai.py +130 -0
- package/payload/secureAI/examples/langchain/langchain_1e_llm_anthropic.py +130 -0
- package/payload/secureAI/examples/langchain/langchain_1f_memory.py +214 -0
- package/payload/secureAI/examples/litellm/litellm_1a_dropin_simple.py +131 -0
- package/payload/secureAI/examples/mcp/1a_simple_invocation.py +115 -0
- package/payload/secureAI/examples/mcp/1b_discovery_and_resources.py +142 -0
- package/payload/secureAI/examples/mcp/1c_logging_client.py +100 -0
- package/payload/secureAI/examples/mcp/1d_progress_client.py +100 -0
- package/payload/secureAI/examples/mcp/1e_sampling_client.py +150 -0
- package/payload/secureAI/examples/mcp/1e_sampling_server.py +90 -0
- package/payload/secureAI/examples/mcp/1f_elicitation_client.py +127 -0
- package/payload/secureAI/examples/mcp/1f_elicitation_server.py +73 -0
- package/payload/secureAI/examples/mcp/1g_roots_client.py +122 -0
- package/payload/secureAI/examples/mcp/1g_roots_server.py +85 -0
- package/payload/secureAI/examples/mcp/2a_proxy_snowflake.py +212 -0
- package/payload/secureAI/examples/mcp/2b_proxy_databricks.py +299 -0
- package/payload/secureAI/examples/mcp/2c_proxy_stdio_fetch.py +91 -0
- package/payload/secureAI/examples/mcp/2d_proxy_stdio_salesforce.py +119 -0
- package/payload/secureAI/examples/mcp/2e_proxy_multiuser.py +136 -0
- package/payload/secureAI/examples/mcp/2f_proxy_stdio_databricks.py +144 -0
- package/payload/secureAI/examples/mcp/3a_native_mcp_client.py +104 -0
- package/payload/secureAI/examples/mcp/3b_native_mcp_proxy.py +114 -0
- package/payload/secureAI/examples/mcp/3c_native_mcp_sampling.py +180 -0
- package/payload/secureAI/examples/mcp/3d_native_mcp_2026.py +135 -0
- package/payload/secureAI/examples/mcp/securemcp_calculator.py +248 -0
- package/payload/secureAI/examples/mcp/securemcp_server.py +75 -0
- package/payload/secureAI/examples/openai/openai_1a_dropin_simple.py +102 -0
- package/payload/secureAI/examples/openai/openai_1b_multiuser_bind.py +193 -0
- package/payload/secureAI/examples/openai/openai_1b_multiuser_bind_streaming.py +199 -0
- package/payload/secureAI/examples/openai/openai_1c_a2a_invoke.py +156 -0
- package/payload/secureAI/examples/openai/openai_1d_invocation_patterns.py +337 -0
- package/payload/secureAI/examples/phishing-resistance/1a_login_alice.py +180 -0
- package/payload/secureAI/examples/phishing-resistance/1b_trade_alice.py +241 -0
- package/payload/secureAI/examples/phishing-resistance/1b_trade_bob.py +164 -0
- package/payload/secureAI/examples/phishing-resistance/README.md +124 -0
- package/payload/secureAI/examples/phishing-resistance/get_token.py +114 -0
- package/payload/secureAI/examples/pydantic_ai/README.md +88 -0
- package/payload/secureAI/examples/pydantic_ai/policies/app_pydantic-agent.json +10 -0
- package/payload/secureAI/examples/pydantic_ai/pydanticai_1a_dropin_simple.py +90 -0
- package/payload/secureAI/examples/pydantic_ai/pydanticai_1b_model_routing.py +85 -0
- package/payload/secureAI/examples/pydantic_ai/pydanticai_1c_native_tools.py +83 -0
- package/payload/secureAI/examples/pydantic_ai/pydanticai_1d_multiuser_bind.py +104 -0
- package/payload/secureAI/examples/pydantic_ai/pydanticai_1e_mcp_compose.py +92 -0
- package/payload/secureAI/macaw_adapters/__init__.py +55 -0
- package/payload/secureAI/macaw_adapters/anthropic/REFERENCE.md +374 -0
- package/payload/secureAI/macaw_adapters/anthropic/__init__.py +22 -0
- package/payload/secureAI/macaw_adapters/anthropic/secure_anthropic.py +792 -0
- package/payload/secureAI/macaw_adapters/anthropic_ext_tools/__init__.py +25 -0
- package/payload/secureAI/macaw_adapters/anthropic_ext_tools/secure_anthropic.py +840 -0
- package/payload/secureAI/macaw_adapters/langchain/REFERENCE.md +234 -0
- package/payload/secureAI/macaw_adapters/langchain/__init__.py +100 -0
- package/payload/secureAI/macaw_adapters/langchain/_utils.py +83 -0
- package/payload/secureAI/macaw_adapters/langchain/agents.py +296 -0
- package/payload/secureAI/macaw_adapters/langchain/anthropic.py +456 -0
- package/payload/secureAI/macaw_adapters/langchain/callbacks.py +306 -0
- package/payload/secureAI/macaw_adapters/langchain/memory.py +352 -0
- package/payload/secureAI/macaw_adapters/langchain/openai.py +410 -0
- package/payload/secureAI/macaw_adapters/langchain/tools.py +174 -0
- package/payload/secureAI/macaw_adapters/litellm/README.md +216 -0
- package/payload/secureAI/macaw_adapters/litellm/__init__.py +143 -0
- package/payload/secureAI/macaw_adapters/litellm/secure_litellm.py +686 -0
- package/payload/secureAI/macaw_adapters/mcp/REFERENCE.md +460 -0
- package/payload/secureAI/macaw_adapters/mcp/__init__.py +65 -0
- package/payload/secureAI/macaw_adapters/mcp/_endpoint.py +193 -0
- package/payload/secureAI/macaw_adapters/mcp/_endpoint2.py +225 -0
- package/payload/secureAI/macaw_adapters/mcp/_serve.py +25 -0
- package/payload/secureAI/macaw_adapters/mcp/client.py +721 -0
- package/payload/secureAI/macaw_adapters/mcp/decorators.py +228 -0
- package/payload/secureAI/macaw_adapters/mcp/fastmcp_compat.py +353 -0
- package/payload/secureAI/macaw_adapters/mcp/mcp.py +785 -0
- package/payload/secureAI/macaw_adapters/mcp/proxy.py +626 -0
- package/payload/secureAI/macaw_adapters/mcp/server.py +317 -0
- package/payload/secureAI/macaw_adapters/mcp/subscriptions.py +450 -0
- package/payload/secureAI/macaw_adapters/openai/REFERENCE.md +368 -0
- package/payload/secureAI/macaw_adapters/openai/__init__.py +22 -0
- package/payload/secureAI/macaw_adapters/openai/secure_openai.py +833 -0
- package/payload/secureAI/macaw_adapters/openai_ext_tools/REFERENCE.md +368 -0
- package/payload/secureAI/macaw_adapters/openai_ext_tools/__init__.py +25 -0
- package/payload/secureAI/macaw_adapters/openai_ext_tools/secure_openai.py +876 -0
- package/payload/secureAI/macaw_adapters/pydantic_ai/__init__.py +36 -0
- package/payload/secureAI/macaw_adapters/pydantic_ai/_core.py +257 -0
- package/payload/secureAI/macaw_adapters/pydantic_ai/agent.py +218 -0
- package/payload/secureAI/macaw_adapters/pydantic_ai/model.py +99 -0
- package/payload/secureAI/macaw_adapters/pydantic_ai/toolset.py +86 -0
- package/payload/secureAI/test_harness_quick.py +296 -0
- package/wheels/macaw_adapters-0.9.10.1-py3-none-any.whl +0 -0
- package/wheels/macaw_client-0.9.10.1-cp312-cp312-macosx_11_0_arm64.whl +0 -0
- package/wheels/macaw_client-0.9.10.1-cp312-cp312-manylinux_2_17_x86_64.whl +0 -0
- package/wheels/macaw_client-0.9.10.1-cp314-cp314-macosx_11_0_arm64.whl +0 -0
- package/wheels/macaw_client-0.9.10.1-cp314-cp314-manylinux_2_17_x86_64.whl +0 -0
- package/wheels/macaw_client-0.9.10.1-cp314-cp314-win_amd64.whl +0 -0
- package/wheels/macaw_client-0.9.10.1-cp39-cp39-macosx_11_0_arm64.whl +0 -0
- package/README.md +0 -3
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
1a_login_alice.py - Phishing-Resistance (login-strength gating)
|
|
4
|
+
|
|
5
|
+
The simplest phishing-resistance example: a tool that only runs if the
|
|
6
|
+
human behind the agent logged in with a phishing-resistant method
|
|
7
|
+
(passkey / hardware key / WebAuthn).
|
|
8
|
+
|
|
9
|
+
There is no human in the loop and no waiting. When the user authenticates,
|
|
10
|
+
their token carries an `amr` (authentication methods) claim describing how
|
|
11
|
+
they logged in — `fido`/`hwk` for a passkey, `pwd` for a password. The
|
|
12
|
+
control plane reads it at LOGIN and, if the login was phishing-resistant,
|
|
13
|
+
mints an internal `phishing_resistant` grant for the agent. The Policy
|
|
14
|
+
Enforcement Point then allows or denies with no prompt.
|
|
15
|
+
|
|
16
|
+
Run it TWICE, changing only how you log in (via get_token.py):
|
|
17
|
+
PASSKEY -> amr:["fido"] -> phishing-resistant -> ALLOWED
|
|
18
|
+
PASSWORD -> amr:["pwd","mfa"] -> not -> DENIED
|
|
19
|
+
|
|
20
|
+
That single variable — the login method — is the whole lesson.
|
|
21
|
+
|
|
22
|
+
NOTE: no approver is involved here. 1b_trade_alice.py shows the same gate
|
|
23
|
+
used as a precondition in front of a human approval step.
|
|
24
|
+
|
|
25
|
+
Prerequisites:
|
|
26
|
+
- MACAW control plane running (LocalAgent)
|
|
27
|
+
- Entra (Azure AD) connector configured in Console -> Settings -> Identity Bridge
|
|
28
|
+
- An Entra user with a passkey enrolled (for the ALLOW run)
|
|
29
|
+
- A token minted by get_token.py (sets ~/.macaw_demo_token / ALICE_TOKEN)
|
|
30
|
+
|
|
31
|
+
Run:
|
|
32
|
+
python get_token.py # sign in: passkey (ALLOW) or password (DENY)
|
|
33
|
+
export ALICE_TOKEN=$(cat ~/.macaw_demo_token)
|
|
34
|
+
python 1a_login_alice.py
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
import json
|
|
38
|
+
import os
|
|
39
|
+
|
|
40
|
+
from macaw_client import MACAWClient
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# Any amount — there is no threshold here; phishing_resistant is unconditional.
|
|
44
|
+
TRADE_SYMBOL = "AAPL"
|
|
45
|
+
TRADE_AMOUNT = 500
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def execute_trade_handler(params):
|
|
49
|
+
"""Execute trade - the actual tool implementation."""
|
|
50
|
+
print(f"\n [TOOL] execute_trade invoked!")
|
|
51
|
+
print(f" - symbol: {params.get('symbol')}")
|
|
52
|
+
print(f" - amount: ${params.get('amount'):,}")
|
|
53
|
+
print(f" - action: {params.get('action')}")
|
|
54
|
+
return {
|
|
55
|
+
"status": "executed",
|
|
56
|
+
"symbol": params.get("symbol"),
|
|
57
|
+
"amount": params.get("amount"),
|
|
58
|
+
"action": params.get("action"),
|
|
59
|
+
"trade_id": "TRD-00001",
|
|
60
|
+
"message": f"Successfully {params.get('action')} ${params.get('amount'):,} of {params.get('symbol')}",
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def main():
|
|
65
|
+
print("=" * 60)
|
|
66
|
+
print("Example 1a: Phishing-Resistance - Login Gating (Alice)")
|
|
67
|
+
print("=" * 60)
|
|
68
|
+
|
|
69
|
+
# Step 1: Create the Trading Service (provides execute_trade)
|
|
70
|
+
print("\n[Step 1] Creating Trading Service...")
|
|
71
|
+
try:
|
|
72
|
+
trading_service = MACAWClient(
|
|
73
|
+
app_name="trading",
|
|
74
|
+
agent_type="service",
|
|
75
|
+
tools={
|
|
76
|
+
"tool:trading/execute_trade": {
|
|
77
|
+
"handler": execute_trade_handler,
|
|
78
|
+
"description": "Execute a stock trade",
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
)
|
|
82
|
+
if not trading_service.register():
|
|
83
|
+
print(" ERROR: Failed to register Trading Service")
|
|
84
|
+
print(" Make sure the control plane is running: python3 -m macaw_agent.main")
|
|
85
|
+
return 1
|
|
86
|
+
print(f" Service ID: {trading_service.agent_id}")
|
|
87
|
+
except Exception as e:
|
|
88
|
+
print(f" ERROR: Failed to create Trading Service: {e}")
|
|
89
|
+
return 1
|
|
90
|
+
|
|
91
|
+
# Step 2: Authenticate as Alice (token from get_token.py)
|
|
92
|
+
print("\n[Step 2] Authenticating as alice...")
|
|
93
|
+
# passkey login -> amr:fido -> phishing-resistant (ALLOW run)
|
|
94
|
+
# password login -> amr:pwd/mfa -> not (DENY run)
|
|
95
|
+
jwt_token = os.environ.get("ALICE_TOKEN")
|
|
96
|
+
if not jwt_token and os.path.exists(os.path.expanduser("~/.macaw_demo_token")):
|
|
97
|
+
jwt_token = open(os.path.expanduser("~/.macaw_demo_token")).read().strip()
|
|
98
|
+
if not jwt_token:
|
|
99
|
+
print(" ERROR: no ALICE_TOKEN — run get_token.py first")
|
|
100
|
+
trading_service.unregister()
|
|
101
|
+
return 1
|
|
102
|
+
print(" Got JWT token")
|
|
103
|
+
|
|
104
|
+
# Step 3: Create Alice's user agent — gated ONLY by phishing_resistant
|
|
105
|
+
print("\n[Step 3] Creating Alice's user agent...")
|
|
106
|
+
try:
|
|
107
|
+
alice = MACAWClient(
|
|
108
|
+
user_name="alice",
|
|
109
|
+
iam_token=jwt_token,
|
|
110
|
+
agent_type="user",
|
|
111
|
+
app_name="trading-app",
|
|
112
|
+
intent_policy={
|
|
113
|
+
"resources": ["tool:trading/execute_trade"],
|
|
114
|
+
# The one and only gate: the human must have logged in with a
|
|
115
|
+
# phishing-resistant method. Deny-if-absent — no approver, no wait.
|
|
116
|
+
"attestations": ["phishing_resistant"],
|
|
117
|
+
},
|
|
118
|
+
)
|
|
119
|
+
if not alice.register():
|
|
120
|
+
print(" ERROR: Failed to register Alice's agent")
|
|
121
|
+
trading_service.unregister()
|
|
122
|
+
return 1
|
|
123
|
+
print(f" Agent ID: {alice.agent_id}")
|
|
124
|
+
except Exception as e:
|
|
125
|
+
print(f" ERROR: Failed to create Alice's agent: {e}")
|
|
126
|
+
trading_service.unregister()
|
|
127
|
+
return 1
|
|
128
|
+
|
|
129
|
+
# Step 4: Invoke the gated tool — allowed iff the login was phishing-resistant
|
|
130
|
+
print("\n[Step 4] Invoking gated tool (no approver, no waiting)...")
|
|
131
|
+
print(" Gate: attestation 'phishing_resistant' must be present for alice@trading-app")
|
|
132
|
+
print("-" * 60)
|
|
133
|
+
try:
|
|
134
|
+
result = alice.invoke_tool(
|
|
135
|
+
tool_name="tool:trading/execute_trade",
|
|
136
|
+
target_agent=trading_service.agent_id,
|
|
137
|
+
parameters={"symbol": TRADE_SYMBOL, "amount": TRADE_AMOUNT, "action": "buy"},
|
|
138
|
+
)
|
|
139
|
+
print("\n" + "=" * 60)
|
|
140
|
+
print("ALLOWED — login was phishing-resistant (amr:fido)")
|
|
141
|
+
print("=" * 60)
|
|
142
|
+
print(f"Result: {json.dumps(result, indent=2)}")
|
|
143
|
+
except Exception as e:
|
|
144
|
+
error_msg = str(e)
|
|
145
|
+
if "phishing_resistant" in error_msg or "attestation" in error_msg.lower() or "denied" in error_msg.lower():
|
|
146
|
+
print("\n" + "=" * 60)
|
|
147
|
+
print("DENIED — login was NOT phishing-resistant")
|
|
148
|
+
print("=" * 60)
|
|
149
|
+
print(f"\nReason: {error_msg}")
|
|
150
|
+
print("\nRe-run get_token.py and sign in with a PASSKEY to get the ALLOW path.")
|
|
151
|
+
else:
|
|
152
|
+
print(f"\n ERROR: {e}")
|
|
153
|
+
finally:
|
|
154
|
+
print("\n[Cleanup] Unregistering agents...")
|
|
155
|
+
try:
|
|
156
|
+
alice.unregister()
|
|
157
|
+
trading_service.unregister()
|
|
158
|
+
except Exception:
|
|
159
|
+
pass
|
|
160
|
+
|
|
161
|
+
return 0
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
if __name__ == "__main__":
|
|
165
|
+
import sys
|
|
166
|
+
try:
|
|
167
|
+
sys.exit(main())
|
|
168
|
+
except Exception as e:
|
|
169
|
+
err = str(e)
|
|
170
|
+
print("\n" + "=" * 60)
|
|
171
|
+
if "Local provider does not support" in err:
|
|
172
|
+
print("ERROR: Identity Provider not configured")
|
|
173
|
+
print("Fix: Console -> Settings -> Identity Bridge (add the Entra connector)")
|
|
174
|
+
elif "Connection refused" in err or "connect" in err.lower():
|
|
175
|
+
print("ERROR: Cannot connect to MACAW")
|
|
176
|
+
print("Fix: Ensure the control plane (LocalAgent) is running")
|
|
177
|
+
else:
|
|
178
|
+
print(f"ERROR: {e}")
|
|
179
|
+
print("=" * 60)
|
|
180
|
+
sys.exit(1)
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
1b_trade_alice.py - Phishing-Resistance as a precondition to human approval
|
|
4
|
+
|
|
5
|
+
This builds on the alice/bob trade example (examples/attestations/) by
|
|
6
|
+
LAYERING two gates on the same trade:
|
|
7
|
+
|
|
8
|
+
1. phishing_resistant - internal, decided at login from the token's amr.
|
|
9
|
+
Deny-if-absent, no approver, checked FIRST.
|
|
10
|
+
2. trade-approved - external HITL, blocks waiting for a manager (bob),
|
|
11
|
+
only required when amount > $10,000.
|
|
12
|
+
|
|
13
|
+
Two run outcomes, driven only by how Alice logged in (via get_token.py):
|
|
14
|
+
|
|
15
|
+
PASSKEY -> clears phishing_resistant -> BLOCKS on trade-approved
|
|
16
|
+
-> bob approves (1b_trade_bob.py) -> trade executes.
|
|
17
|
+
PASSWORD -> DENIED at phishing_resistant. The trade never reaches the
|
|
18
|
+
manager gate; bob is never involved.
|
|
19
|
+
|
|
20
|
+
Start simple with 1a_login_alice.py (phishing-resistance alone); this
|
|
21
|
+
example shows how it composes with a human approval step.
|
|
22
|
+
|
|
23
|
+
NOTE: interactive — the passkey run needs bob to approve. Not for
|
|
24
|
+
automated test harnesses.
|
|
25
|
+
|
|
26
|
+
Prerequisites:
|
|
27
|
+
- MACAW control plane running (LocalAgent)
|
|
28
|
+
- Entra connector configured (Console -> Settings -> Identity Bridge),
|
|
29
|
+
an Entra user with a passkey, and a token from get_token.py
|
|
30
|
+
- bob configured as an approver with role:manager (see attestations/ demo)
|
|
31
|
+
|
|
32
|
+
Run:
|
|
33
|
+
python get_token.py # sign in with a PASSKEY
|
|
34
|
+
export ALICE_TOKEN=$(cat ~/.macaw_demo_token)
|
|
35
|
+
|
|
36
|
+
# Terminal 1: Alice's trade (clears phishing gate, then blocks on approval)
|
|
37
|
+
python 1b_trade_alice.py
|
|
38
|
+
|
|
39
|
+
# Terminal 2: Bob approves
|
|
40
|
+
python 1b_trade_bob.py
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
import json
|
|
44
|
+
import os
|
|
45
|
+
|
|
46
|
+
from macaw_client import MACAWClient, RemoteIdentityProvider
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# Trade configuration
|
|
50
|
+
TRADE_SYMBOL = "AAPL"
|
|
51
|
+
TRADE_AMOUNT = 15000 # High-value trade requiring approval
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def execute_trade_handler(params):
|
|
55
|
+
"""Execute trade - the actual tool implementation."""
|
|
56
|
+
print(f"\n [TOOL] execute_trade invoked!")
|
|
57
|
+
print(f" [TOOL] Received parameters:")
|
|
58
|
+
print(f" - symbol: {params.get('symbol')}")
|
|
59
|
+
print(f" - amount: ${params.get('amount'):,}")
|
|
60
|
+
print(f" - action: {params.get('action')}")
|
|
61
|
+
|
|
62
|
+
# Simulate trade execution
|
|
63
|
+
result = {
|
|
64
|
+
"status": "executed",
|
|
65
|
+
"symbol": params.get("symbol"),
|
|
66
|
+
"amount": params.get("amount"),
|
|
67
|
+
"action": params.get("action"),
|
|
68
|
+
"trade_id": "TRD-12345",
|
|
69
|
+
"message": f"Successfully {params.get('action')} ${params.get('amount'):,} of {params.get('symbol')}"
|
|
70
|
+
}
|
|
71
|
+
print(f" [TOOL] Returning: {result}")
|
|
72
|
+
return result
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def main():
|
|
76
|
+
print("=" * 60)
|
|
77
|
+
print("Example 1b: Phishing-Resistance + Trade Approval (Alice)")
|
|
78
|
+
print("=" * 60)
|
|
79
|
+
|
|
80
|
+
# Step 1: Create Trading Service (provides execute_trade tool)
|
|
81
|
+
print("\n[Step 1] Creating Trading Service...")
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
trading_service = MACAWClient(
|
|
85
|
+
app_name="trading",
|
|
86
|
+
agent_type="service",
|
|
87
|
+
tools={
|
|
88
|
+
"tool:trading/execute_trade": {
|
|
89
|
+
"handler": execute_trade_handler,
|
|
90
|
+
"description": "Execute a stock trade"
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
if not trading_service.register():
|
|
96
|
+
print(" ERROR: Failed to register Trading Service")
|
|
97
|
+
print("\n Make sure LocalAgent is running:")
|
|
98
|
+
print(" python3 -m macaw_agent.main")
|
|
99
|
+
return 1
|
|
100
|
+
|
|
101
|
+
print(f" Service ID: {trading_service.agent_id}")
|
|
102
|
+
print(f" Provides: tool:trading/execute_trade")
|
|
103
|
+
|
|
104
|
+
except Exception as e:
|
|
105
|
+
print(f" ERROR: Failed to create Trading Service: {e}")
|
|
106
|
+
return 1
|
|
107
|
+
|
|
108
|
+
# Step 2: Authenticate as Alice
|
|
109
|
+
print("\n[Step 2] Authenticating as alice...")
|
|
110
|
+
try:
|
|
111
|
+
# Alice authenticates via Entra. Token is minted by get_token.py:
|
|
112
|
+
# passkey login -> amr:fido -> phishing-resistant (ALLOW path)
|
|
113
|
+
# password login -> amr:pwd/mfa -> not (DENY path)
|
|
114
|
+
jwt_token = os.environ.get("ALICE_TOKEN")
|
|
115
|
+
if not jwt_token and os.path.exists(os.path.expanduser("~/.macaw_demo_token")):
|
|
116
|
+
jwt_token = open(os.path.expanduser("~/.macaw_demo_token")).read().strip()
|
|
117
|
+
if not jwt_token:
|
|
118
|
+
print(" ERROR: no ALICE_TOKEN — run get_token.py first")
|
|
119
|
+
return 1
|
|
120
|
+
print(" Got JWT token")
|
|
121
|
+
except Exception as e:
|
|
122
|
+
print(f" ERROR: Failed to authenticate: {e}")
|
|
123
|
+
print("\n Make sure your identity provider is running and alice user exists")
|
|
124
|
+
trading_service.unregister()
|
|
125
|
+
return 1
|
|
126
|
+
|
|
127
|
+
# Step 3: Create Alice's user agent with attestation policy
|
|
128
|
+
print("\n[Step 3] Creating Alice's user agent...")
|
|
129
|
+
try:
|
|
130
|
+
# MAPL v3 format for attestations:
|
|
131
|
+
# - "attestations" array: defines WHEN attestation is required
|
|
132
|
+
# - "constraints.attestations": defines HOW attestation works
|
|
133
|
+
alice = MACAWClient(
|
|
134
|
+
user_name="alice",
|
|
135
|
+
iam_token=jwt_token,
|
|
136
|
+
agent_type="user",
|
|
137
|
+
app_name="trading-app",
|
|
138
|
+
intent_policy={
|
|
139
|
+
"resources": ["tool:trading/execute_trade"],
|
|
140
|
+
# Attestation required when amount > 10000
|
|
141
|
+
"attestations": [
|
|
142
|
+
# Precondition: the human must have logged in with a phishing-resistant
|
|
143
|
+
# method (hardware key / passkey). Deny-if-absent, checked before approval.
|
|
144
|
+
"phishing_resistant",
|
|
145
|
+
"trade-approved::{params.amount > 10000}"
|
|
146
|
+
],
|
|
147
|
+
# Attestation metadata
|
|
148
|
+
"constraints": {
|
|
149
|
+
"attestations": {
|
|
150
|
+
"trade-approved": {
|
|
151
|
+
"approval_criteria": "role:manager",
|
|
152
|
+
"timeout": 300, # 5 minutes to approve
|
|
153
|
+
"time_to_live": 3600, # Valid for 1 hour after approval
|
|
154
|
+
"one_time": True # Consumed after single use
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
if not alice.register():
|
|
162
|
+
print(" ERROR: Failed to register Alice's agent")
|
|
163
|
+
trading_service.unregister()
|
|
164
|
+
return 1
|
|
165
|
+
|
|
166
|
+
print(f" Agent ID: {alice.agent_id}")
|
|
167
|
+
|
|
168
|
+
except Exception as e:
|
|
169
|
+
print(f" ERROR: Failed to create Alice's agent: {e}")
|
|
170
|
+
trading_service.unregister()
|
|
171
|
+
return 1
|
|
172
|
+
|
|
173
|
+
# Step 4: Execute trade (will block waiting for approval)
|
|
174
|
+
print("\n[Step 4] Executing trade...")
|
|
175
|
+
print(f" Symbol: {TRADE_SYMBOL}")
|
|
176
|
+
print(f" Amount: ${TRADE_AMOUNT:,}")
|
|
177
|
+
print(f"\n Policy check: amount ({TRADE_AMOUNT}) > 10000 -> attestation REQUIRED")
|
|
178
|
+
print(f" Attestation: 'trade-approved' requires role:manager")
|
|
179
|
+
print(f"\n Creating PENDING attestation and BLOCKING...")
|
|
180
|
+
print(f" Bob has 5 minutes (timeout=300s) to approve.")
|
|
181
|
+
print(f"\n >>> Run 1b_trade_bob.py in another terminal <<<")
|
|
182
|
+
print("-" * 60)
|
|
183
|
+
|
|
184
|
+
try:
|
|
185
|
+
# This will block internally while waiting for attestation
|
|
186
|
+
result = alice.invoke_tool(
|
|
187
|
+
tool_name="tool:trading/execute_trade",
|
|
188
|
+
target_agent=trading_service.agent_id,
|
|
189
|
+
parameters={
|
|
190
|
+
"symbol": TRADE_SYMBOL,
|
|
191
|
+
"amount": TRADE_AMOUNT,
|
|
192
|
+
"action": "buy"
|
|
193
|
+
}
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
print("\n" + "=" * 60)
|
|
197
|
+
print("TRADE EXECUTED SUCCESSFULLY!")
|
|
198
|
+
print("=" * 60)
|
|
199
|
+
print(f"Result: {json.dumps(result, indent=2)}")
|
|
200
|
+
|
|
201
|
+
except Exception as e:
|
|
202
|
+
error_msg = str(e)
|
|
203
|
+
if "attestation" in error_msg.lower() or "denied" in error_msg.lower():
|
|
204
|
+
print("\n" + "=" * 60)
|
|
205
|
+
print("TRADE BLOCKED - ATTESTATION REQUIRED OR DENIED")
|
|
206
|
+
print("=" * 60)
|
|
207
|
+
print(f"\nError: {error_msg}")
|
|
208
|
+
print(f"\nTo approve, Bob should run:")
|
|
209
|
+
print(f" python 1b_trade_bob.py")
|
|
210
|
+
else:
|
|
211
|
+
print(f"\n ERROR: {e}")
|
|
212
|
+
|
|
213
|
+
finally:
|
|
214
|
+
# Cleanup
|
|
215
|
+
print("\n[Cleanup] Unregistering agents...")
|
|
216
|
+
try:
|
|
217
|
+
alice.unregister()
|
|
218
|
+
trading_service.unregister()
|
|
219
|
+
except:
|
|
220
|
+
pass
|
|
221
|
+
|
|
222
|
+
return 0
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
if __name__ == "__main__":
|
|
226
|
+
import sys
|
|
227
|
+
try:
|
|
228
|
+
sys.exit(main())
|
|
229
|
+
except Exception as e:
|
|
230
|
+
err = str(e)
|
|
231
|
+
print("\n" + "=" * 60)
|
|
232
|
+
if "Local provider does not support" in err:
|
|
233
|
+
print("ERROR: Identity Provider not configured")
|
|
234
|
+
print("Fix: Console -> Settings -> Identity Bridge")
|
|
235
|
+
elif "Connection refused" in err or "connect" in err.lower():
|
|
236
|
+
print("ERROR: Cannot connect to MACAW")
|
|
237
|
+
print("Fix: Ensure LocalAgent is running")
|
|
238
|
+
else:
|
|
239
|
+
print(f"ERROR: {e}")
|
|
240
|
+
print("=" * 60)
|
|
241
|
+
sys.exit(1)
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
1b_trade_bob.py - External Attestation Manager Approval
|
|
4
|
+
|
|
5
|
+
Bob reviews and approves/denies pending attestation requests.
|
|
6
|
+
Alice's trade request is BLOCKING, waiting for Bob's approval.
|
|
7
|
+
|
|
8
|
+
This demonstrates:
|
|
9
|
+
- list_attestations(): Find pending requests you can approve
|
|
10
|
+
- approve_attestation(): Grant the request
|
|
11
|
+
- deny_attestation(): Reject the request with reason
|
|
12
|
+
|
|
13
|
+
NOTE: This example requires interactive input and is not suitable
|
|
14
|
+
for automated test harnesses.
|
|
15
|
+
|
|
16
|
+
Prerequisites:
|
|
17
|
+
- MACAW SDK installed (pip install macaw-client macaw-adapters)
|
|
18
|
+
- Identity Provider configured (Console -> Settings -> Identity Bridge)
|
|
19
|
+
- Test users: alice/Alice123!, bob/Bob@123! (bob needs manager role)
|
|
20
|
+
- Alice has already requested a trade (1b_trade_alice.py running)
|
|
21
|
+
|
|
22
|
+
Run:
|
|
23
|
+
# Terminal 1: Run Alice's trade request first
|
|
24
|
+
python 1b_trade_alice.py
|
|
25
|
+
|
|
26
|
+
# Terminal 2: Run Bob's approval
|
|
27
|
+
python 1b_trade_bob.py
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
import json
|
|
31
|
+
|
|
32
|
+
from macaw_client import MACAWClient, RemoteIdentityProvider
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def main():
|
|
36
|
+
print("=" * 60)
|
|
37
|
+
print("Example 1b: External Attestation - Manager Approval (Bob)")
|
|
38
|
+
print("=" * 60)
|
|
39
|
+
|
|
40
|
+
# Step 1: Authenticate as Bob
|
|
41
|
+
print("\n[Step 1] Authenticating as bob...")
|
|
42
|
+
try:
|
|
43
|
+
jwt_token, _ = RemoteIdentityProvider().login("bob", "Bob@123!")
|
|
44
|
+
print(" Got JWT token")
|
|
45
|
+
except Exception as e:
|
|
46
|
+
print(f" ERROR: Failed to authenticate: {e}")
|
|
47
|
+
print("\n Make sure your identity provider is running and bob user exists")
|
|
48
|
+
return 1
|
|
49
|
+
|
|
50
|
+
# Step 2: Create Bob's user agent
|
|
51
|
+
print("\n[Step 2] Creating Bob's user agent...")
|
|
52
|
+
try:
|
|
53
|
+
bob = MACAWClient(
|
|
54
|
+
user_name="bob",
|
|
55
|
+
iam_token=jwt_token,
|
|
56
|
+
agent_type="user",
|
|
57
|
+
app_name="attestation-manager",
|
|
58
|
+
intent_policy={
|
|
59
|
+
"resources": ["attestation:*"],
|
|
60
|
+
"constraints": {
|
|
61
|
+
"roles": ["manager"]
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
if not bob.register():
|
|
67
|
+
print(" ERROR: Failed to register Bob's agent")
|
|
68
|
+
print("\n Make sure LocalAgent is running:")
|
|
69
|
+
print(" python3 -m macaw_agent.main")
|
|
70
|
+
return 1
|
|
71
|
+
|
|
72
|
+
print(f" Agent ID: {bob.agent_id}")
|
|
73
|
+
|
|
74
|
+
except Exception as e:
|
|
75
|
+
print(f" ERROR: Failed to create Bob's agent: {e}")
|
|
76
|
+
return 1
|
|
77
|
+
|
|
78
|
+
# Step 3: List pending attestations
|
|
79
|
+
print("\n[Step 3] Listing pending attestations...")
|
|
80
|
+
try:
|
|
81
|
+
attestations = bob.list_attestations(status="pending")
|
|
82
|
+
except Exception as e:
|
|
83
|
+
print(f" ERROR: Failed to list attestations: {e}")
|
|
84
|
+
bob.unregister()
|
|
85
|
+
return 1
|
|
86
|
+
|
|
87
|
+
if not attestations:
|
|
88
|
+
print(" No pending attestations found.")
|
|
89
|
+
print("\n Make sure Alice has requested a trade first:")
|
|
90
|
+
print(" python 1b_trade_alice.py")
|
|
91
|
+
bob.unregister()
|
|
92
|
+
return 0
|
|
93
|
+
|
|
94
|
+
print(f"\n Found {len(attestations)} pending attestation(s):\n")
|
|
95
|
+
|
|
96
|
+
for i, att in enumerate(attestations, 1):
|
|
97
|
+
print(f" [{i}] Key: {att.get('key')}")
|
|
98
|
+
print(f" For Agent: {att.get('for_agent')}")
|
|
99
|
+
print(f" Approval Criteria: {att.get('approval_criteria')}")
|
|
100
|
+
print(f" One-Time: {att.get('one_time', False)}")
|
|
101
|
+
if att.get('value'):
|
|
102
|
+
print(f" Value: {json.dumps(att.get('value', {}))}")
|
|
103
|
+
print()
|
|
104
|
+
|
|
105
|
+
# Step 4: Approve/Deny attestation(s)
|
|
106
|
+
print("[Step 4] Process attestations...")
|
|
107
|
+
print(" Options: [y] Approve [d] Deny [s] Skip")
|
|
108
|
+
|
|
109
|
+
for i, att in enumerate(attestations, 1):
|
|
110
|
+
key = att.get('key')
|
|
111
|
+
for_agent = att.get('for_agent')
|
|
112
|
+
|
|
113
|
+
response = input(f"\n '{key}' for {for_agent}? [y/d/s]: ").strip().lower()
|
|
114
|
+
|
|
115
|
+
if response == 'y':
|
|
116
|
+
print(f" Approving...")
|
|
117
|
+
try:
|
|
118
|
+
if bob.approve_attestation(att, reason="Manager approved"):
|
|
119
|
+
print(f" APPROVED - Alice's request will proceed")
|
|
120
|
+
else:
|
|
121
|
+
print(f" Failed to approve")
|
|
122
|
+
except Exception as e:
|
|
123
|
+
print(f" ERROR: {e}")
|
|
124
|
+
|
|
125
|
+
elif response == 'd':
|
|
126
|
+
print(f" Denying...")
|
|
127
|
+
try:
|
|
128
|
+
if bob.deny_attestation(att, reason="Manager denied"):
|
|
129
|
+
print(f" DENIED - Alice's request will be rejected")
|
|
130
|
+
else:
|
|
131
|
+
print(f" Failed to deny")
|
|
132
|
+
except Exception as e:
|
|
133
|
+
print(f" ERROR: {e}")
|
|
134
|
+
|
|
135
|
+
else:
|
|
136
|
+
print(f" Skipped")
|
|
137
|
+
|
|
138
|
+
# Cleanup
|
|
139
|
+
bob.unregister()
|
|
140
|
+
|
|
141
|
+
print("\n" + "=" * 60)
|
|
142
|
+
print("Done. Alice's request should now proceed (or fail if denied).")
|
|
143
|
+
print("=" * 60)
|
|
144
|
+
|
|
145
|
+
return 0
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
if __name__ == "__main__":
|
|
149
|
+
import sys
|
|
150
|
+
try:
|
|
151
|
+
sys.exit(main())
|
|
152
|
+
except Exception as e:
|
|
153
|
+
err = str(e)
|
|
154
|
+
print("\n" + "=" * 60)
|
|
155
|
+
if "Local provider does not support" in err:
|
|
156
|
+
print("ERROR: Identity Provider not configured")
|
|
157
|
+
print("Fix: Console -> Settings -> Identity Bridge")
|
|
158
|
+
elif "Connection refused" in err or "connect" in err.lower():
|
|
159
|
+
print("ERROR: Cannot connect to MACAW")
|
|
160
|
+
print("Fix: Ensure LocalAgent is running")
|
|
161
|
+
else:
|
|
162
|
+
print(f"ERROR: {e}")
|
|
163
|
+
print("=" * 60)
|
|
164
|
+
sys.exit(1)
|