Skip to content

Commit 5667376

Browse files
h0tak88rclaude
andcommitted
feat(ai): switch default AI provider to OpenCode Zen, add model overrides
The previously-default `stepfun/step-3.5-flash:free` (and the README-claimed `z-ai/glm-4.5-air:free`) is no longer free on OpenRouter. AutoAR now defaults to OpenCode Zen's free tier; users can still bring their own OpenRouter or Gemini key, and both endpoints' models are now overridable from env or UI. brain.go: - New constants OpenCodeEndpoint + DefaultOpenCodeModel ("deepseek-v4-flash-free"). - Renamed DefaultModel -> DefaultOpenRouterModel for clarity. - openRouterModel() / openCodeModel() helpers read OPENROUTER_MODEL / OPENCODE_MODEL env vars with fallback to the constants. - New callOpenCode + callOpenCodeMulti — OpenAI-compatible POSTs reusing the existing OpenRouterRequest/Response shapes (no Referer/X-Title headers). - Provider chain: OpenRouter -> OpenCode -> Z.ai -> Gemini. Each tries on its own key; failures fall through to the next configured provider. - hasAnyAIKey() + errNoAIKey consolidate 4 precondition checks. ui_api.go: - GET /api/config now returns opencode_key_set / openrouter_key_set / gemini_key_set (booleans only — keys never leak) and the effective opencode_model / openrouter_model values. - monitor_ai_available + the internal `ai` health flag count OpenCode. - POST /api/settings accepts opencode_key, opencode_model, openrouter_model. Model fields use pointer-string so `""` or `"default"` clears the override while omitting keeps the current value. settings.js: - New inputs in the AI Intelligence section: OpenCode API Key, OpenCode Model, OpenRouter Model. Each provider row shows a configured/not-set badge driven by the new *_key_set flags. - Saved keys are masked with `••••••• (saved)` placeholders rather than echoed into the DOM. After save, /api/config is re-fetched and the page re-renders. - New handlers saveOpenCodeKey, saveOpenCodeModel, saveOpenRouterModel. docker-compose.yml: pass through OPENCODE_API_KEY, OPENCODE_MODEL, OPENROUTER_API_KEY, OPENROUTER_MODEL, ZHIPU_API_KEY (previously only GEMINI_API_KEY was explicit; the rest worked via env_file but emitted "variable not set" warnings). env.example: new AI/LLM PROVIDERS section documenting all four keys, both model overrides, and the provider priority order. config.go: env allowlist now includes OPENCODE_API_KEY, OPENCODE_MODEL, OPENROUTER_MODEL. monitorsuggest/suggest.go: AI-rank gate also accepts OPENCODE_API_KEY. README.md: rewrote the AI section around OpenCode Zen as the default free path, documented the new env vars, added a "Configuring from the dashboard" note, and updated the Docker env snippet. Build: go build ./... clean. Tests: brain, logger, utils, monitorsuggest, and config packages all pass. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent fdf4d3c commit 5667376

8 files changed

Lines changed: 345 additions & 50 deletions

File tree

README.md

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ Results are automatically uploaded to **Cloudflare R2 storage** and linked direc
4040
| **Misconfigs** | 100+ service misconfiguration checks |
4141
| **BB Scope** | Fetch scope from HackerOne, Bugcrowd, Intigriti, YesWeHack (token), Immunefi — CLI & **dashboard Targets page** |
4242
| **Monitoring** | Subdomain + URL change monitoring daemon with webhook alerts (Discord-compatible) & DB history |
43-
| **AI Agent** | Full AI hunt loop from the CLI (`autoar agent` / `autoar explain`) — powered by **z-ai/glm-4.5-air:free via OpenRouter**zero cost required |
43+
| **AI Agent** | Full AI hunt loop from the CLI (`autoar agent` / `autoar explain`) — defaults to **deepseek-v4-flash-free via OpenCode Zen**free tier, no card required |
4444
| **R2 Storage** | Auto-upload every non-empty result file to Cloudflare R2 and print the public URL |
4545
| **Smart Alerts** | Rich webhook notifications for zero-findings scans — no more empty files or spam |
4646
| **Web dashboard** | **v4.1+** — Stats, scans, domains, monitors, R2 browser, Targets, APK/IPA/ADB Auditors, MITM remote scan, CF-1016 findings. Unified findings table with per-module columns, inline expandable detail panels, severity/chip/multi-field filters |
@@ -351,9 +351,9 @@ autoar status [--json]
351351

