orithos-cli 0.1.0__tar.gz
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.
- orithos_cli-0.1.0/MANIFEST.in +5 -0
- orithos_cli-0.1.0/PKG-INFO +99 -0
- orithos_cli-0.1.0/README.md +71 -0
- orithos_cli-0.1.0/pyproject.toml +47 -0
- orithos_cli-0.1.0/setup.cfg +4 -0
- orithos_cli-0.1.0/src/orithos_cli/__init__.py +6 -0
- orithos_cli-0.1.0/src/orithos_cli/agent.py +78 -0
- orithos_cli-0.1.0/src/orithos_cli/agent_wizard.py +255 -0
- orithos_cli-0.1.0/src/orithos_cli/auth.py +21 -0
- orithos_cli-0.1.0/src/orithos_cli/cli.py +82 -0
- orithos_cli-0.1.0/src/orithos_cli/compliance.py +133 -0
- orithos_cli-0.1.0/src/orithos_cli/config.py +114 -0
- orithos_cli-0.1.0/src/orithos_cli/configure.py +103 -0
- orithos_cli-0.1.0/src/orithos_cli/connection.py +69 -0
- orithos_cli-0.1.0/src/orithos_cli/connection_wizard.py +129 -0
- orithos_cli-0.1.0/src/orithos_cli/discovery.py +78 -0
- orithos_cli-0.1.0/src/orithos_cli/graph.py +102 -0
- orithos_cli-0.1.0/src/orithos_cli/guardrail.py +131 -0
- orithos_cli-0.1.0/src/orithos_cli/mcp.py +170 -0
- orithos_cli-0.1.0/src/orithos_cli/output.py +178 -0
- orithos_cli-0.1.0/src/orithos_cli/probes.py +50 -0
- orithos_cli-0.1.0/src/orithos_cli/remediation.py +111 -0
- orithos_cli-0.1.0/src/orithos_cli/runtime.py +100 -0
- orithos_cli-0.1.0/src/orithos_cli/scan.py +758 -0
- orithos_cli-0.1.0/src/orithos_cli/skill.py +64 -0
- orithos_cli-0.1.0/src/orithos_cli/skillscan/__init__.py +37 -0
- orithos_cli-0.1.0/src/orithos_cli/skillscan/checks/__init__.py +28 -0
- orithos_cli-0.1.0/src/orithos_cli/skillscan/checks/credentials.py +112 -0
- orithos_cli-0.1.0/src/orithos_cli/skillscan/checks/iocs.py +95 -0
- orithos_cli-0.1.0/src/orithos_cli/skillscan/checks/manifest.py +156 -0
- orithos_cli-0.1.0/src/orithos_cli/skillscan/checks/network.py +117 -0
- orithos_cli-0.1.0/src/orithos_cli/skillscan/checks/obfuscation.py +130 -0
- orithos_cli-0.1.0/src/orithos_cli/skillscan/checks/permissions.py +108 -0
- orithos_cli-0.1.0/src/orithos_cli/skillscan/checks/shell.py +140 -0
- orithos_cli-0.1.0/src/orithos_cli/skillscan/collect.py +205 -0
- orithos_cli-0.1.0/src/orithos_cli/skillscan/model.py +99 -0
- orithos_cli-0.1.0/src/orithos_cli/skillscan/report.py +130 -0
- orithos_cli-0.1.0/src/orithos_cli/template.py +60 -0
- orithos_cli-0.1.0/src/orithos_cli/verify.py +85 -0
- orithos_cli-0.1.0/src/orithos_cli/wizard.py +314 -0
- orithos_cli-0.1.0/src/orithos_cli.egg-info/PKG-INFO +99 -0
- orithos_cli-0.1.0/src/orithos_cli.egg-info/SOURCES.txt +46 -0
- orithos_cli-0.1.0/src/orithos_cli.egg-info/dependency_links.txt +1 -0
- orithos_cli-0.1.0/src/orithos_cli.egg-info/entry_points.txt +2 -0
- orithos_cli-0.1.0/src/orithos_cli.egg-info/requires.txt +9 -0
- orithos_cli-0.1.0/src/orithos_cli.egg-info/top_level.txt +1 -0
- orithos_cli-0.1.0/tests/test_cli.py +479 -0
- orithos_cli-0.1.0/tests/test_skillscan.py +185 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: orithos-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Orithos CLI — AI agent security testing from the terminal: scan deployed agents, MCP servers, and skill/plugin artifacts.
|
|
5
|
+
Author: Orithos
|
|
6
|
+
License: Proprietary
|
|
7
|
+
Project-URL: Homepage, https://orithos.com
|
|
8
|
+
Project-URL: Documentation, https://orithos.com/docs
|
|
9
|
+
Project-URL: Repository, https://github.com/nishkmg/TraceShield
|
|
10
|
+
Keywords: ai-security,agent-security,red-team,mcp,llm,security
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Information Technology
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Security
|
|
18
|
+
Requires-Python: >=3.12
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
Requires-Dist: click>=8.1.0
|
|
21
|
+
Requires-Dist: httpx>=0.27.0
|
|
22
|
+
Requires-Dist: pydantic>=2.0.0
|
|
23
|
+
Requires-Dist: pyyaml>=6.0
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
26
|
+
Requires-Dist: pytest-mock>=3.14.0; extra == "dev"
|
|
27
|
+
Requires-Dist: types-PyYAML>=6.0; extra == "dev"
|
|
28
|
+
|
|
29
|
+
# Orithos CLI
|
|
30
|
+
|
|
31
|
+
AI agent security testing from the terminal. Scan deployed agents and MCP
|
|
32
|
+
servers against the Orithos probe catalog, vet skill/plugin artifacts before
|
|
33
|
+
they run, and pull findings, compliance reports, and evidence packages — all
|
|
34
|
+
from the command line.
|
|
35
|
+
|
|
36
|
+
## Install
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install orithos-cli
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Quick start
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# Point the CLI at your Orithos account (or use ORITHOS_API_KEY / ORITHOS_API_URL env vars)
|
|
46
|
+
orithos configure
|
|
47
|
+
|
|
48
|
+
# Vet a skill or plugin before installing it — no account needed, fully offline
|
|
49
|
+
orithos skill scan ./some-agent-skill
|
|
50
|
+
|
|
51
|
+
# Scan a registered agent
|
|
52
|
+
orithos scan run --agent-id <AGENT_ID>
|
|
53
|
+
orithos scan status <SCAN_ID>
|
|
54
|
+
orithos scan findings <SCAN_ID> --format sarif
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## `orithos skill scan`
|
|
58
|
+
|
|
59
|
+
Static vetting for agent skills, plugins, and MCP packages. Runs offline with
|
|
60
|
+
zero outbound calls; archives (zip/tar.gz) are unpacked with traversal
|
|
61
|
+
protection.
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
orithos skill scan <path|url> [--format text|json|sarif] [--fail-on critical|high|medium|low]
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Seven check classes: manifest hygiene · declared-vs-implied permissions ·
|
|
68
|
+
outbound destinations (exfil-prone endpoints, IP literals) · shell & install
|
|
69
|
+
hooks · credential access · obfuscation · known-bad signatures. Every finding
|
|
70
|
+
cites file, line, and evidence.
|
|
71
|
+
|
|
72
|
+
Exit codes: `0` clean · `1` findings at/above `--fail-on` (default `high`) ·
|
|
73
|
+
`2` error — ready for CI gates.
|
|
74
|
+
|
|
75
|
+
## Commands
|
|
76
|
+
|
|
77
|
+
- `orithos agent ...` — register and manage agent endpoints
|
|
78
|
+
- `orithos scan ...` — run scans, fetch findings (`summary`/`json`/`sarif`/`junit`)
|
|
79
|
+
- `orithos skill scan ...` — static skill/plugin vetting
|
|
80
|
+
- `orithos mcp ...` — interact with the Orithos MCP policy server
|
|
81
|
+
- `orithos compliance ...` — compliance mappings and reports
|
|
82
|
+
- `orithos verify <package>` — offline evidence-package integrity check (hashes + Merkle root)
|
|
83
|
+
- `orithos connection / discovery / graph / guardrail / probes / remediation / runtime ...` — platform workflows
|
|
84
|
+
|
|
85
|
+
## Environment variables
|
|
86
|
+
|
|
87
|
+
| Variable | Default | Description |
|
|
88
|
+
|----------|---------|-------------|
|
|
89
|
+
| `ORITHOS_API_KEY` | — | API key (`tsk_...`); overrides the config file |
|
|
90
|
+
| `ORITHOS_API_URL` | `https://api.orithos.com` | API base URL |
|
|
91
|
+
| `ORITHOS_ORG_ID` | — | Organisation ID (optional override) |
|
|
92
|
+
| `ORITHOS_TIMEOUT` | `30` | Request timeout in seconds |
|
|
93
|
+
|
|
94
|
+
## Development
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
pip install -e "apps/cli[dev]"
|
|
98
|
+
pytest -q
|
|
99
|
+
```
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Orithos CLI
|
|
2
|
+
|
|
3
|
+
AI agent security testing from the terminal. Scan deployed agents and MCP
|
|
4
|
+
servers against the Orithos probe catalog, vet skill/plugin artifacts before
|
|
5
|
+
they run, and pull findings, compliance reports, and evidence packages — all
|
|
6
|
+
from the command line.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pip install orithos-cli
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Quick start
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# Point the CLI at your Orithos account (or use ORITHOS_API_KEY / ORITHOS_API_URL env vars)
|
|
18
|
+
orithos configure
|
|
19
|
+
|
|
20
|
+
# Vet a skill or plugin before installing it — no account needed, fully offline
|
|
21
|
+
orithos skill scan ./some-agent-skill
|
|
22
|
+
|
|
23
|
+
# Scan a registered agent
|
|
24
|
+
orithos scan run --agent-id <AGENT_ID>
|
|
25
|
+
orithos scan status <SCAN_ID>
|
|
26
|
+
orithos scan findings <SCAN_ID> --format sarif
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## `orithos skill scan`
|
|
30
|
+
|
|
31
|
+
Static vetting for agent skills, plugins, and MCP packages. Runs offline with
|
|
32
|
+
zero outbound calls; archives (zip/tar.gz) are unpacked with traversal
|
|
33
|
+
protection.
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
orithos skill scan <path|url> [--format text|json|sarif] [--fail-on critical|high|medium|low]
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Seven check classes: manifest hygiene · declared-vs-implied permissions ·
|
|
40
|
+
outbound destinations (exfil-prone endpoints, IP literals) · shell & install
|
|
41
|
+
hooks · credential access · obfuscation · known-bad signatures. Every finding
|
|
42
|
+
cites file, line, and evidence.
|
|
43
|
+
|
|
44
|
+
Exit codes: `0` clean · `1` findings at/above `--fail-on` (default `high`) ·
|
|
45
|
+
`2` error — ready for CI gates.
|
|
46
|
+
|
|
47
|
+
## Commands
|
|
48
|
+
|
|
49
|
+
- `orithos agent ...` — register and manage agent endpoints
|
|
50
|
+
- `orithos scan ...` — run scans, fetch findings (`summary`/`json`/`sarif`/`junit`)
|
|
51
|
+
- `orithos skill scan ...` — static skill/plugin vetting
|
|
52
|
+
- `orithos mcp ...` — interact with the Orithos MCP policy server
|
|
53
|
+
- `orithos compliance ...` — compliance mappings and reports
|
|
54
|
+
- `orithos verify <package>` — offline evidence-package integrity check (hashes + Merkle root)
|
|
55
|
+
- `orithos connection / discovery / graph / guardrail / probes / remediation / runtime ...` — platform workflows
|
|
56
|
+
|
|
57
|
+
## Environment variables
|
|
58
|
+
|
|
59
|
+
| Variable | Default | Description |
|
|
60
|
+
|----------|---------|-------------|
|
|
61
|
+
| `ORITHOS_API_KEY` | — | API key (`tsk_...`); overrides the config file |
|
|
62
|
+
| `ORITHOS_API_URL` | `https://api.orithos.com` | API base URL |
|
|
63
|
+
| `ORITHOS_ORG_ID` | — | Organisation ID (optional override) |
|
|
64
|
+
| `ORITHOS_TIMEOUT` | `30` | Request timeout in seconds |
|
|
65
|
+
|
|
66
|
+
## Development
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
pip install -e "apps/cli[dev]"
|
|
70
|
+
pytest -q
|
|
71
|
+
```
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "orithos-cli"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Orithos CLI — AI agent security testing from the terminal: scan deployed agents, MCP servers, and skill/plugin artifacts."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.12"
|
|
11
|
+
license = {text = "Proprietary"}
|
|
12
|
+
authors = [{ name = "Orithos" }]
|
|
13
|
+
keywords = ["ai-security", "agent-security", "red-team", "mcp", "llm", "security"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Environment :: Console",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Intended Audience :: Information Technology",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.12",
|
|
21
|
+
"Topic :: Security",
|
|
22
|
+
]
|
|
23
|
+
dependencies = [
|
|
24
|
+
"click>=8.1.0",
|
|
25
|
+
"httpx>=0.27.0",
|
|
26
|
+
"pydantic>=2.0.0",
|
|
27
|
+
"pyyaml>=6.0",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://orithos.com"
|
|
32
|
+
Documentation = "https://orithos.com/docs"
|
|
33
|
+
Repository = "https://github.com/nishkmg/TraceShield"
|
|
34
|
+
|
|
35
|
+
[project.optional-dependencies]
|
|
36
|
+
dev = [
|
|
37
|
+
"pytest>=8.0.0",
|
|
38
|
+
"pytest-mock>=3.14.0",
|
|
39
|
+
"types-PyYAML>=6.0",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
[project.scripts]
|
|
43
|
+
orithos = "orithos_cli.cli:main"
|
|
44
|
+
|
|
45
|
+
[tool.setuptools.packages.find]
|
|
46
|
+
where = ["src"]
|
|
47
|
+
include = ["orithos_cli*"]
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Agent management subcommands for Orithos CLI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
import click
|
|
7
|
+
|
|
8
|
+
from orithos_cli.config import get_config
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@click.group(name="agent")
|
|
12
|
+
def agent_group() -> None:
|
|
13
|
+
"""Manage Orithos agents."""
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@agent_group.command("new")
|
|
18
|
+
@click.option("--name", default="", help="Agent name (omit for interactive wizard)")
|
|
19
|
+
def new_agent(name: str) -> None:
|
|
20
|
+
"""Interactive agent setup — configure identity, prompt, tools, memory, guardrails."""
|
|
21
|
+
from orithos_cli.agent_wizard import interactive_agent_wizard
|
|
22
|
+
|
|
23
|
+
interactive_agent_wizard()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@agent_group.command("create")
|
|
27
|
+
@click.option("--name", default="", help="Agent name (omit for interactive wizard)")
|
|
28
|
+
def create_agent(name: str) -> None:
|
|
29
|
+
"""Create a new agent — interactive wizard by default."""
|
|
30
|
+
from orithos_cli.agent_wizard import interactive_agent_wizard
|
|
31
|
+
|
|
32
|
+
interactive_agent_wizard()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@agent_group.command("list")
|
|
36
|
+
def list_agents() -> None:
|
|
37
|
+
"""List all registered agents."""
|
|
38
|
+
cfg = get_config()
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
response = httpx.get(
|
|
42
|
+
f"{cfg.api_url}/v1/agents",
|
|
43
|
+
headers=cfg.auth_headers(),
|
|
44
|
+
timeout=cfg.timeout,
|
|
45
|
+
)
|
|
46
|
+
response.raise_for_status()
|
|
47
|
+
except httpx.HTTPStatusError as exc:
|
|
48
|
+
click.echo(f"Error: {exc.response.status_code} — {exc.response.text}", err=True)
|
|
49
|
+
raise SystemExit(1)
|
|
50
|
+
except httpx.RequestError as exc:
|
|
51
|
+
click.echo(f"Connection error: {exc}", err=True)
|
|
52
|
+
raise SystemExit(1)
|
|
53
|
+
|
|
54
|
+
agents = response.json()
|
|
55
|
+
if not agents:
|
|
56
|
+
click.echo("No agents found.")
|
|
57
|
+
return
|
|
58
|
+
|
|
59
|
+
for agent in agents:
|
|
60
|
+
click.echo(f"[{agent['id']}] {agent['name']} — {agent['endpoint_url']}")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@agent_group.command("delete")
|
|
64
|
+
@click.argument("agent-id")
|
|
65
|
+
def delete_agent(agent_id: str) -> None:
|
|
66
|
+
"""Delete an agent by ID."""
|
|
67
|
+
cfg = get_config()
|
|
68
|
+
try:
|
|
69
|
+
response = httpx.delete(
|
|
70
|
+
f"{cfg.api_url}/v1/agents/{agent_id}",
|
|
71
|
+
headers=cfg.auth_headers(),
|
|
72
|
+
timeout=cfg.timeout,
|
|
73
|
+
)
|
|
74
|
+
response.raise_for_status()
|
|
75
|
+
except httpx.HTTPStatusError as exc:
|
|
76
|
+
click.echo(f"Error: {exc.response.status_code} — {exc.response.text}", err=True)
|
|
77
|
+
raise SystemExit(1)
|
|
78
|
+
click.echo(f"Agent {agent_id[:8]} deleted.")
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
"""Interactive agent wizard for Orithos CLI — 5 steps matching dashboard."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import httpx
|
|
7
|
+
import click
|
|
8
|
+
|
|
9
|
+
from orithos_cli.config import get_config, fmt_http_error
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _s(text: str = "", fg: str | None = None, dim: bool = False, bold: bool = False) -> str:
|
|
13
|
+
return click.style(text, fg=fg, dim=dim, bold=bold)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
PROVIDER_OPTIONS = [
|
|
17
|
+
("Pinecone", "Pinecone", "Vector database"),
|
|
18
|
+
("Weaviate", "Weaviate", "Vector database"),
|
|
19
|
+
("Chroma", "Chroma", "Vector database"),
|
|
20
|
+
("Milvus", "Milvus", "Vector database"),
|
|
21
|
+
("Qdrant", "Qdrant", "Vector database"),
|
|
22
|
+
("Pgvector", "Pgvector", "PostgreSQL vector extension"),
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
RAG_TYPE_OPTIONS = [
|
|
26
|
+
("Vector Database", "Vector Database", "Embedding-based retrieval"),
|
|
27
|
+
("Document Store", "Document Store", "Raw document storage"),
|
|
28
|
+
("Knowledge Graph", "Knowledge Graph", "Graph-based retrieval"),
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
GUARDRAIL_PROVIDERS = [
|
|
32
|
+
("NVIDIA NeMo Guardrails", "NeMo Guardrails", "NeMo"),
|
|
33
|
+
("Llama Guard", "Llama Guard", "Meta safety model"),
|
|
34
|
+
("Custom Classifier", "Custom Classifier", "Custom classifier endpoint"),
|
|
35
|
+
("OpenAI Moderation", "OpenAI Moderation", "OpenAI moderation API"),
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
STAGE_OPTIONS = [
|
|
39
|
+
("pre", "Pre", "Before agent inference"),
|
|
40
|
+
("post", "Post", "After agent response"),
|
|
41
|
+
("both", "Both", "Pre and post"),
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
TOOL_PARAM_TYPES = ["string", "number", "integer", "boolean", "object", "array"]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _pick(options: list[tuple[str, str, str]], prompt: str, default: int = 1) -> str:
|
|
48
|
+
for i, (_, label, desc) in enumerate(options, 1):
|
|
49
|
+
click.echo(f" [{i}] {_s(label, bold=True)} \u2014 {_s(desc, dim=True)}")
|
|
50
|
+
choice = click.prompt(prompt, type=int, default=default)
|
|
51
|
+
if choice < 1 or choice > len(options):
|
|
52
|
+
choice = default
|
|
53
|
+
return options[choice - 1][0]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _confirm(prompt: str, default: bool = True) -> bool:
|
|
57
|
+
return click.confirm(_s(prompt, dim=True), default=default)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _risk_class(name: str, desc: str) -> str:
|
|
61
|
+
low = (name + " " + desc).lower()
|
|
62
|
+
critical_kw = ["refund", "payment", "transfer", "charge", "delete", "remove", "purge",
|
|
63
|
+
"destroy", "create_user", "add_user", "grant", "elevate", "permission"]
|
|
64
|
+
write_kw = critical_kw + ["send", "message", "email", "notify", "write", "create",
|
|
65
|
+
"update", "post", "admin", "access", "install", "deploy"]
|
|
66
|
+
if any(w in low for w in critical_kw):
|
|
67
|
+
return "write"
|
|
68
|
+
if any(w in low for w in write_kw):
|
|
69
|
+
return "write"
|
|
70
|
+
return "read"
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def interactive_agent_wizard() -> None:
|
|
74
|
+
cfg = get_config()
|
|
75
|
+
|
|
76
|
+
click.echo()
|
|
77
|
+
click.echo(_s("\u2726 Orithos Agent Wizard", fg="green", bold=True))
|
|
78
|
+
click.echo(_s(" Configure your agent step by step.", dim=True))
|
|
79
|
+
click.echo()
|
|
80
|
+
|
|
81
|
+
# === Step 1: Identity ===
|
|
82
|
+
click.echo(_s("Step 1/5: Identity", fg="green"))
|
|
83
|
+
name = click.prompt(" Agent name", default="")
|
|
84
|
+
if not name:
|
|
85
|
+
click.echo(_s(" Agent name is required.", fg="red"))
|
|
86
|
+
raise SystemExit(1)
|
|
87
|
+
version = click.prompt(" Version", default="1.0")
|
|
88
|
+
description = click.prompt(" Description", default="")
|
|
89
|
+
click.echo()
|
|
90
|
+
|
|
91
|
+
# === Step 2: System Prompt ===
|
|
92
|
+
click.echo(_s("Step 2/5: System Prompt", fg="green"))
|
|
93
|
+
click.echo(_s(" Paste the system prompt. Enter '.' on a blank line to finish.", dim=True))
|
|
94
|
+
lines: list[str] = []
|
|
95
|
+
while True:
|
|
96
|
+
line = input()
|
|
97
|
+
if line == ".":
|
|
98
|
+
break
|
|
99
|
+
lines.append(line)
|
|
100
|
+
system_prompt = "\n".join(lines).strip()
|
|
101
|
+
|
|
102
|
+
if system_prompt:
|
|
103
|
+
click.echo(_s(" Analysing prompt...", dim=True), nl=False)
|
|
104
|
+
try:
|
|
105
|
+
ar = httpx.post(
|
|
106
|
+
f"{cfg.api_url}/v1/agents/analyze-prompt",
|
|
107
|
+
json={"text": system_prompt},
|
|
108
|
+
headers=cfg.auth_headers(),
|
|
109
|
+
timeout=30,
|
|
110
|
+
)
|
|
111
|
+
if ar.status_code == 200:
|
|
112
|
+
analysis = ar.json()
|
|
113
|
+
constraints = analysis.get("constraints_count", 0)
|
|
114
|
+
identifiers = analysis.get("identifiers_count", 0)
|
|
115
|
+
probes = analysis.get("summary", {}).get("estimated_total_probes", 0)
|
|
116
|
+
leakage = analysis.get("leakage_risk", "low")
|
|
117
|
+
click.echo(_s(f" \u2713", fg="green"))
|
|
118
|
+
click.echo(_s(f" \u21b3 {constraints} constraints \u00b7 {identifiers} identifiers \u00b7 ~{probes} probes \u00b7 leakage: {leakage}", dim=True))
|
|
119
|
+
else:
|
|
120
|
+
click.echo(_s(" \u2716 (analysis unavailable)", fg="yellow"))
|
|
121
|
+
except httpx.RequestError:
|
|
122
|
+
click.echo(_s(" \u2716 (analysis unavailable)", fg="yellow"))
|
|
123
|
+
click.echo()
|
|
124
|
+
|
|
125
|
+
# === Step 3: Tools ===
|
|
126
|
+
click.echo(_s("Step 3/5: Tools (optional)", fg="green"))
|
|
127
|
+
tools: list[dict] = []
|
|
128
|
+
if _confirm(" Add tools?"):
|
|
129
|
+
while True:
|
|
130
|
+
t_name = click.prompt(" Tool name", default="")
|
|
131
|
+
if not t_name:
|
|
132
|
+
break
|
|
133
|
+
t_desc = click.prompt(" Description", default="")
|
|
134
|
+
params: list[dict] = []
|
|
135
|
+
click.echo(_s(" Parameters (enter blank name to stop):", dim=True))
|
|
136
|
+
pi = 1
|
|
137
|
+
while True:
|
|
138
|
+
p_name = click.prompt(f" param {pi} name", default="")
|
|
139
|
+
if not p_name:
|
|
140
|
+
break
|
|
141
|
+
p_type = click.prompt(f" param {pi} type", type=click.Choice(TOOL_PARAM_TYPES), default="string", show_choices=False)
|
|
142
|
+
params.append({"name": p_name, "type": p_type})
|
|
143
|
+
pi += 1
|
|
144
|
+
|
|
145
|
+
access = _risk_class(t_name, t_desc)
|
|
146
|
+
tools.append({
|
|
147
|
+
"name": t_name,
|
|
148
|
+
"description": t_desc,
|
|
149
|
+
"parameters": params,
|
|
150
|
+
"access_level": access,
|
|
151
|
+
})
|
|
152
|
+
click.echo(f" Risk: {_s(access, dim=True)}")
|
|
153
|
+
if not _confirm(" Add another tool?", default=False):
|
|
154
|
+
break
|
|
155
|
+
click.echo()
|
|
156
|
+
|
|
157
|
+
# === Step 4: Memory & Guardrails ===
|
|
158
|
+
click.echo(_s("Step 4/5: Memory & Guardrails (optional)", fg="green"))
|
|
159
|
+
memory_sources: list[dict] = []
|
|
160
|
+
guardrails: list[dict] = []
|
|
161
|
+
|
|
162
|
+
if _confirm(" Add RAG source?"):
|
|
163
|
+
while True:
|
|
164
|
+
rag_name = click.prompt(" Source name", default="knowledge-base")
|
|
165
|
+
rag_provider = _pick(PROVIDER_OPTIONS, " Provider")
|
|
166
|
+
rag_type = _pick(RAG_TYPE_OPTIONS, " Type", default=1)
|
|
167
|
+
rag_docs = click.prompt(" Document count", type=int, default=0)
|
|
168
|
+
memory_sources.append({
|
|
169
|
+
"name": rag_name,
|
|
170
|
+
"type": rag_type,
|
|
171
|
+
"provider": rag_provider,
|
|
172
|
+
"doc_count": rag_docs,
|
|
173
|
+
"risk": "supply_chain",
|
|
174
|
+
})
|
|
175
|
+
if not _confirm(" Add another RAG source?", default=False):
|
|
176
|
+
break
|
|
177
|
+
|
|
178
|
+
if _confirm(" Add guardrail?"):
|
|
179
|
+
while True:
|
|
180
|
+
g_name = click.prompt(" Guardrail name", default="")
|
|
181
|
+
g_provider = _pick(GUARDRAIL_PROVIDERS, " Provider")
|
|
182
|
+
g_stage = _pick(STAGE_OPTIONS, " Stage")
|
|
183
|
+
guardrails.append({
|
|
184
|
+
"name": g_name,
|
|
185
|
+
"type": g_provider,
|
|
186
|
+
"stage": g_stage,
|
|
187
|
+
"enabled": True,
|
|
188
|
+
})
|
|
189
|
+
if not _confirm(" Add another guardrail?", default=False):
|
|
190
|
+
break
|
|
191
|
+
click.echo()
|
|
192
|
+
|
|
193
|
+
# === Step 5: Confirm & Save ===
|
|
194
|
+
click.echo(_s("Step 5/5: Confirm & Save", fg="green"))
|
|
195
|
+
tool_count = len(tools)
|
|
196
|
+
tool_names = ", ".join(t.get("name", "?") for t in tools[:3])
|
|
197
|
+
if tool_count > 3:
|
|
198
|
+
tool_names += f" +{tool_count - 3} more"
|
|
199
|
+
prompt_preview = system_prompt[:60].replace("\n", "\\n") + ("..." if len(system_prompt) > 60 else "")
|
|
200
|
+
prompt_tokens = max(1, len(system_prompt) // 4)
|
|
201
|
+
|
|
202
|
+
click.echo(_s("\u250c" + "\u2500" * 47 + "\u2510", dim=True))
|
|
203
|
+
for line in [
|
|
204
|
+
f" Name: {name}",
|
|
205
|
+
f" Version: {version}",
|
|
206
|
+
f" Description: {description[:40] or '(none)'}",
|
|
207
|
+
f" Prompt: {prompt_tokens} tokens",
|
|
208
|
+
f" Tools: {tool_names or 'none'}",
|
|
209
|
+
f" RAG sources: {len(memory_sources)}",
|
|
210
|
+
f" Guardrails: {len(guardrails)}",
|
|
211
|
+
]:
|
|
212
|
+
click.echo(_s(f"\u2502 {line:<45}\u2502", dim=True))
|
|
213
|
+
click.echo(_s("\u2514" + "\u2500" * 47 + "\u2518", dim=True))
|
|
214
|
+
click.echo()
|
|
215
|
+
|
|
216
|
+
if not _confirm(" Save agent?", default=True):
|
|
217
|
+
click.echo(_s(" Cancelled.", fg="yellow"))
|
|
218
|
+
return
|
|
219
|
+
|
|
220
|
+
payload: dict = {
|
|
221
|
+
"name": name,
|
|
222
|
+
"version": version,
|
|
223
|
+
"endpoint_url": "",
|
|
224
|
+
}
|
|
225
|
+
if description:
|
|
226
|
+
payload["description"] = description
|
|
227
|
+
if system_prompt:
|
|
228
|
+
payload["system_prompt"] = system_prompt
|
|
229
|
+
if tools:
|
|
230
|
+
payload["tools_config"] = tools
|
|
231
|
+
if memory_sources:
|
|
232
|
+
payload["memory_sources"] = memory_sources
|
|
233
|
+
if guardrails:
|
|
234
|
+
payload["guardrails"] = guardrails
|
|
235
|
+
|
|
236
|
+
try:
|
|
237
|
+
r = httpx.post(
|
|
238
|
+
f"{cfg.api_url}/v1/agents",
|
|
239
|
+
json=payload,
|
|
240
|
+
headers=cfg.auth_headers(),
|
|
241
|
+
timeout=cfg.timeout,
|
|
242
|
+
)
|
|
243
|
+
r.raise_for_status()
|
|
244
|
+
except httpx.HTTPStatusError as exc:
|
|
245
|
+
click.echo(_s(f"Error creating agent: {fmt_http_error(exc)}", fg="red"), err=True)
|
|
246
|
+
raise SystemExit(1)
|
|
247
|
+
except httpx.RequestError as exc:
|
|
248
|
+
click.echo(_s(f"Connection error: {exc}", fg="red"), err=True)
|
|
249
|
+
raise SystemExit(1)
|
|
250
|
+
|
|
251
|
+
data = r.json()
|
|
252
|
+
aid = data.get("id", "?")[:8]
|
|
253
|
+
click.echo(_s(f"\u2726 Agent created: {aid}", fg="green", bold=True))
|
|
254
|
+
click.echo(_s(f" Name: {data.get('name', '?')}", dim=True))
|
|
255
|
+
click.echo(_s(f" Tools: {len(tools)} RAG: {len(memory_sources)} Guardrails: {len(guardrails)}", dim=True))
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Authentication and request signing utilities for Orithos CLI."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import hmac
|
|
5
|
+
import hashlib
|
|
6
|
+
import time
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def sign_request(method: str, path: str, body: bytes = b"") -> dict:
|
|
10
|
+
"""Add HMAC-SHA256 signature headers to a request."""
|
|
11
|
+
key = os.environ.get("TRACESHIELD_REQUEST_SIGNING_KEY", "")
|
|
12
|
+
if not key:
|
|
13
|
+
return {} # No signing in dev mode
|
|
14
|
+
|
|
15
|
+
timestamp = int(time.time())
|
|
16
|
+
payload = f"{timestamp}.{method}.{path}.{hashlib.sha256(body).hexdigest()}".encode()
|
|
17
|
+
signature = hmac.new(key.encode(), payload, hashlib.sha256).hexdigest()
|
|
18
|
+
return {
|
|
19
|
+
"X-Orithos-Timestamp": str(timestamp),
|
|
20
|
+
"X-Orithos-Signature": f"t={timestamp},v1={signature}",
|
|
21
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Orithos CLI — main entry point."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from orithos_cli.configure import configure
|
|
6
|
+
from orithos_cli.agent import agent_group
|
|
7
|
+
from orithos_cli.compliance import compliance_group
|
|
8
|
+
from orithos_cli.connection import connection_group
|
|
9
|
+
from orithos_cli.discovery import discovery_group
|
|
10
|
+
from orithos_cli.graph import graph_group
|
|
11
|
+
from orithos_cli.guardrail import guardrail_group
|
|
12
|
+
from orithos_cli.mcp import mcp_group
|
|
13
|
+
from orithos_cli.probes import probes_group
|
|
14
|
+
from orithos_cli.remediation import remediation_group
|
|
15
|
+
from orithos_cli.runtime import alerting_group, runtime_group
|
|
16
|
+
from orithos_cli.scan import scan_group
|
|
17
|
+
from orithos_cli.skill import skill_group
|
|
18
|
+
from orithos_cli.template import template_group
|
|
19
|
+
from orithos_cli.verify import verify_package_file
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@click.group()
|
|
23
|
+
@click.version_option(version="0.1.0", prog_name="orithos")
|
|
24
|
+
def main() -> None:
|
|
25
|
+
"""Orithos CLI — AI Agent Security Testing Platform.
|
|
26
|
+
|
|
27
|
+
Quick start: orithos configure
|
|
28
|
+
|
|
29
|
+
Environment variables (override config file):
|
|
30
|
+
ORITHOS_API_KEY API key (tsk_...)
|
|
31
|
+
ORITHOS_API_URL API base URL (overrides default https://api.orithos.com)
|
|
32
|
+
ORITHOS_ORG_ID Organisation ID (optional override)
|
|
33
|
+
ORITHOS_TIMEOUT Request timeout in seconds (default: 30)
|
|
34
|
+
"""
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@main.command("verify")
|
|
39
|
+
@click.argument("package_file", type=click.Path(exists=True, dir_okay=False))
|
|
40
|
+
def verify_cmd(package_file: str) -> None:
|
|
41
|
+
"""Verify an evidence package offline (hashes + Merkle root).
|
|
42
|
+
|
|
43
|
+
Recomputes every artifact hash and the Merkle root from the package
|
|
44
|
+
content and compares against the declared manifest. No network access,
|
|
45
|
+
no Orithos contact — independent verifiability for auditors.
|
|
46
|
+
"""
|
|
47
|
+
from pathlib import Path
|
|
48
|
+
|
|
49
|
+
result = verify_package_file(Path(package_file))
|
|
50
|
+
if result["ok"]:
|
|
51
|
+
click.echo("OK — package integrity verified")
|
|
52
|
+
else:
|
|
53
|
+
click.echo("FAIL — package integrity check failed")
|
|
54
|
+
for err in result["errors"]:
|
|
55
|
+
click.echo(f" - {err}")
|
|
56
|
+
for art in result.get("artifacts", []):
|
|
57
|
+
click.echo(f" {art['name']}: {art['sha256'][:16]}…")
|
|
58
|
+
if result.get("merkle_root"):
|
|
59
|
+
click.echo(f" merkle root: {result['merkle_root'][:16]}…")
|
|
60
|
+
if not result["ok"]:
|
|
61
|
+
raise SystemExit(1)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
main.add_command(configure)
|
|
65
|
+
main.add_command(agent_group)
|
|
66
|
+
main.add_command(scan_group)
|
|
67
|
+
main.add_command(remediation_group)
|
|
68
|
+
main.add_command(guardrail_group)
|
|
69
|
+
main.add_command(graph_group)
|
|
70
|
+
main.add_command(runtime_group)
|
|
71
|
+
main.add_command(alerting_group)
|
|
72
|
+
main.add_command(discovery_group)
|
|
73
|
+
main.add_command(connection_group)
|
|
74
|
+
main.add_command(template_group)
|
|
75
|
+
main.add_command(probes_group)
|
|
76
|
+
main.add_command(compliance_group)
|
|
77
|
+
main.add_command(mcp_group)
|
|
78
|
+
main.add_command(skill_group)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
if __name__ == "__main__":
|
|
82
|
+
main()
|