network-ai 3.3.0 → 3.3.2
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 +21 -21
- package/QUICKSTART.md +260 -260
- package/README.md +783 -783
- package/SKILL.md +578 -578
- package/dist/lib/locked-blackboard.js +48 -48
- package/dist/lib/swarm-utils.js +24 -24
- package/package.json +85 -85
- package/scripts/blackboard.py +852 -852
- package/scripts/check_permission.py +699 -699
- package/scripts/revoke_token.py +243 -243
- package/scripts/swarm_guard.py +1136 -1136
- package/scripts/validate_token.py +97 -97
- package/types/agent-adapter.d.ts +244 -244
- package/types/openclaw-core.d.ts +52 -52
package/scripts/revoke_token.py
CHANGED
|
@@ -1,243 +1,243 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Revoke Grant Token & TTL Enforcement
|
|
4
|
-
|
|
5
|
-
Revoke permission tokens and automatically cleanup expired grants.
|
|
6
|
-
|
|
7
|
-
Usage:
|
|
8
|
-
python revoke_token.py TOKEN # Revoke specific token
|
|
9
|
-
python revoke_token.py --cleanup # Remove all expired tokens
|
|
10
|
-
python revoke_token.py --list-expired # List expired tokens without removing
|
|
11
|
-
|
|
12
|
-
Example:
|
|
13
|
-
python revoke_token.py grant_a1b2c3d4e5f6
|
|
14
|
-
python revoke_token.py --cleanup --json
|
|
15
|
-
"""
|
|
16
|
-
|
|
17
|
-
import argparse
|
|
18
|
-
import json
|
|
19
|
-
import sys
|
|
20
|
-
from datetime import datetime, timezone
|
|
21
|
-
from pathlib import Path
|
|
22
|
-
from typing import Any
|
|
23
|
-
|
|
24
|
-
GRANTS_FILE = Path(__file__).parent.parent / "data" / "active_grants.json"
|
|
25
|
-
AUDIT_LOG = Path(__file__).parent.parent / "data" / "audit_log.jsonl"
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def log_audit(action: str, details: dict[str, Any]) -> None:
|
|
29
|
-
"""Append entry to audit log."""
|
|
30
|
-
AUDIT_LOG.parent.mkdir(exist_ok=True)
|
|
31
|
-
entry: dict[str, Any] = {
|
|
32
|
-
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
33
|
-
"action": action,
|
|
34
|
-
"details": details
|
|
35
|
-
}
|
|
36
|
-
with open(AUDIT_LOG, "a") as f:
|
|
37
|
-
f.write(json.dumps(entry) + "\n")
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def revoke_token(token: str) -> dict[str, Any]:
|
|
41
|
-
"""Revoke a grant token."""
|
|
42
|
-
if not GRANTS_FILE.exists():
|
|
43
|
-
return {
|
|
44
|
-
"revoked": False,
|
|
45
|
-
"reason": "No grants file found"
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
try:
|
|
49
|
-
grants = json.loads(GRANTS_FILE.read_text())
|
|
50
|
-
except json.JSONDecodeError:
|
|
51
|
-
return {
|
|
52
|
-
"revoked": False,
|
|
53
|
-
"reason": "Invalid grants file"
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if token not in grants:
|
|
57
|
-
return {
|
|
58
|
-
"revoked": False,
|
|
59
|
-
"reason": "Token not found"
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
grant = grants.pop(token)
|
|
63
|
-
GRANTS_FILE.write_text(json.dumps(grants, indent=2))
|
|
64
|
-
|
|
65
|
-
log_audit("permission_revoked", {
|
|
66
|
-
"token": token,
|
|
67
|
-
"original_grant": grant
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
return {
|
|
71
|
-
"revoked": True,
|
|
72
|
-
"grant": grant
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
def is_token_expired(grant: dict[str, Any]) -> bool:
|
|
77
|
-
"""Check if a grant token has expired."""
|
|
78
|
-
expires_at = grant.get("expires_at")
|
|
79
|
-
if not expires_at:
|
|
80
|
-
return False
|
|
81
|
-
try:
|
|
82
|
-
expiry_time = datetime.fromisoformat(expires_at.replace("Z", "+00:00"))
|
|
83
|
-
return datetime.now(timezone.utc) > expiry_time
|
|
84
|
-
except (ValueError, AttributeError):
|
|
85
|
-
return False
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
def list_expired_tokens() -> dict[str, Any]:
|
|
89
|
-
"""List all expired tokens without removing them."""
|
|
90
|
-
if not GRANTS_FILE.exists():
|
|
91
|
-
return {"expired_tokens": [], "total_grants": 0}
|
|
92
|
-
|
|
93
|
-
try:
|
|
94
|
-
grants = json.loads(GRANTS_FILE.read_text())
|
|
95
|
-
except json.JSONDecodeError:
|
|
96
|
-
return {"error": "Invalid grants file"}
|
|
97
|
-
|
|
98
|
-
expired: list[dict[str, Any]] = []
|
|
99
|
-
now = datetime.now(timezone.utc)
|
|
100
|
-
|
|
101
|
-
for token, grant in grants.items():
|
|
102
|
-
if is_token_expired(grant):
|
|
103
|
-
expired.append({
|
|
104
|
-
"token": token,
|
|
105
|
-
"agent": grant.get("agent_id"),
|
|
106
|
-
"resource": grant.get("resource_type"),
|
|
107
|
-
"expired_at": grant.get("expires_at")
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
return {
|
|
111
|
-
"expired_tokens": expired,
|
|
112
|
-
"expired_count": len(expired),
|
|
113
|
-
"total_grants": len(grants),
|
|
114
|
-
"checked_at": now.isoformat()
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
def cleanup_expired_tokens() -> dict[str, Any]:
|
|
119
|
-
"""Remove all expired tokens from active grants (TTL enforcement)."""
|
|
120
|
-
if not GRANTS_FILE.exists():
|
|
121
|
-
return {
|
|
122
|
-
"cleaned": 0,
|
|
123
|
-
"remaining": 0,
|
|
124
|
-
"message": "No grants file found"
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
try:
|
|
128
|
-
grants = json.loads(GRANTS_FILE.read_text())
|
|
129
|
-
except json.JSONDecodeError:
|
|
130
|
-
return {
|
|
131
|
-
"cleaned": 0,
|
|
132
|
-
"error": "Invalid grants file"
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
original_count = len(grants)
|
|
136
|
-
expired_tokens: list[dict[str, Any]] = []
|
|
137
|
-
|
|
138
|
-
# Find and remove expired tokens
|
|
139
|
-
tokens_to_remove: list[str] = []
|
|
140
|
-
for token, grant in grants.items():
|
|
141
|
-
if is_token_expired(grant):
|
|
142
|
-
tokens_to_remove.append(token)
|
|
143
|
-
expired_tokens.append({
|
|
144
|
-
"token": token,
|
|
145
|
-
"agent": grant.get("agent_id"),
|
|
146
|
-
"resource": grant.get("resource_type"),
|
|
147
|
-
"expired_at": grant.get("expires_at")
|
|
148
|
-
})
|
|
149
|
-
|
|
150
|
-
for token in tokens_to_remove:
|
|
151
|
-
grants.pop(token)
|
|
152
|
-
|
|
153
|
-
# Write back cleaned grants
|
|
154
|
-
GRANTS_FILE.write_text(json.dumps(grants, indent=2))
|
|
155
|
-
|
|
156
|
-
# Log the cleanup
|
|
157
|
-
if expired_tokens:
|
|
158
|
-
log_audit("ttl_cleanup", {
|
|
159
|
-
"expired_count": len(expired_tokens),
|
|
160
|
-
"expired_tokens": expired_tokens,
|
|
161
|
-
"remaining_grants": len(grants)
|
|
162
|
-
})
|
|
163
|
-
|
|
164
|
-
return {
|
|
165
|
-
"cleaned": len(expired_tokens),
|
|
166
|
-
"expired_tokens": expired_tokens,
|
|
167
|
-
"remaining": len(grants),
|
|
168
|
-
"original_count": original_count,
|
|
169
|
-
"cleaned_at": datetime.now(timezone.utc).isoformat()
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
def main():
|
|
174
|
-
parser = argparse.ArgumentParser(
|
|
175
|
-
description="Revoke permission grant tokens and enforce TTL cleanup"
|
|
176
|
-
)
|
|
177
|
-
parser.add_argument("token", nargs="?", help="Grant token to revoke")
|
|
178
|
-
parser.add_argument("--cleanup", action="store_true",
|
|
179
|
-
help="Remove all expired tokens (TTL enforcement)")
|
|
180
|
-
parser.add_argument("--list-expired", action="store_true",
|
|
181
|
-
help="List expired tokens without removing")
|
|
182
|
-
parser.add_argument("--json", action="store_true", help="Output as JSON")
|
|
183
|
-
|
|
184
|
-
args = parser.parse_args()
|
|
185
|
-
|
|
186
|
-
# Handle --list-expired
|
|
187
|
-
if args.list_expired:
|
|
188
|
-
result = list_expired_tokens()
|
|
189
|
-
if args.json:
|
|
190
|
-
print(json.dumps(result, indent=2))
|
|
191
|
-
else:
|
|
192
|
-
expired = result.get("expired_tokens", [])
|
|
193
|
-
if expired:
|
|
194
|
-
print(f"⏰ Found {len(expired)} expired token(s):")
|
|
195
|
-
for t in expired:
|
|
196
|
-
print(f" • {t['token'][:20]}... ({t['agent']} → {t['resource']})")
|
|
197
|
-
print(f" Expired: {t['expired_at']}")
|
|
198
|
-
else:
|
|
199
|
-
print("✅ No expired tokens found")
|
|
200
|
-
print(f"\n Total grants: {result.get('total_grants', 0)}")
|
|
201
|
-
sys.exit(0)
|
|
202
|
-
|
|
203
|
-
# Handle --cleanup
|
|
204
|
-
if args.cleanup:
|
|
205
|
-
result = cleanup_expired_tokens()
|
|
206
|
-
if args.json:
|
|
207
|
-
print(json.dumps(result, indent=2))
|
|
208
|
-
else:
|
|
209
|
-
cleaned = result.get("cleaned", 0)
|
|
210
|
-
if cleaned > 0:
|
|
211
|
-
print(f"🧹 TTL Cleanup Complete")
|
|
212
|
-
print(f" Removed: {cleaned} expired token(s)")
|
|
213
|
-
for t in result.get("expired_tokens", []):
|
|
214
|
-
print(f" • {t['token'][:20]}... ({t['agent']})")
|
|
215
|
-
else:
|
|
216
|
-
print("✅ No expired tokens to clean")
|
|
217
|
-
print(f" Remaining active grants: {result.get('remaining', 0)}")
|
|
218
|
-
sys.exit(0)
|
|
219
|
-
|
|
220
|
-
# Handle single token revocation
|
|
221
|
-
if not args.token:
|
|
222
|
-
parser.print_help()
|
|
223
|
-
sys.exit(1)
|
|
224
|
-
|
|
225
|
-
result = revoke_token(args.token)
|
|
226
|
-
|
|
227
|
-
if args.json:
|
|
228
|
-
print(json.dumps(result, indent=2))
|
|
229
|
-
else:
|
|
230
|
-
if result["revoked"]:
|
|
231
|
-
grant = result["grant"]
|
|
232
|
-
print("✅ Token REVOKED")
|
|
233
|
-
print(f" Agent: {grant.get('agent_id')}")
|
|
234
|
-
print(f" Resource: {grant.get('resource_type')}")
|
|
235
|
-
else:
|
|
236
|
-
print("❌ Revocation FAILED")
|
|
237
|
-
print(f" Reason: {result.get('reason')}")
|
|
238
|
-
|
|
239
|
-
sys.exit(0 if result.get("revoked") or result.get("cleaned", 0) >= 0 else 1)
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
if __name__ == "__main__":
|
|
243
|
-
main()
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Revoke Grant Token & TTL Enforcement
|
|
4
|
+
|
|
5
|
+
Revoke permission tokens and automatically cleanup expired grants.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
python revoke_token.py TOKEN # Revoke specific token
|
|
9
|
+
python revoke_token.py --cleanup # Remove all expired tokens
|
|
10
|
+
python revoke_token.py --list-expired # List expired tokens without removing
|
|
11
|
+
|
|
12
|
+
Example:
|
|
13
|
+
python revoke_token.py grant_a1b2c3d4e5f6
|
|
14
|
+
python revoke_token.py --cleanup --json
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import argparse
|
|
18
|
+
import json
|
|
19
|
+
import sys
|
|
20
|
+
from datetime import datetime, timezone
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
GRANTS_FILE = Path(__file__).parent.parent / "data" / "active_grants.json"
|
|
25
|
+
AUDIT_LOG = Path(__file__).parent.parent / "data" / "audit_log.jsonl"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def log_audit(action: str, details: dict[str, Any]) -> None:
|
|
29
|
+
"""Append entry to audit log."""
|
|
30
|
+
AUDIT_LOG.parent.mkdir(exist_ok=True)
|
|
31
|
+
entry: dict[str, Any] = {
|
|
32
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
33
|
+
"action": action,
|
|
34
|
+
"details": details
|
|
35
|
+
}
|
|
36
|
+
with open(AUDIT_LOG, "a") as f:
|
|
37
|
+
f.write(json.dumps(entry) + "\n")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def revoke_token(token: str) -> dict[str, Any]:
|
|
41
|
+
"""Revoke a grant token."""
|
|
42
|
+
if not GRANTS_FILE.exists():
|
|
43
|
+
return {
|
|
44
|
+
"revoked": False,
|
|
45
|
+
"reason": "No grants file found"
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
grants = json.loads(GRANTS_FILE.read_text())
|
|
50
|
+
except json.JSONDecodeError:
|
|
51
|
+
return {
|
|
52
|
+
"revoked": False,
|
|
53
|
+
"reason": "Invalid grants file"
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if token not in grants:
|
|
57
|
+
return {
|
|
58
|
+
"revoked": False,
|
|
59
|
+
"reason": "Token not found"
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
grant = grants.pop(token)
|
|
63
|
+
GRANTS_FILE.write_text(json.dumps(grants, indent=2))
|
|
64
|
+
|
|
65
|
+
log_audit("permission_revoked", {
|
|
66
|
+
"token": token,
|
|
67
|
+
"original_grant": grant
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
"revoked": True,
|
|
72
|
+
"grant": grant
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def is_token_expired(grant: dict[str, Any]) -> bool:
|
|
77
|
+
"""Check if a grant token has expired."""
|
|
78
|
+
expires_at = grant.get("expires_at")
|
|
79
|
+
if not expires_at:
|
|
80
|
+
return False
|
|
81
|
+
try:
|
|
82
|
+
expiry_time = datetime.fromisoformat(expires_at.replace("Z", "+00:00"))
|
|
83
|
+
return datetime.now(timezone.utc) > expiry_time
|
|
84
|
+
except (ValueError, AttributeError):
|
|
85
|
+
return False
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def list_expired_tokens() -> dict[str, Any]:
|
|
89
|
+
"""List all expired tokens without removing them."""
|
|
90
|
+
if not GRANTS_FILE.exists():
|
|
91
|
+
return {"expired_tokens": [], "total_grants": 0}
|
|
92
|
+
|
|
93
|
+
try:
|
|
94
|
+
grants = json.loads(GRANTS_FILE.read_text())
|
|
95
|
+
except json.JSONDecodeError:
|
|
96
|
+
return {"error": "Invalid grants file"}
|
|
97
|
+
|
|
98
|
+
expired: list[dict[str, Any]] = []
|
|
99
|
+
now = datetime.now(timezone.utc)
|
|
100
|
+
|
|
101
|
+
for token, grant in grants.items():
|
|
102
|
+
if is_token_expired(grant):
|
|
103
|
+
expired.append({
|
|
104
|
+
"token": token,
|
|
105
|
+
"agent": grant.get("agent_id"),
|
|
106
|
+
"resource": grant.get("resource_type"),
|
|
107
|
+
"expired_at": grant.get("expires_at")
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
"expired_tokens": expired,
|
|
112
|
+
"expired_count": len(expired),
|
|
113
|
+
"total_grants": len(grants),
|
|
114
|
+
"checked_at": now.isoformat()
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def cleanup_expired_tokens() -> dict[str, Any]:
|
|
119
|
+
"""Remove all expired tokens from active grants (TTL enforcement)."""
|
|
120
|
+
if not GRANTS_FILE.exists():
|
|
121
|
+
return {
|
|
122
|
+
"cleaned": 0,
|
|
123
|
+
"remaining": 0,
|
|
124
|
+
"message": "No grants file found"
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
grants = json.loads(GRANTS_FILE.read_text())
|
|
129
|
+
except json.JSONDecodeError:
|
|
130
|
+
return {
|
|
131
|
+
"cleaned": 0,
|
|
132
|
+
"error": "Invalid grants file"
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
original_count = len(grants)
|
|
136
|
+
expired_tokens: list[dict[str, Any]] = []
|
|
137
|
+
|
|
138
|
+
# Find and remove expired tokens
|
|
139
|
+
tokens_to_remove: list[str] = []
|
|
140
|
+
for token, grant in grants.items():
|
|
141
|
+
if is_token_expired(grant):
|
|
142
|
+
tokens_to_remove.append(token)
|
|
143
|
+
expired_tokens.append({
|
|
144
|
+
"token": token,
|
|
145
|
+
"agent": grant.get("agent_id"),
|
|
146
|
+
"resource": grant.get("resource_type"),
|
|
147
|
+
"expired_at": grant.get("expires_at")
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
for token in tokens_to_remove:
|
|
151
|
+
grants.pop(token)
|
|
152
|
+
|
|
153
|
+
# Write back cleaned grants
|
|
154
|
+
GRANTS_FILE.write_text(json.dumps(grants, indent=2))
|
|
155
|
+
|
|
156
|
+
# Log the cleanup
|
|
157
|
+
if expired_tokens:
|
|
158
|
+
log_audit("ttl_cleanup", {
|
|
159
|
+
"expired_count": len(expired_tokens),
|
|
160
|
+
"expired_tokens": expired_tokens,
|
|
161
|
+
"remaining_grants": len(grants)
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
"cleaned": len(expired_tokens),
|
|
166
|
+
"expired_tokens": expired_tokens,
|
|
167
|
+
"remaining": len(grants),
|
|
168
|
+
"original_count": original_count,
|
|
169
|
+
"cleaned_at": datetime.now(timezone.utc).isoformat()
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def main():
|
|
174
|
+
parser = argparse.ArgumentParser(
|
|
175
|
+
description="Revoke permission grant tokens and enforce TTL cleanup"
|
|
176
|
+
)
|
|
177
|
+
parser.add_argument("token", nargs="?", help="Grant token to revoke")
|
|
178
|
+
parser.add_argument("--cleanup", action="store_true",
|
|
179
|
+
help="Remove all expired tokens (TTL enforcement)")
|
|
180
|
+
parser.add_argument("--list-expired", action="store_true",
|
|
181
|
+
help="List expired tokens without removing")
|
|
182
|
+
parser.add_argument("--json", action="store_true", help="Output as JSON")
|
|
183
|
+
|
|
184
|
+
args = parser.parse_args()
|
|
185
|
+
|
|
186
|
+
# Handle --list-expired
|
|
187
|
+
if args.list_expired:
|
|
188
|
+
result = list_expired_tokens()
|
|
189
|
+
if args.json:
|
|
190
|
+
print(json.dumps(result, indent=2))
|
|
191
|
+
else:
|
|
192
|
+
expired = result.get("expired_tokens", [])
|
|
193
|
+
if expired:
|
|
194
|
+
print(f"⏰ Found {len(expired)} expired token(s):")
|
|
195
|
+
for t in expired:
|
|
196
|
+
print(f" • {t['token'][:20]}... ({t['agent']} → {t['resource']})")
|
|
197
|
+
print(f" Expired: {t['expired_at']}")
|
|
198
|
+
else:
|
|
199
|
+
print("✅ No expired tokens found")
|
|
200
|
+
print(f"\n Total grants: {result.get('total_grants', 0)}")
|
|
201
|
+
sys.exit(0)
|
|
202
|
+
|
|
203
|
+
# Handle --cleanup
|
|
204
|
+
if args.cleanup:
|
|
205
|
+
result = cleanup_expired_tokens()
|
|
206
|
+
if args.json:
|
|
207
|
+
print(json.dumps(result, indent=2))
|
|
208
|
+
else:
|
|
209
|
+
cleaned = result.get("cleaned", 0)
|
|
210
|
+
if cleaned > 0:
|
|
211
|
+
print(f"🧹 TTL Cleanup Complete")
|
|
212
|
+
print(f" Removed: {cleaned} expired token(s)")
|
|
213
|
+
for t in result.get("expired_tokens", []):
|
|
214
|
+
print(f" • {t['token'][:20]}... ({t['agent']})")
|
|
215
|
+
else:
|
|
216
|
+
print("✅ No expired tokens to clean")
|
|
217
|
+
print(f" Remaining active grants: {result.get('remaining', 0)}")
|
|
218
|
+
sys.exit(0)
|
|
219
|
+
|
|
220
|
+
# Handle single token revocation
|
|
221
|
+
if not args.token:
|
|
222
|
+
parser.print_help()
|
|
223
|
+
sys.exit(1)
|
|
224
|
+
|
|
225
|
+
result = revoke_token(args.token)
|
|
226
|
+
|
|
227
|
+
if args.json:
|
|
228
|
+
print(json.dumps(result, indent=2))
|
|
229
|
+
else:
|
|
230
|
+
if result["revoked"]:
|
|
231
|
+
grant = result["grant"]
|
|
232
|
+
print("✅ Token REVOKED")
|
|
233
|
+
print(f" Agent: {grant.get('agent_id')}")
|
|
234
|
+
print(f" Resource: {grant.get('resource_type')}")
|
|
235
|
+
else:
|
|
236
|
+
print("❌ Revocation FAILED")
|
|
237
|
+
print(f" Reason: {result.get('reason')}")
|
|
238
|
+
|
|
239
|
+
sys.exit(0 if result.get("revoked") or result.get("cleaned", 0) >= 0 else 1)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
if __name__ == "__main__":
|
|
243
|
+
main()
|