352352
## AI-Driven Security Framework — Free for Everyone
353353

354-
As of the latest release, AutoAR's AI engine runs on `**[stepfun/step-3.5-flash:free](https://openrouter.ai/stepfun/step-3.5-flash:free)**` via [OpenRouter](https://openrouter.ai). This is a **completely free model** — no credits, no billing required.
354+
AutoAR's AI engine defaults to **[OpenCode Zen](https://opencode.ai/zen)** running `deepseek-v4-flash-free`. OpenCode Zen is OpenAI-compatible and offers a free tier with no card required. You can also bring your own OpenRouter or Gemini key — AutoAR routes to whichever providers you configure.
355355

356-
> **Every AutoAR user can now access a full AI-driven bug bounty framework at zero cost** — just sign up for a free OpenRouter account and paste your key into `.env`.
356+
> **Free path in 60 seconds:** create a free OpenCode Zen account, copy your key into `OPENCODE_API_KEY`, and the agent works.
357357
358358
### What the AI powers
359359

@@ -365,19 +365,43 @@ As of the latest release, AutoAR's AI engine runs on `**[stepfun/step-3.5-flash:
365365
| `autoar explain <result-file>` | Feed any result file to the AI for triage and follow-up suggestions. |
366366

367367

368-
### Getting your free OpenRouter key
368+
### Getting your free OpenCode Zen key
369369

