superuser 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.
- superuser-0.1.0/PKG-INFO +215 -0
- superuser-0.1.0/README.md +191 -0
- superuser-0.1.0/pyproject.toml +39 -0
- superuser-0.1.0/setup.cfg +4 -0
- superuser-0.1.0/src/superuser/__init__.py +5 -0
- superuser-0.1.0/src/superuser/__main__.py +62 -0
- superuser-0.1.0/src/superuser/audit.py +70 -0
- superuser-0.1.0/src/superuser/authz.py +155 -0
- superuser-0.1.0/src/superuser/billing.py +91 -0
- superuser-0.1.0/src/superuser/config.py +314 -0
- superuser-0.1.0/src/superuser/guard.py +152 -0
- superuser-0.1.0/src/superuser/security.py +170 -0
- superuser-0.1.0/src/superuser/server.py +138 -0
- superuser-0.1.0/src/superuser/tools/__init__.py +1 -0
- superuser-0.1.0/src/superuser/tools/github.py +90 -0
- superuser-0.1.0/src/superuser/tools/shell.py +61 -0
- superuser-0.1.0/src/superuser/tools/system.py +69 -0
- superuser-0.1.0/src/superuser.egg-info/PKG-INFO +215 -0
- superuser-0.1.0/src/superuser.egg-info/SOURCES.txt +22 -0
- superuser-0.1.0/src/superuser.egg-info/dependency_links.txt +1 -0
- superuser-0.1.0/src/superuser.egg-info/entry_points.txt +2 -0
- superuser-0.1.0/src/superuser.egg-info/requires.txt +18 -0
- superuser-0.1.0/src/superuser.egg-info/top_level.txt +1 -0
- superuser-0.1.0/tests/test_guard.py +181 -0
superuser-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: superuser
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A permission-gated superuser (sysadmin) MCP agent with Cisco AI Defense runtime guardrails.
|
|
5
|
+
Author: Super
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Keywords: mcp,agent,sysadmin,superuser,cisco,ai-defense,guardrails
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Requires-Dist: mcp<2,>=1.2.0
|
|
11
|
+
Requires-Dist: psutil>=5.9
|
|
12
|
+
Requires-Dist: PyYAML>=6.0
|
|
13
|
+
Requires-Dist: requests>=2.31
|
|
14
|
+
Provides-Extra: security
|
|
15
|
+
Requires-Dist: cisco-aidefense-sdk>=2.1.0; extra == "security"
|
|
16
|
+
Provides-Extra: billing
|
|
17
|
+
Requires-Dist: stripe>=11.0; extra == "billing"
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
20
|
+
Requires-Dist: ruff>=0.6; extra == "dev"
|
|
21
|
+
Provides-Extra: all
|
|
22
|
+
Requires-Dist: cisco-aidefense-sdk>=2.1.0; extra == "all"
|
|
23
|
+
Requires-Dist: stripe>=11.0; extra == "all"
|
|
24
|
+
|
|
25
|
+
# Super
|
|
26
|
+
|
|
27
|
+
<!-- mcp-name: io.github.fitzyracing1/superuser -->
|
|
28
|
+
|
|
29
|
+
A **permission-gated superuser agent** exposed over the [Model Context Protocol
|
|
30
|
+
(MCP)](https://modelcontextprotocol.io). It gives an AI agent real
|
|
31
|
+
system-administration powers on this machine — running shell commands, checking
|
|
32
|
+
system state, and acting on GitHub — while wrapping every action in guardrails:
|
|
33
|
+
a dangerous-command denylist, a human-approval gate for risky operations,
|
|
34
|
+
least-privilege GitHub access, an append-only audit log, and **Cisco AI Defense**
|
|
35
|
+
runtime inspection of every tool call and response.
|
|
36
|
+
|
|
37
|
+
The design goal is the version of a "superuser agent" you can actually ship:
|
|
38
|
+
powerful, but safe, auditable, and compatible with enterprise guardrails.
|
|
39
|
+
|
|
40
|
+
> **Landing site:** https://fitzyracing1.github.io/super-landing/ (source: [fitzyracing1/super-landing](https://github.com/fitzyracing1/super-landing)).
|
|
41
|
+
> The previous Pages site in this repo is preserved at tag `site-v1` / branch `site-archive`.
|
|
42
|
+
|
|
43
|
+
## Why it's built this way
|
|
44
|
+
|
|
45
|
+
An agent that can do *anything* as root is a liability, not a product. Buyers
|
|
46
|
+
(and marketplace security reviews) expect scoping, approvals, and an audit
|
|
47
|
+
trail. So capabilities here always pass through a single choke point — the
|
|
48
|
+
`Guard` — which enforces, in order:
|
|
49
|
+
|
|
50
|
+
1. **Cisco AI Defense** inspects the tool call (can block).
|
|
51
|
+
2. **Cisco Duo Agentic Identity** authorizes the tool call per-policy (can block).
|
|
52
|
+
3. **Approval gate** — anything at or above `approval_required_at` (default
|
|
53
|
+
`MEDIUM`) needs an explicit `confirm=true` from a human.
|
|
54
|
+
4. The operation runs.
|
|
55
|
+
5. **Cisco AI Defense** inspects the response before it is returned (catches
|
|
56
|
+
data leakage).
|
|
57
|
+
6. **Stripe** meters the successful call (best-effort; never blocks).
|
|
58
|
+
7. The outcome is written to the **audit log** as
|
|
59
|
+
`human owner -> agent -> tool -> action -> outcome`.
|
|
60
|
+
|
|
61
|
+
This mirrors the enterprise model: your app owns scoping + approvals + local
|
|
62
|
+
audit; **Cisco AI Defense** provides runtime threat inspection at the MCP layer;
|
|
63
|
+
**Cisco Duo Agentic Identity** provides per-tool-call authorization tied to an
|
|
64
|
+
accountable human owner; and **Stripe** turns metered tool calls into revenue.
|
|
65
|
+
|
|
66
|
+
### Cisco Duo Agentic Identity (authorization)
|
|
67
|
+
|
|
68
|
+
Duo follows a "Duo decides, the gateway enforces" model. This agent registers as
|
|
69
|
+
a non-human identity, authenticates via the OAuth 2.1 **client-credentials**
|
|
70
|
+
grant (with an RFC 8707 resource indicator), and authorizes every tool call
|
|
71
|
+
either by matching the tool's required **scope** against what Duo granted, or by
|
|
72
|
+
calling an explicit Duo **decision endpoint**. Set `DUO_ENABLED=true` plus
|
|
73
|
+
`DUO_TOKEN_URL` / `DUO_CLIENT_ID` / `DUO_CLIENT_SECRET` (see `.env.example`). When
|
|
74
|
+
Duo is not configured, calls are allowed but flagged `governed: false`; in
|
|
75
|
+
`enforce` mode authorization errors fail closed.
|
|
76
|
+
|
|
77
|
+
### Stripe usage-based billing (monetization)
|
|
78
|
+
|
|
79
|
+
Each authorized, successful tool call emits a Stripe **meter event**
|
|
80
|
+
(v2 billing meter events API), so the agent can be sold per-action. Configure a
|
|
81
|
+
meter in Stripe, then set `STRIPE_ENABLED=true`, `STRIPE_API_KEY`,
|
|
82
|
+
`STRIPE_CUSTOMER_ID`, and `STRIPE_METER_EVENT_NAME`. Per-tool weights let
|
|
83
|
+
higher-risk actions cost more. Billing is best-effort: metering failures are
|
|
84
|
+
logged and never block or fail the tool call (results are annotated
|
|
85
|
+
`billed: true|false`).
|
|
86
|
+
|
|
87
|
+
## Tools
|
|
88
|
+
|
|
89
|
+
| Tool | Risk | Notes |
|
|
90
|
+
|------|------|-------|
|
|
91
|
+
| `system_status` | LOW | Host, CPU, memory, uptime |
|
|
92
|
+
| `disk_usage` | LOW | Per-partition disk usage |
|
|
93
|
+
| `list_processes` | LOW | Top processes by memory/cpu |
|
|
94
|
+
| `run_command` | HIGH* | Shell command; denylist + approval + audit |
|
|
95
|
+
| `github_get_repo` | LOW | Repo metadata (allowlisted repos only) |
|
|
96
|
+
| `github_list_issues` | LOW | Issues for an allowlisted repo |
|
|
97
|
+
| `github_create_issue_comment` | HIGH | Write; needs approval + `GITHUB_ALLOW_WRITES` |
|
|
98
|
+
| `security_status` | — | Reports the active guardrail configuration |
|
|
99
|
+
|
|
100
|
+
\* `run_command` is LOW risk only when its base command is in
|
|
101
|
+
`command_allowlist`; otherwise HIGH (requires `confirm=true`).
|
|
102
|
+
|
|
103
|
+
## Install
|
|
104
|
+
|
|
105
|
+
Not yet on PyPI. Install straight from git:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
pip install "superuser @ git+https://github.com/fitzyracing1/Super.git"
|
|
109
|
+
pip install "superuser[all] @ git+https://github.com/fitzyracing1/Super.git" # + Cisco AI Defense + Stripe SDKs
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
From a clone:
|
|
113
|
+
|
|
114
|
+
Requires Python 3.10+.
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
pip install -e . # core agent (Duo authz uses requests; no extra dep)
|
|
118
|
+
pip install -e '.[security]' # + Cisco AI Defense SDK (cisco-aidefense-sdk)
|
|
119
|
+
pip install -e '.[billing]' # + Stripe SDK (usage-based billing)
|
|
120
|
+
pip install -e '.[all]' # security + billing
|
|
121
|
+
pip install -e '.[dev]' # + pytest / ruff
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Configure
|
|
125
|
+
|
|
126
|
+
Copy `.env.example` to `.env` (or use a YAML file — see `superuser.example.yaml`)
|
|
127
|
+
and set values. Key settings:
|
|
128
|
+
|
|
129
|
+
- `SUPERUSER_HUMAN_OWNER` — the human accountable for the agent's actions.
|
|
130
|
+
- `SUPERUSER_APPROVAL_AT` — risk threshold for the approval gate (`LOW`…`CRITICAL`).
|
|
131
|
+
- `AI_DEFENSE_*` — Cisco AI Defense credentials/mode (see below).
|
|
132
|
+
- `DUO_*` — Cisco Duo Agentic Identity authorization (see below).
|
|
133
|
+
- `STRIPE_*` — Stripe usage-based billing (see below).
|
|
134
|
+
- `GITHUB_TOKEN`, `GITHUB_ALLOWED_REPOS`, `GITHUB_ALLOW_WRITES` — least-privilege GitHub.
|
|
135
|
+
|
|
136
|
+
> For a full console-value → env-var mapping for all three services, see
|
|
137
|
+
> [`docs/INTEGRATIONS.md`](docs/INTEGRATIONS.md).
|
|
138
|
+
|
|
139
|
+
### Cisco AI Defense
|
|
140
|
+
|
|
141
|
+
Install the SDK (`pip install 'superuser[security]'`) and provide credentials:
|
|
142
|
+
|
|
143
|
+
- **Explicit inspection** of this server's own tool calls/responses:
|
|
144
|
+
set `AI_DEFENSE_INSPECTION_API_KEY`.
|
|
145
|
+
- **Runtime protection** (`agentsec.protect()` auto-patches LLM + MCP clients):
|
|
146
|
+
set `AI_DEFENSE_API_MODE_LLM_ENDPOINT` + `AI_DEFENSE_API_MODE_LLM_API_KEY`,
|
|
147
|
+
or point `AI_DEFENSE_AGENTSEC_CONFIG` at an `agentsec.yaml`
|
|
148
|
+
(see `agentsec.example.yaml`).
|
|
149
|
+
- `AI_DEFENSE_MODE`: `enforce` (block), `monitor` (log only), or `off`.
|
|
150
|
+
|
|
151
|
+
If the SDK is not installed or no credentials are set, the agent still runs but
|
|
152
|
+
every result is annotated `protected: false` / `UNPROTECTED` — the gap is never
|
|
153
|
+
silent. In `enforce` mode, inspection errors **fail closed** (the call is
|
|
154
|
+
blocked); in `monitor` mode they fail open (logged, allowed).
|
|
155
|
+
|
|
156
|
+
## Environment variables
|
|
157
|
+
|
|
158
|
+
| Area | Variables |
|
|
159
|
+
|------|-----------|
|
|
160
|
+
| Core | `SUPERUSER_CONFIG`, `SUPERUSER_HUMAN_OWNER`, `SUPERUSER_APPROVAL_AT`, `SUPERUSER_COMMAND_TIMEOUT`, `SUPERUSER_AUDIT_LOG` |
|
|
161
|
+
| Cisco AI Defense | `AI_DEFENSE_ENABLED` (default `true`; set `false` for local dev), `AI_DEFENSE_MODE`, `AI_DEFENSE_INSPECTION_API_KEY`, `AI_DEFENSE_API_MODE_LLM_ENDPOINT`, `AI_DEFENSE_API_MODE_LLM_API_KEY`, `AI_DEFENSE_AGENTSEC_CONFIG` |
|
|
162
|
+
| Cisco Duo | `DUO_ENABLED`, `DUO_MODE`, `DUO_AGENT_ID`, `DUO_TOKEN_URL`, `DUO_CLIENT_ID`, `DUO_CLIENT_SECRET`, `DUO_AUTHORIZATION_URL`, `DUO_RESOURCE`, `DUO_SCOPES` |
|
|
163
|
+
| Stripe | `STRIPE_ENABLED`, `STRIPE_API_KEY`, `STRIPE_CUSTOMER_ID`, `STRIPE_METER_EVENT_NAME`, `STRIPE_DEFAULT_VALUE` |
|
|
164
|
+
| GitHub | `GITHUB_TOKEN`, `GITHUB_ALLOWED_REPOS`, `GITHUB_ALLOW_WRITES`, `GITHUB_API_URL` |
|
|
165
|
+
|
|
166
|
+
With no keys set the server still runs: Duo and Stripe are off by default, and Cisco AI Defense runs unprotected (clearly flagged) if the SDK/keys are missing.
|
|
167
|
+
|
|
168
|
+
## Run
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
# Validate configuration and print guardrail/Cisco status, then exit:
|
|
172
|
+
superuser --check
|
|
173
|
+
|
|
174
|
+
# Start the MCP server (stdio transport, for MCP clients like Cursor/Claude):
|
|
175
|
+
superuser
|
|
176
|
+
# or: python -m superuser
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Register with an MCP client
|
|
180
|
+
|
|
181
|
+
Example client config (stdio):
|
|
182
|
+
|
|
183
|
+
```json
|
|
184
|
+
{
|
|
185
|
+
"mcpServers": {
|
|
186
|
+
"superuser": {
|
|
187
|
+
"command": "superuser",
|
|
188
|
+
"env": {
|
|
189
|
+
"SUPERUSER_HUMAN_OWNER": "you@example.com",
|
|
190
|
+
"AI_DEFENSE_MODE": "enforce",
|
|
191
|
+
"AI_DEFENSE_INSPECTION_API_KEY": "..."
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Test
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
pytest
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Security notes
|
|
205
|
+
|
|
206
|
+
- The dangerous-command denylist is a backstop, not the primary control. The
|
|
207
|
+
primary controls are Cisco AI Defense inspection and the approval gate.
|
|
208
|
+
- GitHub access **fails closed**: no token or an empty repo allowlist means no
|
|
209
|
+
access. Use a fine-grained, least-privilege token.
|
|
210
|
+
- Secrets are redacted in the audit log. Keep `.env` / `*.yaml` config files out
|
|
211
|
+
of version control (already in `.gitignore`).
|
|
212
|
+
|
|
213
|
+
## License
|
|
214
|
+
|
|
215
|
+
Apache-2.0.
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# Super
|
|
2
|
+
|
|
3
|
+
<!-- mcp-name: io.github.fitzyracing1/superuser -->
|
|
4
|
+
|
|
5
|
+
A **permission-gated superuser agent** exposed over the [Model Context Protocol
|
|
6
|
+
(MCP)](https://modelcontextprotocol.io). It gives an AI agent real
|
|
7
|
+
system-administration powers on this machine — running shell commands, checking
|
|
8
|
+
system state, and acting on GitHub — while wrapping every action in guardrails:
|
|
9
|
+
a dangerous-command denylist, a human-approval gate for risky operations,
|
|
10
|
+
least-privilege GitHub access, an append-only audit log, and **Cisco AI Defense**
|
|
11
|
+
runtime inspection of every tool call and response.
|
|
12
|
+
|
|
13
|
+
The design goal is the version of a "superuser agent" you can actually ship:
|
|
14
|
+
powerful, but safe, auditable, and compatible with enterprise guardrails.
|
|
15
|
+
|
|
16
|
+
> **Landing site:** https://fitzyracing1.github.io/super-landing/ (source: [fitzyracing1/super-landing](https://github.com/fitzyracing1/super-landing)).
|
|
17
|
+
> The previous Pages site in this repo is preserved at tag `site-v1` / branch `site-archive`.
|
|
18
|
+
|
|
19
|
+
## Why it's built this way
|
|
20
|
+
|
|
21
|
+
An agent that can do *anything* as root is a liability, not a product. Buyers
|
|
22
|
+
(and marketplace security reviews) expect scoping, approvals, and an audit
|
|
23
|
+
trail. So capabilities here always pass through a single choke point — the
|
|
24
|
+
`Guard` — which enforces, in order:
|
|
25
|
+
|
|
26
|
+
1. **Cisco AI Defense** inspects the tool call (can block).
|
|
27
|
+
2. **Cisco Duo Agentic Identity** authorizes the tool call per-policy (can block).
|
|
28
|
+
3. **Approval gate** — anything at or above `approval_required_at` (default
|
|
29
|
+
`MEDIUM`) needs an explicit `confirm=true` from a human.
|
|
30
|
+
4. The operation runs.
|
|
31
|
+
5. **Cisco AI Defense** inspects the response before it is returned (catches
|
|
32
|
+
data leakage).
|
|
33
|
+
6. **Stripe** meters the successful call (best-effort; never blocks).
|
|
34
|
+
7. The outcome is written to the **audit log** as
|
|
35
|
+
`human owner -> agent -> tool -> action -> outcome`.
|
|
36
|
+
|
|
37
|
+
This mirrors the enterprise model: your app owns scoping + approvals + local
|
|
38
|
+
audit; **Cisco AI Defense** provides runtime threat inspection at the MCP layer;
|
|
39
|
+
**Cisco Duo Agentic Identity** provides per-tool-call authorization tied to an
|
|
40
|
+
accountable human owner; and **Stripe** turns metered tool calls into revenue.
|
|
41
|
+
|
|
42
|
+
### Cisco Duo Agentic Identity (authorization)
|
|
43
|
+
|
|
44
|
+
Duo follows a "Duo decides, the gateway enforces" model. This agent registers as
|
|
45
|
+
a non-human identity, authenticates via the OAuth 2.1 **client-credentials**
|
|
46
|
+
grant (with an RFC 8707 resource indicator), and authorizes every tool call
|
|
47
|
+
either by matching the tool's required **scope** against what Duo granted, or by
|
|
48
|
+
calling an explicit Duo **decision endpoint**. Set `DUO_ENABLED=true` plus
|
|
49
|
+
`DUO_TOKEN_URL` / `DUO_CLIENT_ID` / `DUO_CLIENT_SECRET` (see `.env.example`). When
|
|
50
|
+
Duo is not configured, calls are allowed but flagged `governed: false`; in
|
|
51
|
+
`enforce` mode authorization errors fail closed.
|
|
52
|
+
|
|
53
|
+
### Stripe usage-based billing (monetization)
|
|
54
|
+
|
|
55
|
+
Each authorized, successful tool call emits a Stripe **meter event**
|
|
56
|
+
(v2 billing meter events API), so the agent can be sold per-action. Configure a
|
|
57
|
+
meter in Stripe, then set `STRIPE_ENABLED=true`, `STRIPE_API_KEY`,
|
|
58
|
+
`STRIPE_CUSTOMER_ID`, and `STRIPE_METER_EVENT_NAME`. Per-tool weights let
|
|
59
|
+
higher-risk actions cost more. Billing is best-effort: metering failures are
|
|
60
|
+
logged and never block or fail the tool call (results are annotated
|
|
61
|
+
`billed: true|false`).
|
|
62
|
+
|
|
63
|
+
## Tools
|
|
64
|
+
|
|
65
|
+
| Tool | Risk | Notes |
|
|
66
|
+
|------|------|-------|
|
|
67
|
+
| `system_status` | LOW | Host, CPU, memory, uptime |
|
|
68
|
+
| `disk_usage` | LOW | Per-partition disk usage |
|
|
69
|
+
| `list_processes` | LOW | Top processes by memory/cpu |
|
|
70
|
+
| `run_command` | HIGH* | Shell command; denylist + approval + audit |
|
|
71
|
+
| `github_get_repo` | LOW | Repo metadata (allowlisted repos only) |
|
|
72
|
+
| `github_list_issues` | LOW | Issues for an allowlisted repo |
|
|
73
|
+
| `github_create_issue_comment` | HIGH | Write; needs approval + `GITHUB_ALLOW_WRITES` |
|
|
74
|
+
| `security_status` | — | Reports the active guardrail configuration |
|
|
75
|
+
|
|
76
|
+
\* `run_command` is LOW risk only when its base command is in
|
|
77
|
+
`command_allowlist`; otherwise HIGH (requires `confirm=true`).
|
|
78
|
+
|
|
79
|
+
## Install
|
|
80
|
+
|
|
81
|
+
Not yet on PyPI. Install straight from git:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
pip install "superuser @ git+https://github.com/fitzyracing1/Super.git"
|
|
85
|
+
pip install "superuser[all] @ git+https://github.com/fitzyracing1/Super.git" # + Cisco AI Defense + Stripe SDKs
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
From a clone:
|
|
89
|
+
|
|
90
|
+
Requires Python 3.10+.
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
pip install -e . # core agent (Duo authz uses requests; no extra dep)
|
|
94
|
+
pip install -e '.[security]' # + Cisco AI Defense SDK (cisco-aidefense-sdk)
|
|
95
|
+
pip install -e '.[billing]' # + Stripe SDK (usage-based billing)
|
|
96
|
+
pip install -e '.[all]' # security + billing
|
|
97
|
+
pip install -e '.[dev]' # + pytest / ruff
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Configure
|
|
101
|
+
|
|
102
|
+
Copy `.env.example` to `.env` (or use a YAML file — see `superuser.example.yaml`)
|
|
103
|
+
and set values. Key settings:
|
|
104
|
+
|
|
105
|
+
- `SUPERUSER_HUMAN_OWNER` — the human accountable for the agent's actions.
|
|
106
|
+
- `SUPERUSER_APPROVAL_AT` — risk threshold for the approval gate (`LOW`…`CRITICAL`).
|
|
107
|
+
- `AI_DEFENSE_*` — Cisco AI Defense credentials/mode (see below).
|
|
108
|
+
- `DUO_*` — Cisco Duo Agentic Identity authorization (see below).
|
|
109
|
+
- `STRIPE_*` — Stripe usage-based billing (see below).
|
|
110
|
+
- `GITHUB_TOKEN`, `GITHUB_ALLOWED_REPOS`, `GITHUB_ALLOW_WRITES` — least-privilege GitHub.
|
|
111
|
+
|
|
112
|
+
> For a full console-value → env-var mapping for all three services, see
|
|
113
|
+
> [`docs/INTEGRATIONS.md`](docs/INTEGRATIONS.md).
|
|
114
|
+
|
|
115
|
+
### Cisco AI Defense
|
|
116
|
+
|
|
117
|
+
Install the SDK (`pip install 'superuser[security]'`) and provide credentials:
|
|
118
|
+
|
|
119
|
+
- **Explicit inspection** of this server's own tool calls/responses:
|
|
120
|
+
set `AI_DEFENSE_INSPECTION_API_KEY`.
|
|
121
|
+
- **Runtime protection** (`agentsec.protect()` auto-patches LLM + MCP clients):
|
|
122
|
+
set `AI_DEFENSE_API_MODE_LLM_ENDPOINT` + `AI_DEFENSE_API_MODE_LLM_API_KEY`,
|
|
123
|
+
or point `AI_DEFENSE_AGENTSEC_CONFIG` at an `agentsec.yaml`
|
|
124
|
+
(see `agentsec.example.yaml`).
|
|
125
|
+
- `AI_DEFENSE_MODE`: `enforce` (block), `monitor` (log only), or `off`.
|
|
126
|
+
|
|
127
|
+
If the SDK is not installed or no credentials are set, the agent still runs but
|
|
128
|
+
every result is annotated `protected: false` / `UNPROTECTED` — the gap is never
|
|
129
|
+
silent. In `enforce` mode, inspection errors **fail closed** (the call is
|
|
130
|
+
blocked); in `monitor` mode they fail open (logged, allowed).
|
|
131
|
+
|
|
132
|
+
## Environment variables
|
|
133
|
+
|
|
134
|
+
| Area | Variables |
|
|
135
|
+
|------|-----------|
|
|
136
|
+
| Core | `SUPERUSER_CONFIG`, `SUPERUSER_HUMAN_OWNER`, `SUPERUSER_APPROVAL_AT`, `SUPERUSER_COMMAND_TIMEOUT`, `SUPERUSER_AUDIT_LOG` |
|
|
137
|
+
| Cisco AI Defense | `AI_DEFENSE_ENABLED` (default `true`; set `false` for local dev), `AI_DEFENSE_MODE`, `AI_DEFENSE_INSPECTION_API_KEY`, `AI_DEFENSE_API_MODE_LLM_ENDPOINT`, `AI_DEFENSE_API_MODE_LLM_API_KEY`, `AI_DEFENSE_AGENTSEC_CONFIG` |
|
|
138
|
+
| Cisco Duo | `DUO_ENABLED`, `DUO_MODE`, `DUO_AGENT_ID`, `DUO_TOKEN_URL`, `DUO_CLIENT_ID`, `DUO_CLIENT_SECRET`, `DUO_AUTHORIZATION_URL`, `DUO_RESOURCE`, `DUO_SCOPES` |
|
|
139
|
+
| Stripe | `STRIPE_ENABLED`, `STRIPE_API_KEY`, `STRIPE_CUSTOMER_ID`, `STRIPE_METER_EVENT_NAME`, `STRIPE_DEFAULT_VALUE` |
|
|
140
|
+
| GitHub | `GITHUB_TOKEN`, `GITHUB_ALLOWED_REPOS`, `GITHUB_ALLOW_WRITES`, `GITHUB_API_URL` |
|
|
141
|
+
|
|
142
|
+
With no keys set the server still runs: Duo and Stripe are off by default, and Cisco AI Defense runs unprotected (clearly flagged) if the SDK/keys are missing.
|
|
143
|
+
|
|
144
|
+
## Run
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
# Validate configuration and print guardrail/Cisco status, then exit:
|
|
148
|
+
superuser --check
|
|
149
|
+
|
|
150
|
+
# Start the MCP server (stdio transport, for MCP clients like Cursor/Claude):
|
|
151
|
+
superuser
|
|
152
|
+
# or: python -m superuser
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Register with an MCP client
|
|
156
|
+
|
|
157
|
+
Example client config (stdio):
|
|
158
|
+
|
|
159
|
+
```json
|
|
160
|
+
{
|
|
161
|
+
"mcpServers": {
|
|
162
|
+
"superuser": {
|
|
163
|
+
"command": "superuser",
|
|
164
|
+
"env": {
|
|
165
|
+
"SUPERUSER_HUMAN_OWNER": "you@example.com",
|
|
166
|
+
"AI_DEFENSE_MODE": "enforce",
|
|
167
|
+
"AI_DEFENSE_INSPECTION_API_KEY": "..."
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Test
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
pytest
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Security notes
|
|
181
|
+
|
|
182
|
+
- The dangerous-command denylist is a backstop, not the primary control. The
|
|
183
|
+
primary controls are Cisco AI Defense inspection and the approval gate.
|
|
184
|
+
- GitHub access **fails closed**: no token or an empty repo allowlist means no
|
|
185
|
+
access. Use a fine-grained, least-privilege token.
|
|
186
|
+
- Secrets are redacted in the audit log. Keep `.env` / `*.yaml` config files out
|
|
187
|
+
of version control (already in `.gitignore`).
|
|
188
|
+
|
|
189
|
+
## License
|
|
190
|
+
|
|
191
|
+
Apache-2.0.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "superuser"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A permission-gated superuser (sysadmin) MCP agent with Cisco AI Defense runtime guardrails."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "Apache-2.0" }
|
|
12
|
+
authors = [{ name = "Super" }]
|
|
13
|
+
keywords = ["mcp", "agent", "sysadmin", "superuser", "cisco", "ai-defense", "guardrails"]
|
|
14
|
+
|
|
15
|
+
dependencies = [
|
|
16
|
+
"mcp>=1.2.0,<2",
|
|
17
|
+
"psutil>=5.9",
|
|
18
|
+
"PyYAML>=6.0",
|
|
19
|
+
"requests>=2.31",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[project.optional-dependencies]
|
|
23
|
+
# Cisco AI Defense runtime guardrails. Imported lazily so the server still
|
|
24
|
+
# runs (in an unprotected, clearly-warned state) when this is not installed.
|
|
25
|
+
security = ["cisco-aidefense-sdk>=2.1.0"]
|
|
26
|
+
# Stripe usage-based billing. Optional; metering degrades gracefully without it.
|
|
27
|
+
billing = ["stripe>=11.0"]
|
|
28
|
+
dev = ["pytest>=8.0", "ruff>=0.6"]
|
|
29
|
+
all = ["cisco-aidefense-sdk>=2.1.0", "stripe>=11.0"]
|
|
30
|
+
|
|
31
|
+
[project.scripts]
|
|
32
|
+
superuser = "superuser.__main__:main"
|
|
33
|
+
|
|
34
|
+
[tool.setuptools.packages.find]
|
|
35
|
+
where = ["src"]
|
|
36
|
+
|
|
37
|
+
[tool.ruff]
|
|
38
|
+
line-length = 100
|
|
39
|
+
target-version = "py310"
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Entry point: `superuser` / `python -m superuser`."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import logging
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
from .config import Config
|
|
10
|
+
from .server import build_server
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def main(argv: list[str] | None = None) -> int:
|
|
14
|
+
parser = argparse.ArgumentParser(
|
|
15
|
+
prog="superuser",
|
|
16
|
+
description="Permission-gated superuser MCP agent with Cisco AI Defense guardrails.",
|
|
17
|
+
)
|
|
18
|
+
parser.add_argument("--config", help="Path to a YAML config file (or set SUPERUSER_CONFIG).")
|
|
19
|
+
parser.add_argument(
|
|
20
|
+
"--transport", default="stdio", choices=["stdio", "sse", "streamable-http"],
|
|
21
|
+
help="MCP transport (default: stdio).",
|
|
22
|
+
)
|
|
23
|
+
parser.add_argument("--log-level", default="INFO")
|
|
24
|
+
parser.add_argument(
|
|
25
|
+
"--check", action="store_true",
|
|
26
|
+
help="Validate config and print security status, then exit (no server).",
|
|
27
|
+
)
|
|
28
|
+
args = parser.parse_args(argv)
|
|
29
|
+
|
|
30
|
+
logging.basicConfig(
|
|
31
|
+
level=getattr(logging, args.log_level.upper(), logging.INFO),
|
|
32
|
+
format="%(asctime)s %(levelname)s %(name)s: %(message)s",
|
|
33
|
+
stream=sys.stderr,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
config = Config.load(args.config)
|
|
37
|
+
server = build_server(config)
|
|
38
|
+
|
|
39
|
+
if args.check:
|
|
40
|
+
from .authz import DuoAuthorizer
|
|
41
|
+
from .billing import StripeMeter
|
|
42
|
+
from .security import CiscoAIDefense
|
|
43
|
+
|
|
44
|
+
defense = CiscoAIDefense(config.cisco)
|
|
45
|
+
authorizer = DuoAuthorizer(config.duo)
|
|
46
|
+
meter = StripeMeter(config.stripe)
|
|
47
|
+
print("Superuser agent configuration OK.")
|
|
48
|
+
print(f" Human owner: {config.human_owner}")
|
|
49
|
+
print(f" Approval required at: {config.approval_required_at.name}")
|
|
50
|
+
print(f" Audit log: {config.audit_log_path}")
|
|
51
|
+
print(f" Cisco AI Defense: {defense.status}")
|
|
52
|
+
print(f" Cisco Duo (authz): {authorizer.status}")
|
|
53
|
+
print(f" Stripe billing: {meter.status}")
|
|
54
|
+
print(f" GitHub configured: {bool(config.github.token)}")
|
|
55
|
+
return 0
|
|
56
|
+
|
|
57
|
+
server.run(transport=args.transport)
|
|
58
|
+
return 0
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
if __name__ == "__main__":
|
|
62
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Append-only audit logging.
|
|
2
|
+
|
|
3
|
+
Every tool invocation produces an identity-correlated record:
|
|
4
|
+
``human owner -> agent -> tool -> action -> outcome``. This is the local
|
|
5
|
+
counterpart to the audit chain enforced by Cisco Duo's MCP gateway, and it is
|
|
6
|
+
what makes the agent's actions reviewable after the fact.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
import threading
|
|
14
|
+
from datetime import datetime, timezone
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
# Argument keys whose values must never be written to the audit log verbatim.
|
|
19
|
+
_SENSITIVE_KEYS = {"token", "api_key", "apikey", "password", "secret", "authorization"}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _redact(value: Any) -> Any:
|
|
23
|
+
if isinstance(value, dict):
|
|
24
|
+
return {
|
|
25
|
+
k: ("***REDACTED***" if k.lower() in _SENSITIVE_KEYS else _redact(v))
|
|
26
|
+
for k, v in value.items()
|
|
27
|
+
}
|
|
28
|
+
if isinstance(value, list):
|
|
29
|
+
return [_redact(v) for v in value]
|
|
30
|
+
if isinstance(value, str) and len(value) > 2000:
|
|
31
|
+
return value[:2000] + f"...[truncated {len(value) - 2000} chars]"
|
|
32
|
+
return value
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class AuditLog:
|
|
36
|
+
"""Thread-safe JSONL audit logger."""
|
|
37
|
+
|
|
38
|
+
def __init__(self, path: str | os.PathLike[str], human_owner: str = "unknown") -> None:
|
|
39
|
+
self.path = Path(path)
|
|
40
|
+
self.human_owner = human_owner
|
|
41
|
+
self._lock = threading.Lock()
|
|
42
|
+
if self.path.parent and not self.path.parent.exists():
|
|
43
|
+
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
44
|
+
|
|
45
|
+
def record(
|
|
46
|
+
self,
|
|
47
|
+
tool: str,
|
|
48
|
+
arguments: dict[str, Any],
|
|
49
|
+
decision: str,
|
|
50
|
+
status: str,
|
|
51
|
+
*,
|
|
52
|
+
risk: str | None = None,
|
|
53
|
+
detail: str | None = None,
|
|
54
|
+
) -> dict[str, Any]:
|
|
55
|
+
entry = {
|
|
56
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
57
|
+
"human_owner": self.human_owner,
|
|
58
|
+
"agent": "superuser",
|
|
59
|
+
"tool": tool,
|
|
60
|
+
"arguments": _redact(arguments),
|
|
61
|
+
"risk": risk,
|
|
62
|
+
"decision": decision,
|
|
63
|
+
"status": status,
|
|
64
|
+
"detail": detail,
|
|
65
|
+
}
|
|
66
|
+
line = json.dumps(entry, ensure_ascii=False)
|
|
67
|
+
with self._lock:
|
|
68
|
+
with self.path.open("a", encoding="utf-8") as fh:
|
|
69
|
+
fh.write(line + "\n")
|
|
70
|
+
return entry
|