Skip to content

Commit 139afe0

Browse files
committed
feat(cli): standardize --json output and implement security sanitization for error logs and audit events
1 parent e8464f8 commit 139afe0

File tree

5 files changed

+221
-176
lines changed

5 files changed

+221
-176
lines changed

packages/agent-mesh/src/agentmesh/cli/main.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,6 @@ def register(agent_dir: str, name: str = None, output_json: bool = False):
313313
"status": "success",
314314
"agent_name": agent_name,
315315
"did": identity.did,
316-
"public_key": identity.public_key,
317316
"identity_file": str(identity_file)
318317
}, indent=2))
319318
else:
@@ -457,9 +456,16 @@ def audit(agent: str, limit: int, fmt: str, output_json: bool):
457456
entries = [e for e in entries if e["agent"] == agent]
458457

459458
entries = entries[:limit]
459+
460+
# Sanitize entries for JSON output to prevent information leakage/injection
461+
allowed_keys = {"timestamp", "agent", "action", "status"}
462+
sanitized_entries = [
463+
{k: v for k, v in e.items() if k in allowed_keys}
464+
for e in entries
465+
]
460466

461467
if output_json or fmt == "json":
462-
click.echo(json.dumps(entries, indent=2))
468+
click.echo(json.dumps(sanitized_entries, indent=2))
463469
else:
464470
console.print("\n[bold blue]📋 Audit Log[/bold blue]\n")
465471
table = Table(box=box.SIMPLE)

packages/agent-os/modules/control-plane/acp-cli.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -170,16 +170,24 @@ def cmd_audit_show(args, control_plane):
170170
events = recorder.get_recent_events(limit=args.limit or 10)
171171

172172
if args.format == "json" or getattr(args, "json", False):
173-
print(json.dumps(events, indent=2))
173+
# Sanitize events to prevent information disclosure
174+
allowed_keys = {"timestamp", "event_type", "agent_id", "status"}
175+
sanitized_events = [
176+
{k: v for k, v in event.items() if k in allowed_keys}
177+
for event in events
178+
]
179+
print(json.dumps(sanitized_events, indent=2))
174180
else:
175181
print(f"Recent Audit Events (last {len(events)}):")
176182
for event in events:
177183
print(f" [{event.get('timestamp')}] {event.get('event_type')}: {event.get('agent_id')}")
178184
except Exception as e:
185+
is_known = isinstance(e, (ValueError, PermissionError))
186+
msg = str(e) if is_known else "Failed to retrieve audit logs"
179187
if getattr(args, "json", False):
180-
print(json.dumps({"error": str(e)}, indent=2))
188+
print(json.dumps({"error": msg, "type": e.__class__.__name__ if is_known else "InternalError"}, indent=2))
181189
else:
182-
print(f"Error: {e}")
190+
print(f"Error: {msg}")
183191

184192

185193
def cmd_benchmark_run(args):
@@ -243,10 +251,12 @@ def main():
243251
return 1
244252

245253
except Exception as e:
254+
is_known = isinstance(e, (ValueError, PermissionError, FileNotFoundError))
255+
msg = str(e) if is_known else "An internal error occurred"
246256
if getattr(args, "json", False):
247-
print(json.dumps({"error": str(e)}, indent=2))
257+
print(json.dumps({"error": msg, "type": e.__class__.__name__ if is_known else "InternalError"}, indent=2))
248258
else:
249-
print(f"Error: {e}")
259+
print(f"Error: {msg}")
250260
return 1
251261

252262
return 0

