Skip to content

Commit d8ce702

Browse files
committed
P0: security scans + stable tool envelopes + doctor tool
1 parent da1dfcf commit d8ce702

5 files changed

Lines changed: 211 additions & 143 deletions

File tree

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: Security scan (report-only)
2+
3+
on:
4+
push:
5+
branches: ["**"]
6+
pull_request:
7+
8+
permissions:
9+
contents: read
10+
11+
jobs:
12+
gitleaks:
13+
name: gitleaks (secrets)
14+
runs-on: ubuntu-latest
15+
continue-on-error: true
16+
steps:
17+
- uses: actions/checkout@v4
18+
with:
19+
fetch-depth: 0
20+
- name: Run gitleaks (report-only)
21+
uses: gitleaks/gitleaks-action@v2
22+
env:
23+
GITLEAKS_ENABLE_UPLOAD_ARTIFACT: "true"
24+
GITLEAKS_ENABLE_SUMMARY: "true"
25+
26+
semgrep:
27+
name: semgrep (sast)
28+
runs-on: ubuntu-latest
29+
continue-on-error: true
30+
steps:
31+
- uses: actions/checkout@v4
32+
- name: Run semgrep (report-only)
33+
uses: semgrep/semgrep-action@v1
34+
with:
35+
config: "auto"
36+

README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Use it from **Cursor**, **VS Code MCP**, **Claude Desktop**, or **Windsurf/Cline
1414
- **Session / Config**
1515
- `ethora-configure` — set API URL + App JWT (in-memory for this MCP session)
1616
- `ethora-status` — show configured API URL + whether auth tokens are present
17+
- `ethora-doctor` — validate config + ping the configured Ethora API
1718
- `ethora-app-select` — select current appId and optionally set appToken (B2B)
1819
- `ethora-auth-use-app` — switch to app-token auth mode (B2B)
1920
- `ethora-auth-use-user` — switch to user-session auth mode
@@ -195,7 +196,7 @@ You can provide these either:
195196
After the server shows as **connected** in your client:
196197

197198
- Run `list tools` (client command) to verify Ethora tools are available.
198-
- Check config: call `ethora-status`
199+
- Check config/connectivity: call `ethora-doctor` (or `ethora-status`)
199200
- Configure (optional): call `ethora-configure` with `apiUrl` / `appJwt`
200201
- Try a login: call `ethora-user-login`
201202
- List applications: call `ethora-app-list`
@@ -209,6 +210,12 @@ After the server shows as **connected** in your client:
209210
- Use **least privilege** keys and consider **allowlists/rate limits** on your Ethora backend.
210211
- Rotate credentials regularly in production use.
211212

213+
### CI security scans (report-only)
214+
215+
This repo runs **report-only** scans on pushes/PRs:
216+
- **gitleaks** for secret scanning
217+
- **semgrep** for basic SAST
218+
212219
---
213220

214221
## 🧰 Development

src/apiClientDappros.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ export const httpClientDappros = axios.create({
2929
baseURL: appConfig.apiUrl,
3030
});
3131

32+
// Small helper used by `ethora-doctor`
33+
export async function apiPing(timeoutMs = 3000) {
34+
return httpClientDappros.get("/ping", { timeout: timeoutMs })
35+
}
36+
3237
httpClientDappros.interceptors.request.use((config) => {
3338
// If the user provides a full URL, don't attempt to mutate headers.
3439
if (!config.url) return config

src/mcpResponse.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
export type McpEnvelope = {
2+
ok: boolean
3+
ts: string
4+
meta?: Record<string, any>
5+
data?: any
6+
error?: {
7+
message: string
8+
kind?: string
9+
status?: number
10+
details?: any
11+
}
12+
}
13+
14+
function isoNow() {
15+
return new Date().toISOString()
16+
}
17+
18+
function parseAxiosishError(error: unknown): { message: string; status?: number; details?: any } {
19+
if (error && typeof error === "object" && "response" in error) {
20+
const e = error as any
21+
const status = e.response?.status
22+
const data = e.response?.data
23+
const msg = e.message || "request failed"
24+
return { message: msg, status, details: data }
25+
}
26+
if (error instanceof Error) return { message: error.message }
27+
return { message: String(error) }
28+
}
29+
30+
export function ok(data: any, meta?: Record<string, any>): McpEnvelope {
31+
return { ok: true, ts: isoNow(), meta, data }
32+
}
33+
34+
export function fail(error: unknown, meta?: Record<string, any>): McpEnvelope {
35+
const parsed = parseAxiosishError(error)
36+
return {
37+
ok: false,
38+
ts: isoNow(),
39+
meta,
40+
error: {
41+
message: parsed.message,
42+
status: parsed.status,
43+
details: parsed.details,
44+
},
45+
}
46+
}
47+
48+

0 commit comments

Comments
 (0)