diff --git a/gateway/extensions/aikido/aikido-mcp-server.js b/gateway/extensions/aikido/aikido-mcp-server.js index 8e3ab3a..a8ddbb5 100644 --- a/gateway/extensions/aikido/aikido-mcp-server.js +++ b/gateway/extensions/aikido/aikido-mcp-server.js @@ -237,7 +237,7 @@ async function handleMessage(msg) { const result = await tool.handler(args || {}); send({ jsonrpc: "2.0", id, result }); } catch (err) { - send({ jsonrpc: "2.0", id, result: { content: [{ type: "text", text: `Fout: ${err.message}` }], isError: true } }); + send({ jsonrpc: "2.0", id, result: { content: [{ type: "text", text: `Error: ${err.message}` }], isError: true } }); } return; } diff --git a/gateway/extensions/aikido/frontend/component.js b/gateway/extensions/aikido/frontend/component.js index 1677df7..539bdbb 100644 --- a/gateway/extensions/aikido/frontend/component.js +++ b/gateway/extensions/aikido/frontend/component.js @@ -402,16 +402,16 @@ @@ -439,13 +439,13 @@ @@ -458,15 +458,15 @@ @@ -570,11 +570,11 @@ this._renderView(); setTimeout(() => this._openCreds(), 100); } else { - this._renderMain(`
Fout: ${this.esc(e.message)}
`); + this._renderMain(`
Error: ${this.esc(e.message)}
`); } } } else { - this._renderMain('
Repositories laden…
'); + this._renderMain('
Loading repositories…
'); try { s.workspaces = await this.api('GET', '/workspaces'); this._renderView(); @@ -748,12 +748,12 @@ ${repoHeader}
- + ${sevBtns}
${batchBar} @@ -762,7 +762,7 @@ Severity${arrow('severity')} - Titel${arrow('title')} + Title${arrow('title')} CVE Score${arrow('severity_score')} @@ -817,8 +817,8 @@
-

Geen issues gevonden

-

Geen security-issues${sevFilter || search ? ' voor dit filter' : ''}.

+

No issues found

+

No security issues${sevFilter || search ? ' for this filter' : ''}.

`; @@ -836,9 +836,9 @@ let footer = this.$('.table-footer'); if (hasPrev || hasMore) { const footerHtml = ` - - Pagina ${page + 1} · ${Math.min(start + perPage, filteredTotal)} van ${filteredTotal} - `; + + Page ${page + 1} · ${Math.min(start + perPage, filteredTotal)} of ${filteredTotal} + `; if (!footer) { footer = document.createElement('div'); footer.className = 'table-footer'; @@ -1002,40 +1002,40 @@ const isRunning = existing?.status?.startsWith('Up'); if (existing && isRunning) { - setMsg(spinner(`Injecteren in bestaande container ${containerName}…`)); + setMsg(spinner(`Injecting into existing container ${containerName}…`)); await this.api('POST', `/workspaces/${encodeURIComponent(ws)}/inject`, { container_name: containerName, issues: toFix, }); - setMsg(`
✓ Geïnjecteerd in bestaande container ${containerName}. Voer aikido-fix uit in de container.
`); + setMsg(`
✓ Injected into existing container ${containerName}. Run aikido-fix in the container.
`); } else if (existing && !isRunning) { - setMsg(spinner(`Container ${containerName} hervatten…`)); + setMsg(spinner(`Resuming container ${containerName}…`)); await fetch(`/api/docker/containers/${encodeURIComponent(containerName)}/start`, { method: 'POST' }) .then(async r => { if (!r.ok) { const d = await r.json().catch(() => ({})); throw new Error(d.error || `HTTP ${r.status}`); } }); - setMsg(spinner(`Container geherstart, injecteren…`)); + setMsg(spinner(`Container restarted, injecting…`)); await new Promise(r => setTimeout(r, 1500)); await this.api('POST', `/workspaces/${encodeURIComponent(ws)}/inject`, { container_name: containerName, issues: toFix, }); - setMsg(`
✓ Container ${containerName} hervat en geïnjecteerd. Voer aikido-fix uit in de container.
`); + setMsg(`
✓ Container ${containerName} resumed and injected. Run aikido-fix in the container.
`); } else { const wsObj = this._s.workspaces.find(w => w.name === ws); const wsPath = wsObj?.repo_path; if (!wsPath) throw new Error('Geen repo-pad bekend voor deze workspace. Stel het in via de workspace-instellingen.'); - setMsg(spinner(`Container ${containerName} aanmaken…`)); + setMsg(spinner(`Creating container ${containerName}…`)); await fetch('/api/docker/start', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ imageName: image, containerName, presentableName: `Aikido ${ws}`, workspaceDir: wsPath }), }).then(async r => { if (!r.ok) { const d = await r.json().catch(() => ({})); throw new Error(d.error || `HTTP ${r.status}`); } }); - setMsg(spinner(`Container gestart, injecteren…`)); + setMsg(spinner(`Container started, injecting…`)); await new Promise(r => setTimeout(r, 2000)); await this.api('POST', `/workspaces/${encodeURIComponent(ws)}/inject`, { container_name: containerName, issues: toFix, }); await this._loadContainers(); - setMsg(`
✓ Container ${containerName} aangemaakt en geïnjecteerd. Voer aikido-fix uit in de container.
`); + setMsg(`
✓ Container ${containerName} created and injected. Run aikido-fix in the container.
`); } } diff --git a/gateway/extensions/aikido/frontend/index.html b/gateway/extensions/aikido/frontend/index.html index 0907e43..d11e23c 100644 --- a/gateway/extensions/aikido/frontend/index.html +++ b/gateway/extensions/aikido/frontend/index.html @@ -431,15 +431,15 @@