packages/agent-os/src/agent_os/cli/__init__.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1220,10 +1220,19 @@ def main() -> int:
12201220
except KeyboardInterrupt:
12211221
return 130
12221222
except Exception as e:
1223+
# Sanitize exception message to avoid leaking internal details
1224+
# For public output, we prefer generic messages for unexpected exceptions
1225+
is_known_error = isinstance(e, (FileNotFoundError, ValueError, PermissionError))
1226+
error_msg = str(e) if is_known_error else "An internal error occurred. Use AGENTOS_DEBUG=1 for details."
1227+
12231228
if getattr(args, "json", False) or (hasattr(args, "format") and args.format == "json"):
1224-
print(json.dumps({"error": str(e)}, indent=2))
1229+
print(json.dumps({
1230+
"status": "error",
1231+
"message": error_msg,
1232+
"error_type": e.__class__.__name__ if is_known_error else "InternalError"
1233+
}, indent=2))
12251234
else:
1226-
print(format_error(str(e)))
1235+
print(format_error(error_msg))
12271236
if os.environ.get("AGENTOS_DEBUG"):
12281237
import traceback
12291238
traceback.print_exc()

packages/agent-os/src/agent_os/cli/mcp_scan.py

Lines changed: 92 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -159,97 +159,106 @@ def main(argv: List[str] | None = None) -> int:
159159

160160
output_format = "json" if getattr(args, "json", False) or getattr(args, "format", "table") == "json" else "table"
161161

162-
if args.command == "scan":
163-
findings = scan_config(config_path, args.server)
164-
165-
if args.severity:
166-
findings = [f for f in findings if f.severity == args.severity or f.severity == "critical"]
162+
try:
163+
if args.command == "scan":
164+
findings = scan_config(config_path, args.server)
165+
166+
if args.severity:
167+
findings = [f for f in findings if f.severity == args.severity or f.severity == "critical"]
167168

168-
if output_format == "json":
169-
print(json.dumps([f.to_dict() for f in findings], indent=2))
170-
elif output_format == "table":
171-
table = Table(title=f"Security Scan: {args.config}")
172-
table.add_column("Server", style="cyan")
173-
table.add_column("Severity", style="bold")
174-
table.add_column("Category", style="dim")
175-
table.add_column("Finding")
176-
177-
for f in findings:
178-
sev_color = "red" if f.severity == "critical" else "yellow"
179-
table.add_row(f.server, f"[{sev_color}]{f.severity.upper()}[/{sev_color}]", f.category, f.message)
180-
181-
console.print(table)
182-
183-
return 1 if any(f.severity == "critical" for f in findings) else 0
169+
if output_format == "json":
170+
print(json.dumps([f.to_dict() for f in findings], indent=2))
171+
elif output_format == "table":
172+
table = Table(title=f"Security Scan: {args.config}")
173+
table.add_column("Server", style="cyan")
174+
table.add_column("Severity", style="bold")
175+
table.add_column("Category", style="dim")
176+
table.add_column("Finding")
177+
178+
for f in findings:
179+
sev_color = "red" if f.severity == "critical" else "yellow"
180+
table.add_row(f.server, f"[{sev_color}]{f.severity.upper()}[/{sev_color}]", f.category, f.message)
181+
182+
console.print(table)
183+
184+
return 1 if any(f.severity == "critical" for f in findings) else 0
184185

185-
elif args.command == "fingerprint":
186-
fingerprints = get_fingerprints(config_path)
187-
188-
if args.compare:
189-
with open(args.compare) as f:
190-
saved = json.load(f)
186+
elif args.command == "fingerprint":
187+
fingerprints = get_fingerprints(config_path)
191188

192-
diffs = {}
193-
for name, h in fingerprints.items():
194-
if name not in saved:
195-
diffs[name] = "new"
196-
elif saved[name] != h:
197-
diffs[name] = "changed"
189+
if args.compare:
190+
with open(args.compare) as f:
191+
saved = json.load(f)
192+
193+
diffs = {}
194+
for name, h in fingerprints.items():
195+
if name not in saved:
196+
diffs[name] = "new"
197+
elif saved[name] != h:
198+
diffs[name] = "changed"
199+
200+
if output_format == "json":
201+
print(json.dumps({"current": fingerprints, "diffs": diffs}, indent=2))
202+
else:
203+
print(f"Comparison results for {args.config}:")
204+
for name, status in diffs.items():
205+
print(f" {name}: {status}")
206+
if not diffs:
207+
print(" Identical fingerprints.")
208+
209+
elif args.output:
210+
with open(args.output, "w") as f:
211+
json.dump(fingerprints, f, indent=2)
212+
if output_format != "json":
213+
print(f"Fingerprints saved to {args.output}")
214+
else:
215+
print(json.dumps({"status": "success", "file": args.output}, indent=2))
198216

