sage-governance 1.0.0 → 1.0.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/AGENTS.MD +10 -2
- package/Contributors.md +9 -0
- package/README.md +107 -27
- package/bin/sage.js +98 -8
- package/claude.json +24 -1
- package/codex.json +24 -0
- package/cursor.json +23 -0
- package/kimicode.json +39 -0
- package/opencode.json +30 -7
- package/package.json +12 -6
- package/sage/sage_agent.py +48 -3
- package/sage/security_agent.py +1 -1
- package/sage/startup.py +9 -6
- package/scripts/bootstrap-python.js +138 -0
- package/security.md +9 -0
- package/trae.json +39 -0
- package/windsurf.json +38 -0
package/AGENTS.MD
CHANGED
|
@@ -312,7 +312,7 @@ When the request involves cloud infrastructure, API integrations, or
|
|
|
312
312
|
deployment configs, the Security Agent also checks:
|
|
313
313
|
|
|
314
314
|
- Environment variable handling (secrets must never be in code)
|
|
315
|
-
- AI provider credential hygiene (
|
|
315
|
+
- AI provider credential hygiene (OPENAI_API_KEY, etc.)
|
|
316
316
|
- Database connection string exposure
|
|
317
317
|
- Cloud misconfiguration patterns (public S3 buckets, open ports)
|
|
318
318
|
- GDPR data processor agreement gaps (third-party API calls with PII)
|
|
@@ -448,7 +448,15 @@ report_generate(session_id?, output_format?)
|
|
|
448
448
|
→ {content: string, report_path: string}
|
|
449
449
|
→ output_format: "markdown" (full model card) | "summary" (terminal)
|
|
450
450
|
→ Call at end of session or on developer request
|
|
451
|
-
|
|
451
|
+
|
|
452
|
+
duckduckgo-search (via ddg server)
|
|
453
|
+
→ Query for real-time web searches and factual documentation retrieval.
|
|
454
|
+
→ Use ONLY when current state, external documentation, or real-time web facts are needed.
|
|
455
|
+
|
|
456
|
+
github (via github server)
|
|
457
|
+
→ Query for git commands, repository status, commits, and pull requests.
|
|
458
|
+
→ Use ONLY when interacting with GitHub or performing git/PR tasks.
|
|
459
|
+
```\n\n## MCP Tool Integration\n\nThe SAGE runtime can combine the generic web‑search (`duckduckgo-search`) and GitHub (`github`) MCP tools with the core SAGE MCP tools to enrich evaluations.\n\n**When to use:**\n- **`duckduckgo-search`** – before calling `sage_evaluate` when the prompt references statutes, standards, or recent policy updates that need factual verification.\n- **`github`** – when the task involves code provenance, repository status, or pulling commit metadata (e.g., verifying that a referenced file exists in the repo).\n\n**How to combine:**\n1. Run `duckduckgo-search` (or `github`) and capture the result.\n2. Include the retrieved information in the `context` argument of `sage_evaluate`.\n3. If the result contains commit hashes or issue identifiers, log them via `audit_write` for traceability.\n4. Continue with the normal SAGE flow (`sage_evaluate` → optional fairness options → `intercept_file_write`).\n\n**Example flow (Markdown mermaid):**\n```mermaid\nflowchart TD\n A[Start] --> B[duckduckgo-search / github]\n B --> C[Capture result]\n C --> D[sage_evaluate(prompt, context=result)]\n D --> E{Risk level}\n E -->|LOW/MEDIUM| F[Proceed to coding]\n E -->|HIGH/CRITICAL| G[Present fairness options]\n F --> H[intercept_file_write]\n G --> H\n H --> I[audit_write]\n I --> J[Done]\n```\n\n*All findings from these external calls are recorded in the audit trail and are subject to the same HITL requirements as other SAGE decisions.*\n
|
|
452
460
|
|
|
453
461
|
---
|
|
454
462
|
|
package/Contributors.md
ADDED
package/README.md
CHANGED
|
@@ -84,7 +84,7 @@ Deterministic (no LLM) full-spectrum code scanner.
|
|
|
84
84
|
|
|
85
85
|
| Severity | Category | Examples |
|
|
86
86
|
|----------|----------|---------|
|
|
87
|
-
| P0 | Secret exposure | Hardcoded API keys,
|
|
87
|
+
| P0 | Secret exposure | Hardcoded API keys, OpenAI keys, DB passwords |
|
|
88
88
|
| P0 | Critical PII | SSN, biometrics, medical data, GDPR Article 9 special categories |
|
|
89
89
|
| P1 | Sensitive PII | Geolocation, passport numbers, date of birth |
|
|
90
90
|
| P1 | Protected attribute direct use | `race`, `sex`, `age` as model features |
|
|
@@ -128,64 +128,144 @@ Developer asks coding agent to create classifier.py
|
|
|
128
128
|
|
|
129
129
|
---
|
|
130
130
|
|
|
131
|
-
## Installation
|
|
131
|
+
## Installation & Quick Start
|
|
132
132
|
|
|
133
|
-
###
|
|
133
|
+
### 1. Global Installation (recommended)
|
|
134
|
+
|
|
135
|
+
Install SAGE globally via npm:
|
|
134
136
|
|
|
135
137
|
```bash
|
|
136
138
|
npm install -g sage-governance
|
|
137
139
|
```
|
|
138
140
|
|
|
139
|
-
This installs the `sage` CLI command globally.
|
|
141
|
+
This installs the `sage` CLI command globally.
|
|
142
|
+
|
|
143
|
+
### 2. Python Dependencies
|
|
140
144
|
|
|
141
|
-
|
|
145
|
+
Install the required Python packages:
|
|
142
146
|
|
|
143
147
|
```bash
|
|
144
|
-
|
|
145
|
-
|
|
148
|
+
pip install -r $(npm root -g)/sage-governance/requirements.txt
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
*(Alternatively, run `pip install mcp openai pydantic fairlearn diffprivlib pandas scikit-learn`)*
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Configuring SAGE in AI Clients
|
|
156
|
+
|
|
157
|
+
Since SAGE is an MCP server, you must add it to the MCP configuration of your AI coding environment. Ensure the environment has access to your `OPENAI_API_KEY`.
|
|
146
158
|
|
|
147
|
-
|
|
148
|
-
pip install mcp anthropic pydantic fairlearn diffprivlib
|
|
159
|
+
### 1. Cursor
|
|
149
160
|
|
|
150
|
-
|
|
151
|
-
export ANTHROPIC_API_KEY=sk-ant-...
|
|
161
|
+
To enable SAGE in Cursor, you can configure it globally or on a per-project basis.
|
|
152
162
|
|
|
153
|
-
|
|
154
|
-
|
|
163
|
+
#### Option A: Project-level Configuration (recommended)
|
|
164
|
+
Create a `.cursor/mcp.json` file in your project root with the following content:
|
|
165
|
+
|
|
166
|
+
```json
|
|
167
|
+
{
|
|
168
|
+
"mcpServers": {
|
|
169
|
+
"sage-governance": {
|
|
170
|
+
"command": "sage",
|
|
171
|
+
"args": [],
|
|
172
|
+
"type": "stdio",
|
|
173
|
+
"env": {
|
|
174
|
+
"OPENAI_API_KEY": "YOUR_OPENAI_API_KEY",
|
|
175
|
+
"SAGE_LLM_MODEL": "gpt-4o-mini"
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
155
180
|
```
|
|
156
181
|
|
|
157
|
-
|
|
182
|
+
#### Option B: Global Settings
|
|
183
|
+
1. Go to **Cursor Settings** -> **Features** -> **MCP**.
|
|
184
|
+
2. Click **+ Add New MCP Server**.
|
|
185
|
+
3. Configure the fields:
|
|
186
|
+
- **Name**: `sage-governance`
|
|
187
|
+
- **Type**: `command`
|
|
188
|
+
- **Command**: `sage`
|
|
189
|
+
- **Args**: (leave empty)
|
|
190
|
+
4. Click **Save**.
|
|
158
191
|
|
|
159
|
-
|
|
160
|
-
# Via CLI (after npm install -g)
|
|
161
|
-
sage
|
|
192
|
+
---
|
|
162
193
|
|
|
163
|
-
|
|
164
|
-
|
|
194
|
+
### 2. OpenCode
|
|
195
|
+
|
|
196
|
+
Add the following configuration to `opencode.json` in your project root directory:
|
|
197
|
+
|
|
198
|
+
```json
|
|
199
|
+
{
|
|
200
|
+
"mcp": {
|
|
201
|
+
"sage-governance": {
|
|
202
|
+
"type": "local",
|
|
203
|
+
"command": ["sage"],
|
|
204
|
+
"enabled": true,
|
|
205
|
+
"environment": {
|
|
206
|
+
"OPENAI_API_KEY": "(env:OPENAI_API_KEY)",
|
|
207
|
+
"SAGE_LLM_MODEL": "gpt-4o-mini"
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
165
212
|
```
|
|
166
213
|
|
|
167
214
|
---
|
|
168
215
|
|
|
169
|
-
|
|
216
|
+
### 3. Claude Desktop
|
|
217
|
+
|
|
218
|
+
Add SAGE to your global Claude Desktop configuration file:
|
|
219
|
+
- **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
220
|
+
- **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
221
|
+
|
|
222
|
+
Add this entry under the `mcpServers` object:
|
|
170
223
|
|
|
171
224
|
```json
|
|
172
225
|
{
|
|
173
|
-
"$schema": "https://opencode.ai/config.schema.json",
|
|
174
226
|
"mcpServers": {
|
|
175
227
|
"sage-governance": {
|
|
176
|
-
"type": "local",
|
|
177
228
|
"command": "sage",
|
|
178
229
|
"args": [],
|
|
179
230
|
"env": {
|
|
180
|
-
"
|
|
181
|
-
"SAGE_LLM_MODEL": "
|
|
231
|
+
"OPENAI_API_KEY": "YOUR_OPENAI_API_KEY",
|
|
232
|
+
"SAGE_LLM_MODEL": "gpt-4o-mini"
|
|
182
233
|
}
|
|
183
234
|
}
|
|
184
235
|
}
|
|
185
236
|
}
|
|
186
237
|
```
|
|
187
238
|
|
|
188
|
-
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
### 4. Claude Code
|
|
242
|
+
|
|
243
|
+
Run this single command in your terminal to automatically register SAGE with Claude Code:
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
claude mcp add sage-governance sage -- sage
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
### 5. VS Code (Cline / Continue)
|
|
252
|
+
|
|
253
|
+
Add the server to your Cline/Continue MCP configuration settings JSON:
|
|
254
|
+
|
|
255
|
+
```json
|
|
256
|
+
{
|
|
257
|
+
"mcpServers": {
|
|
258
|
+
"sage-governance": {
|
|
259
|
+
"command": "sage",
|
|
260
|
+
"args": [],
|
|
261
|
+
"env": {
|
|
262
|
+
"OPENAI_API_KEY": "YOUR_OPENAI_API_KEY",
|
|
263
|
+
"SAGE_LLM_MODEL": "gpt-4o-mini"
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
```
|
|
189
269
|
|
|
190
270
|
---
|
|
191
271
|
|
|
@@ -299,7 +379,7 @@ When base rates differ across groups, Demographic Parity, Equalized Odds, and Pr
|
|
|
299
379
|
| George | Ethics & regulatory policy files, project management |
|
|
300
380
|
| Jeremy | Data science validation, security pipeline, presentation |
|
|
301
381
|
|
|
302
|
-
**Built with:** FastMCP · Python · Pydantic · Fairlearn ·
|
|
382
|
+
**Built with:** FastMCP · Python · Pydantic · Fairlearn · OpenAI API
|
|
303
383
|
**Built by:** Oluwagbemisola, Prajwal, Roshan, Jeremy, George
|
|
304
384
|
**License:** MIT
|
|
305
385
|
|
|
@@ -316,4 +396,4 @@ When base rates differ across groups, Demographic Parity, Equalized Odds, and Pr
|
|
|
316
396
|
- ProPublica (2016). "Machine Bias." COMPAS audit.
|
|
317
397
|
- Ali et al. (2019). "Discrimination through Optimization." ACM FAccT.
|
|
318
398
|
- Beunec Technologies Inc Agentic Annotation Protocol - github.com/beunec
|
|
319
|
-
-
|
|
399
|
+
- Model Context Protocol — modelcontextprotocol.io
|
package/bin/sage.js
CHANGED
|
@@ -7,20 +7,21 @@
|
|
|
7
7
|
* SAGE MCP server (sage/mcp_server.py).
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
const { spawn } = require('child_process');
|
|
10
|
+
const { spawn, spawnSync } = require('child_process');
|
|
11
11
|
const path = require('path');
|
|
12
12
|
const fs = require('fs');
|
|
13
|
+
const os = require('os');
|
|
13
14
|
|
|
14
15
|
const PROJECT_ROOT = path.resolve(__dirname, '..');
|
|
15
16
|
const MCP_SERVER_PATH = path.join(PROJECT_ROOT, 'sage', 'mcp_server.py');
|
|
17
|
+
const BOOTSTRAP_SCRIPT = path.join(PROJECT_ROOT, 'scripts', 'bootstrap-python.js');
|
|
16
18
|
|
|
17
19
|
/**
|
|
18
20
|
* Resolves the Python executable in order of preference.
|
|
19
21
|
*/
|
|
20
22
|
function getPythonExecutable() {
|
|
21
|
-
const executables = ['
|
|
23
|
+
const executables = process.platform === 'win32' ? ['python', 'python3', 'py'] : ['python3', 'python'];
|
|
22
24
|
const { execSync } = require('child_process');
|
|
23
|
-
|
|
24
25
|
for (const exe of executables) {
|
|
25
26
|
try {
|
|
26
27
|
execSync(`${exe} --version`, { stdio: 'ignore' });
|
|
@@ -32,17 +33,68 @@ function getPythonExecutable() {
|
|
|
32
33
|
return null;
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
|
|
36
|
+
// Resolve Python executable, preferring bundled venv if available
|
|
37
|
+
let pythonExe = getPythonExecutable();
|
|
38
|
+
const venvDir = path.join(os.homedir(), '.cache', 'sage-governance', 'venv');
|
|
39
|
+
const venvPython = process.platform === 'win32'
|
|
40
|
+
? path.join(venvDir, 'Scripts', 'python.exe')
|
|
41
|
+
: path.join(venvDir, 'bin', 'python');
|
|
42
|
+
if (fs.existsSync(venvPython)) {
|
|
43
|
+
pythonExe = venvPython;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Run the bootstrap script synchronously. Used when the user explicitly
|
|
48
|
+
* requests `sage setup` or when the venv is missing before starting the server.
|
|
49
|
+
*/
|
|
50
|
+
function runBootstrap() {
|
|
51
|
+
console.log('[SAGE] Running bootstrap to prepare Python environment…');
|
|
52
|
+
const result = spawnSync('node', [BOOTSTRAP_SCRIPT], { stdio: 'inherit' });
|
|
53
|
+
if (result.status !== 0) {
|
|
54
|
+
console.error('[SAGE] Bootstrap failed.');
|
|
55
|
+
process.exit(result.status || 1);
|
|
56
|
+
}
|
|
57
|
+
console.log('[SAGE] Bootstrap completed successfully.');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Determine command mode ----------------------------------------------------
|
|
61
|
+
const args = process.argv.slice(2);
|
|
62
|
+
const firstArg = args[0] ? args[0].toLowerCase() : '';
|
|
63
|
+
|
|
64
|
+
// Help / setup flags -------------------------------------------------------
|
|
65
|
+
const isHelp = ['--help', '-h', 'help'].includes(firstArg);
|
|
66
|
+
const isSetup = ['setup', '--setup', 'config', '--config'].includes(firstArg);
|
|
67
|
+
|
|
68
|
+
if (isHelp) {
|
|
69
|
+
// Show the user‑facing guide (same as before) and exit.
|
|
70
|
+
printSetupGuide();
|
|
71
|
+
process.exit(0);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (isSetup) {
|
|
75
|
+
// Explicit bootstrap request.
|
|
76
|
+
runBootstrap();
|
|
77
|
+
process.exit(0);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// If the venv is missing, auto‑bootstrap before launching the server.
|
|
81
|
+
if (!fs.existsSync(venvPython)) {
|
|
82
|
+
console.log('[SAGE] No bundled virtual environment detected – bootstrapping now.');
|
|
83
|
+
runBootstrap();
|
|
84
|
+
// Refresh pythonExe after bootstrap.
|
|
85
|
+
pythonExe = venvPython;
|
|
86
|
+
}
|
|
36
87
|
|
|
37
88
|
if (!pythonExe) {
|
|
38
|
-
console.error('[SAGE] Error: Python 3 not found in PATH.');
|
|
39
|
-
console.error(' Please
|
|
89
|
+
console.error('[SAGE] Error: Python 3 not found in PATH and no bundled venv detected.');
|
|
90
|
+
console.error(' Please ensure Python 3 is installed or run `sage setup` to bootstrap.');
|
|
40
91
|
process.exit(1);
|
|
41
92
|
}
|
|
42
93
|
|
|
43
|
-
|
|
94
|
+
// Launch the MCP server ----------------------------------------------------
|
|
95
|
+
const child = spawn(pythonExe, [MCP_SERVER_PATH, ...args], {
|
|
44
96
|
stdio: 'inherit',
|
|
45
|
-
env: process.env
|
|
97
|
+
env: process.env,
|
|
46
98
|
});
|
|
47
99
|
|
|
48
100
|
child.on('error', (err) => {
|
|
@@ -53,3 +105,41 @@ child.on('error', (err) => {
|
|
|
53
105
|
child.on('exit', (code) => {
|
|
54
106
|
process.exit(code);
|
|
55
107
|
});
|
|
108
|
+
|
|
109
|
+
function printSetupGuide() {
|
|
110
|
+
console.log(`\n\x1b[1;36m╔══════════════════════════════════════════════════════════════════════════╗\x1b[0m
|
|
111
|
+
\x1b[1;36m║ SAGE — Supervisory Agentic Governance Engine ║\x1b[0m
|
|
112
|
+
\x1b[1;36m║ Model Context Protocol (MCP) Server Setup & Configuration Guide ║\x1b[0m
|
|
113
|
+
\x1b[1;36m╚══════════════════════════════════════════════════════════════════════════╝\x1b[0m
|
|
114
|
+
|
|
115
|
+
\x1b[1;33m────────────────────────────────────────────────────────────────────────────\x1b[0m
|
|
116
|
+
\x1b[1;32m4. CLAUDE CODE CONFIGURATION\x1b[0m
|
|
117
|
+
\x1b[1;33m────────────────────────────────────────────────────────────────────────────\x1b[0m
|
|
118
|
+
Run the following command in your terminal:
|
|
119
|
+
|
|
120
|
+
\x1b[1;35mclaude mcp add sage-governance sage -- sage\x1b[0m
|
|
121
|
+
|
|
122
|
+
\x1b[1;33m────────────────────────────────────────────────────────────────────────────\x1b[0m
|
|
123
|
+
\x1b[1;32m5. VS CODE (CLINE / CONTINUE) CONFIGURATION\x1b[0m
|
|
124
|
+
\x1b[1;33m────────────────────────────────────────────────────────────────────────────\x1b[0m
|
|
125
|
+
Add the following to your Cline/Continue MCP settings json file:
|
|
126
|
+
|
|
127
|
+
\x1b[32m{
|
|
128
|
+
"mcpServers": {
|
|
129
|
+
"sage-governance": {
|
|
130
|
+
"command": "sage",
|
|
131
|
+
"args": [],
|
|
132
|
+
"env": {
|
|
133
|
+
"OPENAI_API_KEY": "YOUR_OPENAI_API_KEY",
|
|
134
|
+
"SAGE_LLM_MODEL": "gpt-4o-mini"
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}\x1b[0m
|
|
139
|
+
|
|
140
|
+
\x1b[1;33m────────────────────────────────────────────────────────────────────────────\x1b[0m
|
|
141
|
+
\x1b[1mNOTE:\x1b[0m Make sure you have python dependencies installed:
|
|
142
|
+
\x1b[35mpip install mcp openai pydantic fairlearn diffprivlib pandas scikit-learn\x1b[0m
|
|
143
|
+
`);
|
|
144
|
+
}
|
|
145
|
+
|
package/claude.json
CHANGED
|
@@ -11,6 +11,29 @@
|
|
|
11
11
|
"OPENAI_API_KEY": "${OPENAI_API_KEY}",
|
|
12
12
|
"SAGE_LLM_MODEL": "gpt-4o-mini"
|
|
13
13
|
}
|
|
14
|
-
}
|
|
14
|
+
},
|
|
15
|
+
"duckduckgo-search": {
|
|
16
|
+
"type": "local",
|
|
17
|
+
"command": ["uvx", "duckduckgo-mcp-server"],
|
|
18
|
+
"enabled": true,
|
|
19
|
+
"timeout": 10000
|
|
20
|
+
},
|
|
21
|
+
"github": {
|
|
22
|
+
"type": "local",
|
|
23
|
+
"command": [
|
|
24
|
+
"docker",
|
|
25
|
+
"run",
|
|
26
|
+
"-i",
|
|
27
|
+
"--rm",
|
|
28
|
+
"-e",
|
|
29
|
+
"GITHUB_PERSONAL_ACCESS_TOKEN",
|
|
30
|
+
"ghcr.io/github/github-mcp-server"
|
|
31
|
+
],
|
|
32
|
+
"enabled": false,
|
|
33
|
+
"environment": {
|
|
34
|
+
"GITHUB_PERSONAL_ACCESS_TOKEN": "{env:GITHUB_PERSONAL_ACCESS_TOKEN}"
|
|
35
|
+
},
|
|
36
|
+
"timeout": 10000
|
|
15
37
|
}
|
|
16
38
|
}
|
|
39
|
+
}
|
package/codex.json
CHANGED
|
@@ -13,6 +13,30 @@
|
|
|
13
13
|
"OPENAI_API_KEY": "${OPENAI_API_KEY}",
|
|
14
14
|
"SAGE_LLM_MODEL": "gpt-4o-mini"
|
|
15
15
|
},
|
|
16
|
+
"duckduckgo-search": {
|
|
17
|
+
"type": "local",
|
|
18
|
+
"command": ["uvx", "duckduckgo-mcp-server"],
|
|
19
|
+
"enabled": true,
|
|
20
|
+
"timeout": 10000
|
|
21
|
+
},
|
|
22
|
+
"github": {
|
|
23
|
+
"type": "local",
|
|
24
|
+
"command": [
|
|
25
|
+
"docker",
|
|
26
|
+
"run",
|
|
27
|
+
"-i",
|
|
28
|
+
"--rm",
|
|
29
|
+
"-e",
|
|
30
|
+
"GITHUB_PERSONAL_ACCESS_TOKEN",
|
|
31
|
+
"ghcr.io/github/github-mcp-server"
|
|
32
|
+
],
|
|
33
|
+
"enabled": false,
|
|
34
|
+
"environment": {
|
|
35
|
+
"GITHUB_PERSONAL_ACCESS_TOKEN": "{env:GITHUB_PERSONAL_ACCESS_TOKEN}"
|
|
36
|
+
},
|
|
37
|
+
"timeout": 10000
|
|
38
|
+
}
|
|
39
|
+
,
|
|
16
40
|
"description": "SAGE governance layer — evaluates ethics, fairness, and regulatory compliance before code generation"
|
|
17
41
|
}
|
|
18
42
|
},
|
package/cursor.json
CHANGED
|
@@ -21,6 +21,29 @@
|
|
|
21
21
|
"env": {
|
|
22
22
|
"OPENAI_API_KEY": "${OPENAI_API_KEY}",
|
|
23
23
|
"SAGE_LLM_MODEL": "gpt-4o-mini"
|
|
24
|
+
},
|
|
25
|
+
"duckduckgo-search": {
|
|
26
|
+
"type": "local",
|
|
27
|
+
"command": ["uvx", "duckduckgo-mcp-server"],
|
|
28
|
+
"enabled": true,
|
|
29
|
+
"timeout": 10000
|
|
30
|
+
},
|
|
31
|
+
"github": {
|
|
32
|
+
"type": "local",
|
|
33
|
+
"command": [
|
|
34
|
+
"docker",
|
|
35
|
+
"run",
|
|
36
|
+
"-i",
|
|
37
|
+
"--rm",
|
|
38
|
+
"-e",
|
|
39
|
+
"GITHUB_PERSONAL_ACCESS_TOKEN",
|
|
40
|
+
"ghcr.io/github/github-mcp-server"
|
|
41
|
+
],
|
|
42
|
+
"enabled": false,
|
|
43
|
+
"environment": {
|
|
44
|
+
"GITHUB_PERSONAL_ACCESS_TOKEN": "{env:GITHUB_PERSONAL_ACCESS_TOKEN}"
|
|
45
|
+
},
|
|
46
|
+
"timeout": 10000
|
|
24
47
|
}
|
|
25
48
|
}
|
|
26
49
|
}
|
package/kimicode.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_note": "Kimi Code MCP configuration. Copy this file or add the sage-governance block to your Kimi Code MCP server settings.",
|
|
3
|
+
"_docs": "https://kimi.moonshot.cn",
|
|
4
|
+
"_compatible_with": "Kimi Code",
|
|
5
|
+
"mcpServers": {
|
|
6
|
+
"sage-governance": {
|
|
7
|
+
"command": "sage",
|
|
8
|
+
"args": [],
|
|
9
|
+
"type": "stdio",
|
|
10
|
+
"env": {
|
|
11
|
+
"OPENAI_API_KEY": "${OPENAI_API_KEY}",
|
|
12
|
+
"SAGE_LLM_MODEL": "gpt-4o-mini"
|
|
13
|
+
},
|
|
14
|
+
"duckduckgo-search": {
|
|
15
|
+
"type": "local",
|
|
16
|
+
"command": ["uvx", "duckduckgo-mcp-server"],
|
|
17
|
+
"enabled": true,
|
|
18
|
+
"timeout": 10000
|
|
19
|
+
},
|
|
20
|
+
"github": {
|
|
21
|
+
"type": "local",
|
|
22
|
+
"command": [
|
|
23
|
+
"docker",
|
|
24
|
+
"run",
|
|
25
|
+
"-i",
|
|
26
|
+
"--rm",
|
|
27
|
+
"-e",
|
|
28
|
+
"GITHUB_PERSONAL_ACCESS_TOKEN",
|
|
29
|
+
"ghcr.io/github/github-mcp-server"
|
|
30
|
+
],
|
|
31
|
+
"enabled": false,
|
|
32
|
+
"environment": {
|
|
33
|
+
"GITHUB_PERSONAL_ACCESS_TOKEN": "{env:GITHUB_PERSONAL_ACCESS_TOKEN}"
|
|
34
|
+
},
|
|
35
|
+
"timeout": 10000
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
package/opencode.json
CHANGED
|
@@ -1,14 +1,37 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$schema": "https://opencode.ai/config.
|
|
3
|
-
"model": "
|
|
4
|
-
"
|
|
2
|
+
"$schema": "https://opencode.ai/config.json",
|
|
3
|
+
"model": "openai/gpt-4o-mini",
|
|
4
|
+
"mcp": {
|
|
5
5
|
"sage-governance": {
|
|
6
6
|
"type": "local",
|
|
7
|
-
"command": "sage",
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"OPENAI_API_KEY": "
|
|
7
|
+
"command": ["sage"],
|
|
8
|
+
"enabled": true,
|
|
9
|
+
"environment": {
|
|
10
|
+
"OPENAI_API_KEY": "(env:OPENAI_API_KEY)",
|
|
11
11
|
"SAGE_LLM_MODEL": "gpt-4o-mini"
|
|
12
|
+
},
|
|
13
|
+
"duckduckgo-search": {
|
|
14
|
+
"type": "local",
|
|
15
|
+
"command": ["uvx", "duckduckgo-mcp-server"],
|
|
16
|
+
"enabled": true,
|
|
17
|
+
"timeout": 10000
|
|
18
|
+
},
|
|
19
|
+
"github": {
|
|
20
|
+
"type": "local",
|
|
21
|
+
"command": [
|
|
22
|
+
"docker",
|
|
23
|
+
"run",
|
|
24
|
+
"-i",
|
|
25
|
+
"--rm",
|
|
26
|
+
"-e",
|
|
27
|
+
"GITHUB_PERSONAL_ACCESS_TOKEN",
|
|
28
|
+
"ghcr.io/github/github-mcp-server"
|
|
29
|
+
],
|
|
30
|
+
"enabled": false,
|
|
31
|
+
"environment": {
|
|
32
|
+
"GITHUB_PERSONAL_ACCESS_TOKEN": "{env:GITHUB_PERSONAL_ACCESS_TOKEN}"
|
|
33
|
+
},
|
|
34
|
+
"timeout": 10000
|
|
12
35
|
}
|
|
13
36
|
}
|
|
14
37
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sage-governance",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Supervisory Agentic Governance Engine — Open-source MCP governance layer for agentic coding systems. Intercepts, evaluates, and audits AI coding prompts for EU AI Act, GDPR, and fairness compliance.",
|
|
5
5
|
"main": "bin/sage.js",
|
|
6
6
|
"bin": {
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"start": "node bin/sage.js",
|
|
11
|
-
"
|
|
12
|
-
"postinstall": "node -
|
|
11
|
+
"prepublishOnly": "find . -path './.git' -prune -o -name '__pycache__' -type d -print -exec rm -rf {} + 2>/dev/null; echo 'pycache cleaned'",
|
|
12
|
+
"postinstall": "node scripts/bootstrap-python.js"
|
|
13
13
|
},
|
|
14
14
|
"files": [
|
|
15
15
|
"bin/",
|
|
@@ -23,7 +23,13 @@
|
|
|
23
23
|
"opencode.json",
|
|
24
24
|
"claude.json",
|
|
25
25
|
"cursor.json",
|
|
26
|
-
"codex.json"
|
|
26
|
+
"codex.json",
|
|
27
|
+
"windsurf.json",
|
|
28
|
+
"trae.json",
|
|
29
|
+
"kimicode.json",
|
|
30
|
+
"Contributors.md",
|
|
31
|
+
"security.md",
|
|
32
|
+
"scripts/"
|
|
27
33
|
],
|
|
28
34
|
"keywords": [
|
|
29
35
|
"mcp",
|
|
@@ -41,7 +47,7 @@
|
|
|
41
47
|
"responsible-ai",
|
|
42
48
|
"model-card"
|
|
43
49
|
],
|
|
44
|
-
"author": "SAGE Team
|
|
50
|
+
"author": "SAGE Team",
|
|
45
51
|
"license": "MIT",
|
|
46
52
|
"repository": {
|
|
47
53
|
"type": "git",
|
|
@@ -55,4 +61,4 @@
|
|
|
55
61
|
"engines": {
|
|
56
62
|
"node": ">=16.0.0"
|
|
57
63
|
}
|
|
58
|
-
}
|
|
64
|
+
}
|
package/sage/sage_agent.py
CHANGED
|
@@ -28,6 +28,7 @@ import sys
|
|
|
28
28
|
from typing import Any, Literal, Optional
|
|
29
29
|
|
|
30
30
|
from pydantic import BaseModel, field_validator
|
|
31
|
+
__all__: list[str] = []
|
|
31
32
|
|
|
32
33
|
# startup is always imported first — it pre-loads all globals
|
|
33
34
|
from startup import (
|
|
@@ -37,6 +38,7 @@ from startup import (
|
|
|
37
38
|
LLM_CLIENT,
|
|
38
39
|
LLM_MODEL,
|
|
39
40
|
POLICY_INDEX,
|
|
41
|
+
POLICY_DOCS,
|
|
40
42
|
PROTECTED_ATTRIBUTES,
|
|
41
43
|
PROXY_ATTRIBUTE_MAP,
|
|
42
44
|
UDHR_ARTICLE_MAP,
|
|
@@ -597,13 +599,24 @@ def _enrich_with_llm(prompt: str, det: dict[str, Any]) -> str:
|
|
|
597
599
|
if not LLM_AVAILABLE:
|
|
598
600
|
return fallback
|
|
599
601
|
|
|
602
|
+
domain_info = POLICY_INDEX.get(det['domain'], POLICY_INDEX.get('general', {}))
|
|
603
|
+
policy_files = domain_info.get('files', [])
|
|
604
|
+
policy_texts = []
|
|
605
|
+
for pf in policy_files:
|
|
606
|
+
if pf in POLICY_DOCS:
|
|
607
|
+
# Inject up to 2500 chars per policy to ensure context fits
|
|
608
|
+
policy_texts.append(f"--- {pf} ---\n{POLICY_DOCS[pf][:2500]}")
|
|
609
|
+
|
|
610
|
+
policies_context = "\n\n".join(policy_texts)
|
|
611
|
+
|
|
600
612
|
system = (
|
|
601
613
|
"You are SAGE, a governance agent for AI systems. Given a developer's coding "
|
|
602
614
|
"prompt and a preliminary risk classification, write a concise 2-3 sentence "
|
|
603
615
|
"explanation of WHY this prompt raises ethical or regulatory concerns. "
|
|
604
|
-
"Reference specific laws, principles, or documented real-world cases. "
|
|
616
|
+
"Reference specific laws, principles, or documented real-world cases from the provided policy documents. "
|
|
605
617
|
"Be factual and precise. Output ONLY the explanation — no JSON, no preamble, "
|
|
606
|
-
"no bullet points
|
|
618
|
+
"no bullet points.\n\n"
|
|
619
|
+
f"RELEVANT POLICIES:\n{policies_context}"
|
|
607
620
|
)
|
|
608
621
|
user = (
|
|
609
622
|
f"Developer prompt: \"{prompt}\"\n\n"
|
|
@@ -619,7 +632,7 @@ def _enrich_with_llm(prompt: str, det: dict[str, Any]) -> str:
|
|
|
619
632
|
try:
|
|
620
633
|
response = LLM_CLIENT.chat.completions.create(
|
|
621
634
|
model=LLM_MODEL,
|
|
622
|
-
max_tokens=
|
|
635
|
+
max_tokens=1500,
|
|
623
636
|
messages=[
|
|
624
637
|
{"role": "system", "content": system},
|
|
625
638
|
{"role": "user", "content": user},
|
|
@@ -708,3 +721,35 @@ def log_model_metrics(metrics: dict[str, Any], dataset_info: Optional[dict[str,
|
|
|
708
721
|
"dataset_info": dataset_info or {},
|
|
709
722
|
}
|
|
710
723
|
return write_audit_entry(entry)
|
|
724
|
+
|
|
725
|
+
# ----- Added utilities to activate previously unused imports and variables -----
|
|
726
|
+
|
|
727
|
+
if FAIRLEARN_AVAILABLE:
|
|
728
|
+
# expose Fairlearn metrics when available
|
|
729
|
+
from fairlearn.metrics import demographic_parity_difference, equalized_odds_difference, MetricFrame
|
|
730
|
+
__all__ += ["demographic_parity_difference", "equalized_odds_difference", "MetricFrame"]
|
|
731
|
+
else:
|
|
732
|
+
# placeholder functions raising informative errors
|
|
733
|
+
def _fairlearn_unavailable(*_args, **_kwargs):
|
|
734
|
+
raise ImportError("Fairlearn is not installed. Install it to use fairness metrics.")
|
|
735
|
+
demographic_parity_difference = _fairlearn_unavailable
|
|
736
|
+
equalized_odds_difference = _fairlearn_unavailable
|
|
737
|
+
MetricFrame = _fairlearn_unavailable
|
|
738
|
+
|
|
739
|
+
def list_fairness_options() -> dict[str, "FairnessOption"]:
|
|
740
|
+
"""Return a dictionary of all defined fairness options.
|
|
741
|
+
This makes the previously defined _FAIRNESS_LIBRARY actively used.
|
|
742
|
+
"""
|
|
743
|
+
return _FAIRNESS_LIBRARY.copy()
|
|
744
|
+
|
|
745
|
+
def get_fairness_option(name: str) -> "FairnessOption":
|
|
746
|
+
"""Retrieve a specific FairnessOption by name.
|
|
747
|
+
Raises KeyError if the option does not exist.
|
|
748
|
+
"""
|
|
749
|
+
try:
|
|
750
|
+
return _FAIRNESS_LIBRARY[name]
|
|
751
|
+
except KeyError as exc:
|
|
752
|
+
raise KeyError(f"Fairness option '{name}' not found. Available options: {list(_FAIRNESS_LIBRARY.keys())}") from exc
|
|
753
|
+
|
|
754
|
+
# Ensure the new utilities are part of the module's public API
|
|
755
|
+
__all__ += ["list_fairness_options", "get_fairness_option"]
|
package/sage/security_agent.py
CHANGED
|
@@ -80,7 +80,7 @@ class SecurityReport:
|
|
|
80
80
|
# (regex, description, severity)
|
|
81
81
|
_SECRET_PATTERNS: list[tuple[str, str, Severity]] = [
|
|
82
82
|
# AI provider keys
|
|
83
|
-
(r"sk-ant-[A-Za-z0-9\-_]{40,}", "
|
|
83
|
+
(r"sk-ant-[A-Za-z0-9\-_]{40,}", "AI Provider API key hardcoded", "P0"),
|
|
84
84
|
(r"sk-[A-Za-z0-9]{48}", "OpenAI API key hardcoded", "P0"),
|
|
85
85
|
(r"AIza[0-9A-Za-z\-_]{35}", "Google API key hardcoded", "P0"),
|
|
86
86
|
# Generic credentials
|
package/sage/startup.py
CHANGED
|
@@ -28,11 +28,13 @@ from typing import Any
|
|
|
28
28
|
_THIS_FILE = pathlib.Path(__file__).resolve()
|
|
29
29
|
PROJECT_ROOT = _THIS_FILE.parent.parent
|
|
30
30
|
|
|
31
|
+
WORKSPACE_ROOT = pathlib.Path(os.getcwd()).resolve()
|
|
32
|
+
|
|
31
33
|
RULES_DIR = PROJECT_ROOT / "rules"
|
|
32
|
-
AUDIT_FILE =
|
|
33
|
-
LOGS_FILE =
|
|
34
|
-
LOCAL_MEMORY =
|
|
35
|
-
REPORTS_DIR =
|
|
34
|
+
AUDIT_FILE = WORKSPACE_ROOT / "audit-trail" / "decisions.jsonl"
|
|
35
|
+
LOGS_FILE = WORKSPACE_ROOT / "LOGS.md"
|
|
36
|
+
LOCAL_MEMORY = WORKSPACE_ROOT / "local_memory.md"
|
|
37
|
+
REPORTS_DIR = WORKSPACE_ROOT / "reports"
|
|
36
38
|
|
|
37
39
|
# ── Ensure required dirs & files exist ───────────────────────────────────────
|
|
38
40
|
for _p in (AUDIT_FILE.parent, REPORTS_DIR):
|
|
@@ -62,8 +64,9 @@ def write_audit_entry(entry: dict) -> str:
|
|
|
62
64
|
prev_hash = last_entry.get("entry_hash", "")
|
|
63
65
|
if not session_id:
|
|
64
66
|
session_id = last_entry.get("session_id")
|
|
65
|
-
except Exception:
|
|
66
|
-
|
|
67
|
+
except Exception as e:
|
|
68
|
+
import sys
|
|
69
|
+
print(f"[SAGE] Error reading audit file for chain hash: {e}", file=sys.stderr)
|
|
67
70
|
|
|
68
71
|
if not session_id:
|
|
69
72
|
session_id = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S")
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// scripts/bootstrap-python.js
|
|
3
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
4
|
+
// Runs automatically on `npm install -g sage-governance` (postinstall hook).
|
|
5
|
+
// Creates a private Python virtual environment and installs requirements.
|
|
6
|
+
// Re-installs only when requirements.txt has changed (hash-checked).
|
|
7
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
const { spawnSync } = require('child_process');
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const os = require('os');
|
|
13
|
+
const crypto = require('crypto');
|
|
14
|
+
|
|
15
|
+
const root = path.resolve(__dirname, '..');
|
|
16
|
+
const reqPath = path.join(root, 'requirements.txt');
|
|
17
|
+
|
|
18
|
+
// Private venv lives in ~/.cache/sage-governance/venv (cross-platform friendly)
|
|
19
|
+
const cacheDir = path.join(os.homedir(), '.cache', 'sage-governance');
|
|
20
|
+
const venvDir = path.join(cacheDir, 'venv');
|
|
21
|
+
const lockFile = path.join(cacheDir, 'requirements.lock');
|
|
22
|
+
|
|
23
|
+
// ── helpers ──────────────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
function banner(msg) {
|
|
26
|
+
console.log(`\x1b[36m[SAGE bootstrap]\x1b[0m ${msg}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function success(msg) {
|
|
30
|
+
console.log(`\x1b[32m[SAGE bootstrap] ✔\x1b[0m ${msg}`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function warn(msg) {
|
|
34
|
+
console.warn(`\x1b[33m[SAGE bootstrap] ⚠\x1b[0m ${msg}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function fatal(msg) {
|
|
38
|
+
console.error(`\x1b[31m[SAGE bootstrap] ✖\x1b[0m ${msg}`);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function run(cmd, args, opts = {}) {
|
|
43
|
+
const result = spawnSync(cmd, args, { stdio: 'inherit', ...opts });
|
|
44
|
+
if (result.error) fatal(`Failed to run '${cmd}': ${result.error.message}`);
|
|
45
|
+
if (result.status !== 0) fatal(`'${cmd} ${args.join(' ')}' exited with code ${result.status}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ── locate Python 3 ──────────────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
function findPython3() {
|
|
51
|
+
const candidates = process.platform === 'win32'
|
|
52
|
+
? ['python', 'python3', 'py']
|
|
53
|
+
: ['python3', 'python'];
|
|
54
|
+
|
|
55
|
+
for (const exe of candidates) {
|
|
56
|
+
const res = spawnSync(exe, ['--version'], { stdio: 'pipe' });
|
|
57
|
+
if (res.status === 0) {
|
|
58
|
+
const ver = (res.stdout || res.stderr || Buffer.alloc(0)).toString().trim();
|
|
59
|
+
// Must be Python 3
|
|
60
|
+
if (/Python 3\./.test(ver)) return exe;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ── venv python path (OS-aware) ───────────────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
const venvPython = process.platform === 'win32'
|
|
69
|
+
? path.join(venvDir, 'Scripts', 'python.exe')
|
|
70
|
+
: path.join(venvDir, 'bin', 'python');
|
|
71
|
+
|
|
72
|
+
// ── hash requirements.txt ────────────────────────────────────────────────────
|
|
73
|
+
|
|
74
|
+
function reqHash() {
|
|
75
|
+
if (!fs.existsSync(reqPath)) return null;
|
|
76
|
+
return crypto.createHash('sha256').update(fs.readFileSync(reqPath)).digest('hex');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function savedHash() {
|
|
80
|
+
if (!fs.existsSync(lockFile)) return null;
|
|
81
|
+
return fs.readFileSync(lockFile, 'utf8').trim();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ── main ─────────────────────────────────────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
banner('Checking Python environment for SAGE governance runtime…');
|
|
87
|
+
|
|
88
|
+
// Ensure requirements.txt exists
|
|
89
|
+
if (!fs.existsSync(reqPath)) {
|
|
90
|
+
warn('requirements.txt not found — skipping Python setup.');
|
|
91
|
+
process.exit(0);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Check if re-install is needed
|
|
95
|
+
const currentHash = reqHash();
|
|
96
|
+
const prevHash = savedHash();
|
|
97
|
+
const venvReady = fs.existsSync(venvPython);
|
|
98
|
+
|
|
99
|
+
if (venvReady && currentHash === prevHash) {
|
|
100
|
+
success('Python environment is up to date. No action needed.');
|
|
101
|
+
process.exit(0);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Locate Python 3 on the host
|
|
105
|
+
const python3 = findPython3();
|
|
106
|
+
if (!python3) {
|
|
107
|
+
fatal(
|
|
108
|
+
'Python 3 not found in PATH.\n' +
|
|
109
|
+
' Please install Python 3.9+ from https://python.org and re-run:\n' +
|
|
110
|
+
' npm install -g sage-governance'
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Create or recreate venv
|
|
115
|
+
if (!venvReady) {
|
|
116
|
+
banner(`Creating virtual environment at ${venvDir} …`);
|
|
117
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
118
|
+
run(python3, ['-m', 'venv', venvDir]);
|
|
119
|
+
success('Virtual environment created.');
|
|
120
|
+
} else {
|
|
121
|
+
banner('requirements.txt has changed — refreshing dependencies…');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Upgrade pip silently
|
|
125
|
+
banner('Upgrading pip…');
|
|
126
|
+
run(venvPython, ['-m', 'pip', 'install', '--upgrade', 'pip', '-q']);
|
|
127
|
+
|
|
128
|
+
// Install requirements
|
|
129
|
+
banner('Installing Python dependencies from requirements.txt…');
|
|
130
|
+
run(venvPython, ['-m', 'pip', 'install', '-r', reqPath]);
|
|
131
|
+
|
|
132
|
+
// Write lock file
|
|
133
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
134
|
+
fs.writeFileSync(lockFile, currentHash);
|
|
135
|
+
|
|
136
|
+
success('Python environment ready.');
|
|
137
|
+
banner(`Venv location : ${venvDir}`);
|
|
138
|
+
banner(`Lock written : ${lockFile}`);
|
package/security.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Reporting a Vulnerability
|
|
4
|
+
|
|
5
|
+
If you discover a security vulnerability within SAGE, please do not open a public issue. Instead, report it privately.
|
|
6
|
+
|
|
7
|
+
Please contact the maintainers directly at security@olustar.io.
|
|
8
|
+
|
|
9
|
+
We will acknowledge your report within 48 hours and provide a timeline for fixing the issue.
|
package/trae.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_note": "Trae MCP configuration. Copy this file or add the sage-governance block to your Trae MCP server settings.",
|
|
3
|
+
"_docs": "https://trae.sh",
|
|
4
|
+
"_compatible_with": "Trae",
|
|
5
|
+
"mcpServers": {
|
|
6
|
+
"sage-governance": {
|
|
7
|
+
"command": "sage",
|
|
8
|
+
"args": [],
|
|
9
|
+
"type": "stdio",
|
|
10
|
+
"env": {
|
|
11
|
+
"OPENAI_API_KEY": "${OPENAI_API_KEY}",
|
|
12
|
+
"SAGE_LLM_MODEL": "gpt-4o-mini"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"duckduckgo-search": {
|
|
16
|
+
"type": "local",
|
|
17
|
+
"command": ["uvx", "duckduckgo-mcp-server"],
|
|
18
|
+
"enabled": true,
|
|
19
|
+
"timeout": 10000
|
|
20
|
+
},
|
|
21
|
+
"github": {
|
|
22
|
+
"type": "local",
|
|
23
|
+
"command": [
|
|
24
|
+
"docker",
|
|
25
|
+
"run",
|
|
26
|
+
"-i",
|
|
27
|
+
"--rm",
|
|
28
|
+
"-e",
|
|
29
|
+
"GITHUB_PERSONAL_ACCESS_TOKEN",
|
|
30
|
+
"ghcr.io/github/github-mcp-server"
|
|
31
|
+
],
|
|
32
|
+
"enabled": false,
|
|
33
|
+
"environment": {
|
|
34
|
+
"GITHUB_PERSONAL_ACCESS_TOKEN": "{env:GITHUB_PERSONAL_ACCESS_TOKEN}"
|
|
35
|
+
},
|
|
36
|
+
"timeout": 10000
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
package/windsurf.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_note": "Windsurf MCP configuration. Copy this file or add the sage-governance block to ~/.codeium/windsurf/mcp_config.json",
|
|
3
|
+
"_docs": "https://codeium.com/windsurf",
|
|
4
|
+
"_compatible_with": "Windsurf",
|
|
5
|
+
"mcpServers": {
|
|
6
|
+
"sage-governance": {
|
|
7
|
+
"command": "sage",
|
|
8
|
+
"args": [],
|
|
9
|
+
"env": {
|
|
10
|
+
"OPENAI_API_KEY": "${OPENAI_API_KEY}",
|
|
11
|
+
"SAGE_LLM_MODEL": "gpt-4o-mini"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"duckduckgo-search": {
|
|
15
|
+
"type": "local",
|
|
16
|
+
"command": ["uvx", "duckduckgo-mcp-server"],
|
|
17
|
+
"enabled": true,
|
|
18
|
+
"timeout": 10000
|
|
19
|
+
},
|
|
20
|
+
"github": {
|
|
21
|
+
"type": "local",
|
|
22
|
+
"command": [
|
|
23
|
+
"docker",
|
|
24
|
+
"run",
|
|
25
|
+
"-i",
|
|
26
|
+
"--rm",
|
|
27
|
+
"-e",
|
|
28
|
+
"GITHUB_PERSONAL_ACCESS_TOKEN",
|
|
29
|
+
"ghcr.io/github/github-mcp-server"
|
|
30
|
+
],
|
|
31
|
+
"enabled": false,
|
|
32
|
+
"environment": {
|
|
33
|
+
"GITHUB_PERSONAL_ACCESS_TOKEN": "{env:GITHUB_PERSONAL_ACCESS_TOKEN}"
|
|
34
|
+
},
|
|
35
|
+
"timeout": 10000
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|