Credentials — × @@ -509,9 +509,9 @@

Credentials — + Workspace`; } else { el.innerHTML = ` - + - `; + `; } } @@ -521,7 +521,7 @@

Credentials —
Workspaces laden…`; + main.innerHTML = `
Loading workspaces…
`; return; } @@ -530,9 +530,9 @@

Credentials —
🔒
-

Geen workspaces

-

Voeg een workspace toe om security-issues te bekijken.

- +

No workspaces

+

Add a workspace to view security issues.

+
`; return; @@ -544,7 +544,7 @@

Geen workspaces

const hasHigh = s && s.high > 0; const cls = hasCrit ? 'has-critical' : hasHigh ? 'has-high' : ''; const credCls = ws.hasCredentials ? 'ok' : 'missing'; - const credLabel = ws.hasCredentials ? '✓ credentials' : '✗ geen credentials'; + const credLabel = ws.hasCredentials ? '✓ credentials' : '✗ no credentials'; return ` - + + ` : ''; const issues = getDisplayIssues(); @@ -626,12 +626,12 @@

Geen workspaces

- + ${sevBtns}
${batchBar} @@ -641,7 +641,7 @@

Geen workspaces

state.selected.has(String(i.id))) ? 'checked' : ''} /> Severity ${sortArrow('severity')} - Titel ${sortArrow('title')} + Title ${sortArrow('title')} CVE Score ${sortArrow('severity_score')} @@ -652,9 +652,9 @@

Geen workspaces