370-
1. Go to [openrouter.ai](https://openrouter.ai) and create a **free account** (no credit card required for free models)
371-
2. Navigate to **Keys****Create Key**
372-
3. Copy your key and add it to `.env`:
370+
1. Go to [opencode.ai/zen](https://opencode.ai/zen) and create a free account.
371+
2. Generate an API key from the dashboard.
372+
3. Drop it into `.env`:
373+
374+
```env
375+
OPENCODE_API_KEY=oc-...
376+
# Optional model override — leave unset for the default deepseek-v4-flash-free.
377+
# Browse https://opencode.ai/zen/v1/models for the full list.
378+
OPENCODE_MODEL=
379+
```
380+
381+
That's it. AutoAR will use `deepseek-v4-flash-free` automatically for `autoar agent` and `autoar explain`.
382+
383+
### Bringing your own OpenRouter key (optional)
384+
385+
If you want to use OpenRouter (paid premium models or alternative free models):
373386

374387
```env
375388
OPENROUTER_API_KEY=sk-or-v1-...
389+
# Optional model override — leave unset for z-ai/glm-4.5-air:free.
390+
OPENROUTER_MODEL=
376391
```
377392

378-
That's it. AutoAR will automatically use `z-ai/glm-4.5-air:free` for `autoar agent` and `autoar explain`.
393+
### Provider priority
379394

380-
> **Tip:** If `OPENROUTER_API_KEY` is set, it is used first (with `z-ai/glm-4.5-air:free`). `ZHIPU_API_KEY` routes to the Z.ai direct endpoint. `GEMINI_API_KEY` is a final fallback. You only need one of the three.
395+
When multiple keys are configured, AutoAR tries them in this order — each provider falls back to the next on error:
396+
397+
1. `OPENROUTER_API_KEY` → OpenRouter (`OPENROUTER_MODEL` or `z-ai/glm-4.5-air:free`)
398+
2. `OPENCODE_API_KEY` → OpenCode Zen (`OPENCODE_MODEL` or `deepseek-v4-flash-free`)
399+
3. `ZHIPU_API_KEY` → Z.ai direct endpoint
400+
4. `GEMINI_API_KEY` → Gemini Direct
401+
402+
### Configuring from the dashboard
403+
404+
The web dashboard (`/ui/settings`) exposes all four keys and the two model overrides. You can paste keys, change models, and reset to defaults without touching `.env`. Existing keys are masked in the input — leave the field blank to keep the saved value, type a new value to overwrite.
381405

382406
### Database & Results
383407

@@ -547,11 +571,16 @@ H1_API_KEY=... # HackerOne
547571
INTEGRITI_API_KEY=... # Intigriti
548572
549573
# AI analysis — only ONE key is needed
550-
# Recommended: OpenRouter free tier (no credit card required)
551-
# Sign up at https://openrouter.ai · Uses stepfun/step-3.5-flash:free automatically
552-
OPENROUTER_API_KEY=... # Powers `autoar agent` and `autoar explain` — completely free
574+
# Recommended (free): OpenCode Zen (no credit card required)
575+
# Sign up at https://opencode.ai/zen · uses deepseek-v4-flash-free automatically
576+
OPENCODE_API_KEY=oc-... # Powers `autoar agent` and `autoar explain` — completely free
577+
# OPENCODE_MODEL= # Optional override; see https://opencode.ai/zen/v1/models
553578
554-
# Optional fallback: direct Gemini API (only if you don't use OpenRouter)
579+
# Optional: OpenRouter for premium / alternative models
580+
OPENROUTER_API_KEY=...
581+
# OPENROUTER_MODEL= # Optional override; defaults to z-ai/glm-4.5-air:free
582+
583+
# Optional fallback: direct Gemini API
555584
GEMINI_API_KEY=...
556585
```
557586

@@ -708,7 +737,7 @@ AUTOAR_RESULTS_DIR=/app/new-results
708737
DB_TYPE=postgresql
709738
DB_HOST=postgresql://autoar:autoar@postgres:5432/bughunt?sslmode=disable
710739
USE_R2_STORAGE=true
711-
OPENROUTER_API_KEY=...
740+
OPENCODE_API_KEY=oc-... # or OPENROUTER_API_KEY / GEMINI_API_KEY
712741
```
713742

714743
---

docker-compose.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,13 @@ services:
119119
- R2_ACCESS_KEY_ID=${R2_ACCESS_KEY_ID:-}
120120
- R2_SECRET_KEY=${R2_SECRET_KEY:-}
121121
- R2_PUBLIC_URL=${R2_PUBLIC_URL:-}
122+
# AI / LLM providers — set whichever you use. See README "AI-Driven Security Framework".
123+
- OPENCODE_API_KEY=${OPENCODE_API_KEY:-}
124+
- OPENCODE_MODEL=${OPENCODE_MODEL:-}
125+
- OPENROUTER_API_KEY=${OPENROUTER_API_KEY:-}
126+
- OPENROUTER_MODEL=${OPENROUTER_MODEL:-}
122127
- GEMINI_API_KEY=${GEMINI_API_KEY:-}
128+
- ZHIPU_API_KEY=${ZHIPU_API_KEY:-}
123129

124130
ports:
125131
- "8000:8000" # Container listens on API_PORT (default 8000); change host side if needed: "9000:8000"

env.example

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,26 @@ ZOOMEYEAPI_API_KEY=your_zoomeyeapi_key_here
174174
H1_API_KEY=your_hackerone_key_here
175175
INTEGRITI_API_KEY=your_integriti_key_here
176176

177-
# AI/LLM APIs
178-
OPENROUTER_API_KEY=your_openrouter_key_here
177+
# ============================================================================
178+
# AI / LLM PROVIDERS
179+
# ============================================================================
180+
# Set ONE of these to enable AI features (`autoar agent`, `autoar explain`,
181+
# the Brain analysis on monitor changes, and the dashboard's AI helpers).
182+
# Provider priority when multiple are set: OpenRouter -> OpenCode -> Z.ai -> Gemini.
183+
184+
# Default free provider — OpenCode Zen (https://opencode.ai/zen).
185+
# OpenAI-compatible endpoint. Free tier available; create a key at the URL above.
186+
OPENCODE_API_KEY=
187+
# Optional: override the OpenCode model. Defaults to deepseek-v4-flash-free.
188+
# See https://opencode.ai/zen/v1/models for the full list. The model id in this
189+
# variable is sent verbatim as the request "model" field.
190+
OPENCODE_MODEL=
191+
192+
# Optional: OpenRouter (https://openrouter.ai) for premium / alternative models.
193+
OPENROUTER_API_KEY=
194+
# Optional: override the OpenRouter model. Defaults to z-ai/glm-4.5-air:free.
195+
OPENROUTER_MODEL=
196+
179197

180198
# ============================================================================
181199
# AWS CONFIGURATION (for S3 scanning)

internal/api/ui/pages/settings.js

Lines changed: 117 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -54,28 +54,67 @@
5454
<div class="settings-section">
5555
<div class="settings-section-header"> AI Intelligence</div>
5656
<div class="settings-section-body">
57+
<div class="settings-item">
58+
<div class="settings-label">
59+
<div class="settings-title">OpenCode API Key</div>
60+
<div class="settings-hint">Default free provider. Get a key at <a href="https://opencode.ai/zen" target="_blank" rel="noopener">opencode.ai/zen</a>. ${cfg.opencode_key_set ? '<span class="badge badge-done">configured</span>' : '<span class="badge badge-failed">not set</span>'}</div>
61+
</div>
62+
<div class="settings-control">
63+
<input type="password" id="opencode-key-input"
64+
value=""
65+
placeholder="${cfg.opencode_key_set ? '••••••• (saved)' : 'oc-...'}"
66+
class="form-control premium-input">
67+
<button class="btn btn-primary" onclick="window.SettingsPage.saveOpenCodeKey()">Save</button>
68+
</div>
69+
</div>
70+
<div class="settings-item">
71+
<div class="settings-label">
72+
<div class="settings-title">OpenCode Model</div>
73+
<div class="settings-hint">Override the default model. Leave blank or type <code>default</code> to use <code>deepseek-v4-flash-free</code>. See <a href="https://opencode.ai/zen/v1/models" target="_blank" rel="noopener">available models</a>.</div>
74+
</div>
75+
<div class="settings-control">
76+
<input type="text" id="opencode-model-input"
77+
value="${escValue(cfg.opencode_model || '')}"
78+
placeholder="deepseek-v4-flash-free"
79+
class="form-control premium-input">
80+
<button class="btn btn-primary" onclick="window.SettingsPage.saveOpenCodeModel()">Save</button>
81+
</div>
82+
</div>
5783
<div class="settings-item">
5884
<div class="settings-label">
5985
<div class="settings-title">OpenRouter API Key</div>
60-
<div class="settings-hint">Used for vulnerability validation and reporting.</div>
86+
<div class="settings-hint">Optional — used when set, for premium or alternative models. ${cfg.openrouter_key_set ? '<span class="badge badge-done">configured</span>' : '<span class="badge badge-failed">not set</span>'}</div>
6187
</div>
6288
<div class="settings-control">
6389
<input type="password" id="or-key-input"
64-
value="${escValue(localStorage.getItem('autoar_or_key') || '')}"
65-
placeholder="sk-or-v1-…"
90+
value=""
91+
placeholder="${cfg.openrouter_key_set ? '••••••• (saved)' : 'sk-or-v1-…'}"
6692
class="form-control premium-input">
6793
<button class="btn btn-primary" onclick="window.SettingsPage.saveOpenRouterKey()">Save</button>
6894
</div>
6995
</div>
96+
<div class="settings-item">
97+
<div class="settings-label">
98+
<div class="settings-title">OpenRouter Model</div>
99+
<div class="settings-hint">Override the default model. Leave blank or type <code>default</code> to use <code>z-ai/glm-4.5-air:free</code>.</div>
100+
</div>
101+
<div class="settings-control">
102+
<input type="text" id="openrouter-model-input"
103+
value="${escValue(cfg.openrouter_model || '')}"
104+
placeholder="z-ai/glm-4.5-air:free"
105+
class="form-control premium-input">
106+
<button class="btn btn-primary" onclick="window.SettingsPage.saveOpenRouterModel()">Save</button>
107+
</div>
108+
</div>
70109
<div class="settings-item">
71110
<div class="settings-label">
72111
<div class="settings-title">Gemini API Key</div>
73-
<div class="settings-hint">Secondary fallback for AI analysis.</div>
112+
<div class="settings-hint">Final fallback for AI analysis. ${cfg.gemini_key_set ? '<span class="badge badge-done">configured</span>' : '<span class="badge badge-failed">not set</span>'}</div>
74113
</div>
75114
<div class="settings-control">
76115
<input type="password" id="gemini-key-input"
77-
value="${escValue(localStorage.getItem('autoar_gemini_key') || '')}"
78-
placeholder="AIza…"
116+
value=""
117+
placeholder="${cfg.gemini_key_set ? '••••••• (saved)' : 'AIza…'}"
79118
class="form-control premium-input">
80119
<button class="btn btn-primary" onclick="window.SettingsPage.saveGeminiKey()">Save</button>
81120
</div>
@@ -179,8 +218,10 @@
179218

180219
if (key) localStorage.setItem('autoar_or_key', key);
181220
else localStorage.removeItem('autoar_or_key');
182-
221+
183222
window.showToast('success', 'Saved!', 'OpenRouter key updated on server.');
223+
input.value = '';
224+
try { window.state.config = await window.apiFetch('/api/config'); renderSettings(); } catch(_) {}
184225
} catch (e) {
185226
window.showToast('error', 'Error', e.message);
186227
}
@@ -198,11 +239,75 @@
198239
body: JSON.stringify({ gemini_key: key })
199240
});
200241
if (!res.ok) throw new Error('Failed to update server config');
201-
242+
202243
if (key) localStorage.setItem('autoar_gemini_key', key);
203244
else localStorage.removeItem('autoar_gemini_key');
204-
245+
205246
window.showToast('success', 'Saved!', 'Gemini key updated on server.');
247+
input.value = '';
248+
try { window.state.config = await window.apiFetch('/api/config'); renderSettings(); } catch(_) {}
249+
} catch (e) {
250+
window.showToast('error', 'Error', e.message);
251+
}
252+
}
253+
254+
async function saveOpenCodeKey() {
255+
const input = document.getElementById('opencode-key-input');
256+
if (!input) return;
257+
const key = input.value.trim();
258+
if (!key) {
259+
window.showToast('error', 'Empty key', 'Enter an OpenCode API key before saving.');
260+
return;
261+
}
262+
try {
263+
const headers = await window.buildAuthHeaders({ 'Content-Type': 'application/json' });
264+
const res = await fetch('/api/settings', {
265+
method: 'POST',
266+
headers,
267+
body: JSON.stringify({ opencode_key: key })
268+
});
269+
if (!res.ok) throw new Error('Failed to update server config');
270+
window.showToast('success', 'Saved!', 'OpenCode key updated on server.');
271+
input.value = '';
272+
try { window.state.config = await window.apiFetch('/api/config'); renderSettings(); } catch(_) {}
273+
} catch (e) {
274+
window.showToast('error', 'Error', e.message);
275+
}
276+
}
277+
278+
async function saveOpenCodeModel() {
279+
const input = document.getElementById('opencode-model-input');
280+
if (!input) return;
281+
const model = input.value.trim();
282+
try {
283+
const headers = await window.buildAuthHeaders({ 'Content-Type': 'application/json' });
284+
const res = await fetch('/api/settings', {
285+
method: 'POST',
286+
headers,
287+
body: JSON.stringify({ opencode_model: model })
288+
});
289+
if (!res.ok) throw new Error('Failed to update OpenCode model');
290+
window.showToast('success', 'Saved!', model ? `OpenCode model set to "${model}".` : 'OpenCode model reset to default.');
291+
try { window.state.config = await window.apiFetch('/api/config'); renderSettings(); } catch(_) {}
292+
} catch (e) {
293+
window.showToast('error', 'Error', e.message);
294+
}
295+
}
296+
297+
async function saveOpenRouterModel() {
298+
const input = document.getElementById('openrouter-model-input');
299+
if (!input) return;
300+
const model = input.value.trim();
301+
try {
302+
const headers = await window.buildAuthHeaders({ 'Content-Type': 'application/json' });
303+
const res = await fetch('/api/settings', {
304+
method: 'POST',
305+
headers,
306+
body: JSON.stringify({ openrouter_model: model })
307+
});
308+
if (!res.ok) throw new Error('Failed to update OpenRouter model');
309+
window.showToast('success', 'Saved!', model ? `OpenRouter model set to "${model}".` : 'OpenRouter model reset to default.');
310+
try { window.state.config = await window.apiFetch('/api/config'); renderSettings(); } catch(_) {}
206311
} catch (e) {
207312
window.showToast('error', 'Error', e.message);
208313
}
@@ -276,6 +381,9 @@
276381
loadConfig,
277382
renderSettings,
278383
saveOpenRouterKey,
384+
saveOpenCodeKey,
385+
saveOpenCodeModel,
386+
saveOpenRouterModel,
279387
saveGeminiKey,
280388
saveTimeoutSettings,
281389
saveWebhookSettings,

0 commit comments

Comments
 (0)