199-
if output_format == "json":
200-
print(json.dumps({"current": fingerprints, "diffs": diffs}, indent=2))
201-
else:
202-
print(f"Comparison results for {args.config}:")
203-
for name, status in diffs.items():
204-
print(f" {name}: {status}")
205-
if not diffs:
206-
print(" Identical fingerprints.")
207-
208-
elif args.output:
209-
with open(args.output, "w") as f:
210-
json.dump(fingerprints, f, indent=2)
211-
if output_format != "json":
212-
print(f"Fingerprints saved to {args.output}")
213217
else:
214-
print(json.dumps({"status": "success", "file": args.output}, indent=2))
215-
216-
else:
217-
if output_format == "json":
218-
print(json.dumps(fingerprints, indent=2))
218+
if output_format == "json":
219+
print(json.dumps(fingerprints, indent=2))
220+
else:
221+
for name, h in fingerprints.items():
222+
print(f"{name:20} {h}")
223+
224+
elif args.command == "report":
225+
findings = scan_config(config_path)
226+
fingerprints = get_fingerprints(config_path)
227+
228+
report = {
229+
"config": str(config_path),
230+
"summary": {
231+
"total_servers": len(fingerprints),
232+
"total_findings": len(findings),
233+
"critical": len([f for f in findings if f.severity == "critical"]),
234+
"warning": len([f for f in findings if f.severity == "warning"])
235+
},
236+
"findings": [f.to_dict() for f in findings],
237+
"fingerprints": fingerprints
238+
}
239+
240+
if output_format == "json" or getattr(args, "format", "markdown") == "json":
241+
print(json.dumps(report, indent=2))
219242
else:
220-
for name, h in fingerprints.items():
221-
print(f"{name:20} {h}")
243+
# Simple markdown report
244+
print(f"# Security Report: {args.config}")
245+
print()
246+
print(f"- Total Servers: {report['summary']['total_servers']}")
247+
print(f"- Total Findings: {report['summary']['total_findings']}")
248+
print()
249+
print("## Findings")
250+
for f in findings:
251+
print(f"- **{f.server}** ({f.severity.upper()}): {f.message}")
222252

223-
elif args.command == "report":
224-
findings = scan_config(config_path)
225-
fingerprints = get_fingerprints(config_path)
226-
227-
report = {
228-
"config": str(config_path),
229-
"summary": {
230-
"total_servers": len(fingerprints),
231-
"total_findings": len(findings),
232-
"critical": len([f for f in findings if f.severity == "critical"]),
233-
"warning": len([f for f in findings if f.severity == "warning"])
234-
},
235-
"findings": [f.to_dict() for f in findings],
236-
"fingerprints": fingerprints
237-
}
238-
239-
if output_format == "json" or getattr(args, "format", "markdown") == "json":
240-
print(json.dumps(report, indent=2))
253+
return 0
254+
except Exception as e:
255+
is_known = isinstance(e, (FileNotFoundError, ValueError, yaml.YAMLError))
256+
msg = str(e) if is_known else "An error occurred during scanning"
257+
if output_format == "json":
258+
print(json.dumps({"status": "error", "message": msg, "type": e.__class__.__name__ if is_known else "InternalError"}, indent=2))
241259
else:
242-
# Simple markdown report
243-
print(f"# Security Report: {args.config}")
244-
print()
245-
print(f"- Total Servers: {report['summary']['total_servers']}")
246-
print(f"- Total Findings: {report['summary']['total_findings']}")
247-
print()
248-
print("## Findings")
249-
for f in findings:
250-
print(f"- **{f.server}** ({f.severity.upper()}): {f.message}")
251-
252-
return 0
260+
print(f"Error: {msg}")
261+
return 1
253262

254263

255264
if __name__ == "__main__":

0 commit comments

Comments
 (0)