${(hasPrev || hasMore) ? ` ` : ''} `; } @@ -714,7 +714,7 @@

${esc(i.title)}

Container
@@ -737,7 +737,7 @@

${esc(i.title)}

// Load overview in background api('GET', '/overview').then(ov => { state.overview = ov; render(); }).catch(() => {}); } catch (e) { - alert('Fout bij laden workspaces: ' + e.message); + alert('Error loading workspaces: ' + e.message); } finally { state.loading = false; render(); } @@ -774,7 +774,7 @@

${esc(i.title)}

setTimeout(() => openManageCredentials(), 100); return; } - alert('Fout bij laden issues: ' + e.message); + alert('Error loading issues: ' + e.message); } finally { state.issueLoading = false; render(); } @@ -886,22 +886,22 @@

${esc(i.title)}

async function doInject(issues, containerName) { const msgEl = document.getElementById('inject-msg'); - if (msgEl) msgEl.innerHTML = `
Bezig met injecteren…
`; + if (msgEl) msgEl.innerHTML = `
Injecting…
`; try { await api('POST', `/workspaces/${encodeURIComponent(state.selectedWs)}/inject`, { container_name: containerName, issues, }); - if (msgEl) msgEl.innerHTML = `
✓ Context geïnjecteerd in ${esc(containerName)}. Voer aikido-fix uit in de container.
`; + if (msgEl) msgEl.innerHTML = `
✓ Context injected into ${esc(containerName)}. Run aikido-fix in the container.
`; } catch (e) { - if (msgEl) msgEl.innerHTML = `
Fout: ${esc(e.message)}
`; + if (msgEl) msgEl.innerHTML = `
Error: ${esc(e.message)}
`; } } // ── Workspace modal ──────────────────────────────────────────────────────── function openAddWorkspace() { state.editingWs = null; - document.getElementById('ws-modal-title').textContent = 'Workspace toevoegen'; + document.getElementById('ws-modal-title').textContent = 'Add workspace'; document.getElementById('ws-name').value = ''; document.getElementById('ws-prefix').value = ''; document.getElementById('ws-path').value = ''; @@ -917,7 +917,7 @@

${esc(i.title)}

const ws = state.workspaces.find(w => w.name === name); if (!ws) return; state.editingWs = ws; - document.getElementById('ws-modal-title').textContent = `Workspace bewerken — ${name}`; + document.getElementById('ws-modal-title').textContent = `Edit workspace — ${name}`; document.getElementById('ws-name').value = ws.name; document.getElementById('ws-name').disabled = true; document.getElementById('ws-prefix').value = ws.aikido_env_prefix; @@ -941,7 +941,7 @@

${esc(i.title)}

const msg = document.getElementById('ws-modal-msg'); if (!name || !prefix || !rpath || !wsid || !lang) { - msg.innerHTML = `
Alle verplichte velden zijn vereist.
`; return; + msg.innerHTML = `
All required fields are required.
`; return; } const body = { name, aikido_env_prefix: prefix, repo_path: rpath, workspace_id: wsid, language: lang, code_repo_name: repo || undefined }; @@ -986,14 +986,14 @@

${esc(i.title)}

const clientSecret = document.getElementById('cred-secret').value; const msg = document.getElementById('cred-modal-msg'); if (!clientId || !clientSecret) { - msg.innerHTML = `
Client ID en Secret zijn verplicht.
`; return; + msg.innerHTML = `
Client ID and Secret are required.
`; return; } try { const res = await api('POST', `/credentials/${encodeURIComponent(state.credPrefix)}`, { client_id: clientId, client_secret: clientSecret }); if (res.validated) { - msg.innerHTML = `
✓ Credentials opgeslagen en gevalideerd.
`; + msg.innerHTML = `
✓ Credentials saved and validated.
`; } else { - msg.innerHTML = `
Opgeslagen, maar validatie mislukt: ${esc(res.validation_error || 'onbekend')}
`; + msg.innerHTML = `
Saved, but validation failed: ${esc(res.validation_error || 'unknown')}
`; } await loadWorkspaces(); } catch (e) { @@ -1002,7 +1002,7 @@

${esc(i.title)}

} async function deleteCreds() { - if (!confirm('Credentials verwijderen?')) return; + if (!confirm('Delete credentials?')) return; try { await api('DELETE', `/credentials/${encodeURIComponent(state.credPrefix)}`); closeCredModal(); diff --git a/gateway/extensions/aikido/index.js b/gateway/extensions/aikido/index.js index f3e53c2..e58108b 100644 --- a/gateway/extensions/aikido/index.js +++ b/gateway/extensions/aikido/index.js @@ -52,36 +52,36 @@ function generateClaudePrompt(issues, ws) { const repoName = ws.code_repo_name || ws.name; let issueSection; if (issues.length === 1) { - issueSection = `Kwetsbaarheid:\n${JSON.stringify(issues[0], null, 2)}`; + issueSection = `Vulnerability:\n${JSON.stringify(issues[0], null, 2)}`; } else { - issueSection = `Er zijn ${issues.length} kwetsbaarheden:\n\n` + + issueSection = `There are ${issues.length} vulnerabilities:\n\n` + issues.map((i, n) => `### Issue ${n + 1}\n${JSON.stringify(i, null, 2)}`).join('\n\n'); } - return `Je bent een senior security engineer gespecialiseerd in ${language.toUpperCase()} applicaties. + return `You are a senior security engineer specializing in ${language.toUpperCase()} applications. -Fix de volgende ${issues.length === 1 ? 'kwetsbaarheid' : `${issues.length} kwetsbaarheden`} in deze codebase: +Fix the following ${issues.length === 1 ? 'vulnerability' : `${issues.length} vulnerabilities`} in this codebase: -## Beschikbare MCP Tools +## Available MCP Tools -| Tool | Beschrijving | +| Tool | Description | |------|-------------| -| \`aikido_issues_list\` | Open issues ophalen (filter met \`repo_name: "${repoName}"\`) | -| \`aikido_issue_details\` | Details van een specifiek issue | -| \`aikido_scan_repo\` | Scan triggeren na een fix | -| \`aikido_list_repos\` | Repositories ophalen | -| \`aikido_ignore_issue\` | Issue negeren als false positive | -| \`aikido_add_note\` | Notitie toevoegen aan een issue | +| \`aikido_issues_list\` | Get open issues (filter with \`repo_name: "${repoName}"\`) | +| \`aikido_issue_details\` | Get details of a specific issue | +| \`aikido_scan_repo\` | Trigger scan after fix | +| \`aikido_list_repos\` | List repositories | +| \`aikido_ignore_issue\` | Mark issue as false positive | +| \`aikido_add_note\` | Add note to issue | -## Stappen +## Steps -1. **Analyseer** — Zoek de root cause in de code. -2. **Fix** — Pas de code aan om de kwetsbaarheid op te lossen. -3. **Verificeer** — Gebruik \`aikido_list_repos\` → \`aikido_scan_repo\` → \`aikido_issues_list\` om te controleren. -4. **Documenteer** — \`aikido_add_note\` + schrijf \`aikido/SECURITY_FIX.md\`. +1. **Analyze** — Find the root cause in the code. +2. **Fix** — Modify the code to resolve the vulnerability. +3. **Verify** — Use \`aikido_list_repos\` → \`aikido_scan_repo\` → \`aikido_issues_list\` to verify. +4. **Document** — \`aikido_add_note\` + write \`aikido/SECURITY_FIX.md\`. ${issueSection} -Belangrijk: geen git commits, alleen noodzakelijke wijzigingen.`; +Important: no git commits, only necessary changes.`; } function generateIssueContext(issues, ws) { @@ -89,10 +89,10 @@ function generateIssueContext(issues, ws) { for (let n = 0; n < issues.length; n++) { const i = issues[n]; const prefix = issues.length > 1 ? `### ${n + 1}. ` : '## '; - content += `${prefix}${i.title || 'Onbekend'}\n`; - content += `- **Severity**: ${i.severity || '?'} (score: ${i.severity_score ?? 'n.v.t.'})\n`; - content += `- **Type**: ${i.type || 'n.v.t.'}\n`; - content += `- **Package**: ${i.affected_package || 'n.v.t.'}\n\n`; + content += `${prefix}${i.title || 'Unknown'}\n`; + content += `- **Severity**: ${i.severity || '?'} (score: ${i.severity_score ?? 'n/a'})\n`; + content += `- **Type**: ${i.type || 'n/a'}\n`; + content += `- **Package**: ${i.affected_package || 'n/a'}\n\n`; } return content; } @@ -129,7 +129,7 @@ module.exports.register = async function register(ctx) { return reply.code(400).send({ error: 'Verplichte velden: name, aikido_env_prefix, repo_path, workspace_id, language' }); } const existing = getWorkspace(db, name); - if (existing) return reply.code(409).send({ error: `Workspace "${name}" bestaat al` }); + if (existing) return reply.code(409).send({ error: `Workspace "${name}" already exists` }); db.prepare('INSERT INTO aikido_workspaces (name, aikido_env_prefix, repo_path, workspace_id, language, code_repo_name) VALUES (?, ?, ?, ?, ?, ?)') .run(name, aikido_env_prefix, repo_path.replace(/\\/g, '/'), workspace_id, language, code_repo_name || null); return { ok: true }; @@ -141,7 +141,7 @@ module.exports.register = async function register(ctx) { if (!ws) return reply.code(404).send({ error: `Workspace "${req.params.name}" niet gevonden` }); const { name, aikido_env_prefix, repo_path, workspace_id, language, code_repo_name } = req.body; if (name && name !== req.params.name && getWorkspace(db, name)) { - return reply.code(409).send({ error: `Workspace "${name}" bestaat al` }); + return reply.code(409).send({ error: `Workspace "${name}" already exists` }); } db.prepare(`UPDATE aikido_workspaces SET name = COALESCE(?, name), @@ -175,7 +175,7 @@ module.exports.register = async function register(ctx) { const ws = getWorkspace(db, req.params.name); if (!ws) return reply.code(404).send({ error: `Workspace "${req.params.name}" niet gevonden` }); const creds = resolveCreds(ws.aikido_env_prefix); - if (!creds) return reply.code(401).send({ error: 'no_credentials', message: 'Geen Aikido credentials geconfigureerd' }); + if (!creds) return reply.code(401).send({ error: 'no_credentials', message: 'No Aikido credentials configured' }); const page = parseInt(req.query?.page || '0', 10); const perPage = parseInt(req.query?.per_page || '20', 10); diff --git a/gateway/frontend/src/app/shared/pipes/rel-time.pipe.ts b/gateway/frontend/src/app/shared/pipes/rel-time.pipe.ts index 77d9af0..37618fe 100644 --- a/gateway/frontend/src/app/shared/pipes/rel-time.pipe.ts +++ b/gateway/frontend/src/app/shared/pipes/rel-time.pipe.ts @@ -6,10 +6,10 @@ export class RelTimePipe implements PipeTransform { const d = Math.floor(Date.now() / 1000) - unix; if (d < -60) return `over ${Math.ceil(-d / 60)}m`; if (d < 0) return `over ${-d}s`; - if (d < 5) return 'zojuist'; + if (d < 5) return 'just now'; if (d < 60) return `${d}s`; if (d < 3600) return `${Math.floor(d / 60)}m`; - if (d < 86400) return `${Math.floor(d / 3600)}u`; + if (d < 86400) return `${Math.floor(d / 3600)}h`; return `${Math.floor(d / 86400)}d`; } } diff --git a/gateway/src/dns-egress.ts b/gateway/src/dns-egress.ts index 3e65ccf..d7d465d 100644 --- a/gateway/src/dns-egress.ts +++ b/gateway/src/dns-egress.ts @@ -132,8 +132,8 @@ async function doSanitize(): Promise { try { fs.writeFileSync(RESOLV_CONF, rebuilt.join('\n')); console.log( - `[dns-egress] resolv.conf opgeschoond: egress=${working.join(',')} ` + - `verwijderd=${broken.join(',')}`, + `[dns-egress] resolv.conf cleaned: egress=${working.join(',')} ` + + `removed=${broken.join(',')}`, ); } catch (err: any) { console.warn('[dns-egress] kon resolv.conf niet schrijven:', err.message); diff --git a/gateway/src/docker.ts b/gateway/src/docker.ts index 2eb394c..cc1884d 100644 --- a/gateway/src/docker.ts +++ b/gateway/src/docker.ts @@ -520,8 +520,8 @@ else "$INST_PATH/jbr/bin/keytool" -importcert -noprompt -trustcacerts -alias huddle-ca \\ -file /usr/local/share/ca-certificates/huddle-ca.crt \\ -keystore "$INST_PATH/jbr/lib/security/cacerts" -storepass changeit >/dev/null 2>&1 \\ - && echo "[jb-config] huddle CA in JBR-keystore geimporteerd (na deploy)" \\ - || echo "[jb-config] WAARSCHUWING: JBR-keystore import faalde (na deploy)" + && echo "[jb-config] huddle CA imported in JBR-keystore (after deploy)" \\ + || echo "[jb-config] WARNING: JBR-keystore import failed (after deploy)" fi break fi @@ -567,10 +567,10 @@ if [ -x "$JBR_KEYTOOL" ] && [ -f "$JBR_CACERTS" ]; then "$JBR_KEYTOOL" -importcert -noprompt -trustcacerts -alias huddle-ca \\ -file /usr/local/share/ca-certificates/huddle-ca.crt \\ -keystore "$JBR_CACERTS" -storepass changeit >/dev/null 2>&1 \\ - && echo "[jb-config] huddle CA in JBR-keystore geimporteerd" \\ - || echo "[jb-config] WAARSCHUWING: JBR-keystore import faalde" + && echo "[jb-config] huddle CA imported in JBR-keystore" \\ + || echo "[jb-config] WARNING: JBR-keystore import failed" else - echo "[jb-config] WAARSCHUWING: JBR keytool/cacerts niet gevonden op $IDEA_PATH/jbr" + echo "[jb-config] WARNING: JBR keytool/cacerts not found at $IDEA_PATH/jbr" fi fi @@ -795,8 +795,8 @@ export async function createAndStartContainer(params: StartParams): Promise