Compare commits
35 Commits
v0.2.8-beta.1
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 6e33935913 | |||
| 1b3c5a7aec | |||
| a02d7f374c | |||
| 6036ce45c4 | |||
| 648334d153 | |||
| 9d759464c2 | |||
| dc4b63a023 | |||
| d898e92935 | |||
| 0b8b67adbe | |||
| 4e0ef96d80 | |||
| aea3e93d36 | |||
| 79e4042003 | |||
| 92f10c7970 | |||
| 07e38f9d45 | |||
| 49f190e944 | |||
| 72701dee0a | |||
| 503e50a2d0 | |||
| dddcc9031b | |||
| ace50037ba | |||
| 76eaff7788 | |||
| 6ac8512dbc | |||
| 3b8e942e7f | |||
| 051457b11b | |||
| 92f1c6fe82 | |||
| 27df6894a4 | |||
| 1afd978059 | |||
| 146faca7cb | |||
| 4c99247566 | |||
| 81cbb83340 | |||
| 3f65b72cf0 | |||
| 2eb94c8f74 | |||
| 6354c0987c | |||
| 7df34eb1d0 | |||
| ca5d54882f | |||
| ca4664e0cc |
@@ -1 +1,16 @@
|
|||||||
[]
|
[
|
||||||
|
{
|
||||||
|
"level": "warning",
|
||||||
|
"role": "Mage",
|
||||||
|
"location": "app/config.test.js",
|
||||||
|
"suggestion": "`shouldSkipOpenCodeTLSVerify` 函式的新增測試案例未能涵蓋所有可能的輸入情境。在 `process.env.OPENCODE_SKIP_TLS_VERIFY !== 'false'` 的新邏輯下,應增加測試案例來驗證當環境變數設定為空字串 `''`、字串 `'0'` 或其他任意非 `'false'` 字串時,函式是否如預期般返回 `true`(跳過 TLS 驗證)。這有助於確保此關鍵安全邏輯的行為符合預期,並揭示潛在的誤配置風險。",
|
||||||
|
"is_new": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "warning",
|
||||||
|
"role": "Maya",
|
||||||
|
"location": "app/preflight.test.js",
|
||||||
|
"suggestion": "在 `preflight.test.js` 中,關於 `httpsAgent` 的測試案例也已涵蓋了預設行為(跳過 TLS)和明確設定為 `false`(不跳過 TLS)的情況。請新增一個測試,驗證當環境變數 `process.env.OPENCODE_SKIP_TLS_VERIFY` 明確設定為 `'true'` 時,`verifyLLM` 函式是否會傳遞一個不安全的 `httpsAgent` 給 OpenCode 服務進行預檢。",
|
||||||
|
"is_new": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|||||||
@@ -224,8 +224,8 @@ jobs:
|
|||||||
OPENCODE_BASE_URL: http://192.168.3.124:4096
|
OPENCODE_BASE_URL: http://192.168.3.124:4096
|
||||||
OPENCODE_PROVIDER: google
|
OPENCODE_PROVIDER: google
|
||||||
OPENCODE_MODEL: gemini-2.5-flash
|
OPENCODE_MODEL: gemini-2.5-flash
|
||||||
# 若 OpenCode server 使用自簽憑證,才需要提供:
|
# 預設會跳過 OpenCode TLS 驗證;若要強制驗證憑證才需要設定:
|
||||||
# OPENCODE_SKIP_TLS_VERIFY: true
|
# OPENCODE_SKIP_TLS_VERIFY: false
|
||||||
# 若 OpenCode server 有設定 OPENCODE_SERVER_PASSWORD,才需要提供:
|
# 若 OpenCode server 有設定 OPENCODE_SERVER_PASSWORD,才需要提供:
|
||||||
# OPENCODE_SERVER_USERNAME: opencode
|
# OPENCODE_SERVER_USERNAME: opencode
|
||||||
# OPENCODE_SERVER_PASSWORD: ${{ secrets.OPENCODE_SERVER_PASSWORD }}
|
# OPENCODE_SERVER_PASSWORD: ${{ secrets.OPENCODE_SERVER_PASSWORD }}
|
||||||
|
|||||||
+2
-2
@@ -96,9 +96,9 @@ inputs:
|
|||||||
description: 'OpenCode server Basic Auth password'
|
description: 'OpenCode server Basic Auth password'
|
||||||
required: false
|
required: false
|
||||||
OPENCODE_SKIP_TLS_VERIFY:
|
OPENCODE_SKIP_TLS_VERIFY:
|
||||||
description: '跳過 OpenCode server SSL/TLS 憑證驗證(自簽憑證時使用)'
|
description: '跳過 OpenCode server SSL/TLS 憑證驗證'
|
||||||
required: false
|
required: false
|
||||||
default: 'false'
|
default: 'true'
|
||||||
|
|
||||||
runs:
|
runs:
|
||||||
using: 'docker'
|
using: 'docker'
|
||||||
|
|||||||
+1
-1
@@ -14,7 +14,7 @@ export const FINDINGS_PATH = '.gitea/ai-review/findings.json';
|
|||||||
export const EXCLUSIONS_PATH = '.gitea/ai-review/exclusions.json';
|
export const EXCLUSIONS_PATH = '.gitea/ai-review/exclusions.json';
|
||||||
|
|
||||||
export function shouldSkipOpenCodeTLSVerify() {
|
export function shouldSkipOpenCodeTLSVerify() {
|
||||||
return process.env.OPENCODE_SKIP_TLS_VERIFY === 'true';
|
return process.env.OPENCODE_SKIP_TLS_VERIFY !== 'false';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getOpenCodeHttpsAgent() {
|
export function getOpenCodeHttpsAgent() {
|
||||||
|
|||||||
+11
-1
@@ -1,6 +1,6 @@
|
|||||||
import { describe, it, beforeEach, afterEach } from 'node:test';
|
import { describe, it, beforeEach, afterEach } from 'node:test';
|
||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import { getLLMConfig } from './config.js';
|
import { getLLMConfig, shouldSkipOpenCodeTLSVerify } from './config.js';
|
||||||
|
|
||||||
const ENV_KEYS = [
|
const ENV_KEYS = [
|
||||||
'OPENAI_API_KEY', 'OPENAI_BASE_URL', 'OPENAI_MODEL',
|
'OPENAI_API_KEY', 'OPENAI_BASE_URL', 'OPENAI_MODEL',
|
||||||
@@ -10,6 +10,7 @@ const ENV_KEYS = [
|
|||||||
'AMAZONQ_API_KEY', 'AMAZONQ_BASE_URL', 'AMAZONQ_MODEL',
|
'AMAZONQ_API_KEY', 'AMAZONQ_BASE_URL', 'AMAZONQ_MODEL',
|
||||||
'OPENCODE_BASE_URL', 'OPENCODE_MODEL', 'OPENCODE_PROVIDER',
|
'OPENCODE_BASE_URL', 'OPENCODE_MODEL', 'OPENCODE_PROVIDER',
|
||||||
'OPENCODE_SERVER_USERNAME', 'OPENCODE_SERVER_PASSWORD',
|
'OPENCODE_SERVER_USERNAME', 'OPENCODE_SERVER_PASSWORD',
|
||||||
|
'OPENCODE_SKIP_TLS_VERIFY',
|
||||||
];
|
];
|
||||||
|
|
||||||
let saved = {};
|
let saved = {};
|
||||||
@@ -104,6 +105,15 @@ describe('getLLMConfig', () => {
|
|||||||
assert.equal(cfg.model, 'google/gemini-2.5-pro');
|
assert.equal(cfg.model, 'google/gemini-2.5-pro');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('skips OpenCode TLS verification by default', () => {
|
||||||
|
assert.equal(shouldSkipOpenCodeTLSVerify(), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows explicitly enabling OpenCode TLS verification', () => {
|
||||||
|
process.env.OPENCODE_SKIP_TLS_VERIFY = 'false';
|
||||||
|
assert.equal(shouldSkipOpenCodeTLSVerify(), false);
|
||||||
|
});
|
||||||
|
|
||||||
it('openai takes priority over gemini when both set', () => {
|
it('openai takes priority over gemini when both set', () => {
|
||||||
process.env.OPENAI_API_KEY = 'sk-test';
|
process.env.OPENAI_API_KEY = 'sk-test';
|
||||||
process.env.GEMINI_API_KEY = 'gemini-key';
|
process.env.GEMINI_API_KEY = 'gemini-key';
|
||||||
|
|||||||
+14
-2
@@ -164,9 +164,8 @@ describe('chat - key rotation', async () => {
|
|||||||
assert.equal(headers[0]['Authorization'], `Basic ${Buffer.from('opencode:secret').toString('base64')}`);
|
assert.equal(headers[0]['Authorization'], `Basic ${Buffer.from('opencode:secret').toString('base64')}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('passes an insecure https agent to OpenCode when TLS verification is disabled', async () => {
|
it('passes an insecure https agent to OpenCode by default', async () => {
|
||||||
process.env.OPENCODE_BASE_URL = 'https://opencode.local:4096';
|
process.env.OPENCODE_BASE_URL = 'https://opencode.local:4096';
|
||||||
process.env.OPENCODE_SKIP_TLS_VERIFY = 'true';
|
|
||||||
const agents = [];
|
const agents = [];
|
||||||
mock.method(axios, 'post', async (url, _payload, opts) => {
|
mock.method(axios, 'post', async (url, _payload, opts) => {
|
||||||
agents.push(opts.httpsAgent);
|
agents.push(opts.httpsAgent);
|
||||||
@@ -179,6 +178,19 @@ describe('chat - key rotation', async () => {
|
|||||||
assert.equal(agents[1].options.rejectUnauthorized, false);
|
assert.equal(agents[1].options.rejectUnauthorized, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('does not pass an insecure https agent to OpenCode when TLS verification is enabled', async () => {
|
||||||
|
process.env.OPENCODE_BASE_URL = 'https://opencode.local:4096';
|
||||||
|
process.env.OPENCODE_SKIP_TLS_VERIFY = 'false';
|
||||||
|
const agents = [];
|
||||||
|
mock.method(axios, 'post', async (url, _payload, opts) => {
|
||||||
|
agents.push(opts.httpsAgent);
|
||||||
|
if (url.endsWith('/session')) return { data: { id: 'ses_test' } };
|
||||||
|
return { data: { parts: [{ type: 'text', text: 'ok' }] } };
|
||||||
|
});
|
||||||
|
await chat('sys', 'user');
|
||||||
|
assert.deepEqual(agents, [undefined, undefined]);
|
||||||
|
});
|
||||||
|
|
||||||
it('uses Responses API for openai GPT-5.5', async () => {
|
it('uses Responses API for openai GPT-5.5', async () => {
|
||||||
process.env.OPENAI_API_KEY = 'sk-test';
|
process.env.OPENAI_API_KEY = 'sk-test';
|
||||||
process.env.OPENAI_MODEL = 'GPT-5.5';
|
process.env.OPENAI_MODEL = 'GPT-5.5';
|
||||||
|
|||||||
+16
-2
@@ -183,10 +183,9 @@ describe('verifyLLM', () => {
|
|||||||
assert.deepEqual(urls, ['http://opencode.local:4096/global/health', 'http://opencode.local:4096/config/providers']);
|
assert.deepEqual(urls, ['http://opencode.local:4096/global/health', 'http://opencode.local:4096/config/providers']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('passes an insecure https agent for opencode when TLS verification is disabled', async () => {
|
it('passes an insecure https agent for opencode by default', async () => {
|
||||||
clearLLMEnv();
|
clearLLMEnv();
|
||||||
process.env.OPENCODE_BASE_URL = 'https://opencode.local:4096';
|
process.env.OPENCODE_BASE_URL = 'https://opencode.local:4096';
|
||||||
process.env.OPENCODE_SKIP_TLS_VERIFY = 'true';
|
|
||||||
const agents = [];
|
const agents = [];
|
||||||
mock.method(axios, 'get', async (url, opts) => {
|
mock.method(axios, 'get', async (url, opts) => {
|
||||||
agents.push(opts.httpsAgent);
|
agents.push(opts.httpsAgent);
|
||||||
@@ -200,6 +199,21 @@ describe('verifyLLM', () => {
|
|||||||
assert.equal(agents[1].options.rejectUnauthorized, false);
|
assert.equal(agents[1].options.rejectUnauthorized, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('does not pass an insecure https agent for opencode when TLS verification is enabled', async () => {
|
||||||
|
clearLLMEnv();
|
||||||
|
process.env.OPENCODE_BASE_URL = 'https://opencode.local:4096';
|
||||||
|
process.env.OPENCODE_SKIP_TLS_VERIFY = 'false';
|
||||||
|
const agents = [];
|
||||||
|
mock.method(axios, 'get', async (url, opts) => {
|
||||||
|
agents.push(opts.httpsAgent);
|
||||||
|
if (url.endsWith('/global/health')) return { data: { healthy: true } };
|
||||||
|
return { data: { providers: [{ id: 'google', models: { 'gemini-2.5-flash': { id: 'gemini-2.5-flash' } } }] } };
|
||||||
|
});
|
||||||
|
const result = await verifyLLM();
|
||||||
|
assert.equal(result.ok, true);
|
||||||
|
assert.deepEqual(agents, [undefined, undefined]);
|
||||||
|
});
|
||||||
|
|
||||||
it('checks openai GPT-5.5 with Responses API', async () => {
|
it('checks openai GPT-5.5 with Responses API', async () => {
|
||||||
clearLLMEnv();
|
clearLLMEnv();
|
||||||
process.env.OPENAI_API_KEY = 'sk-test';
|
process.env.OPENAI_API_KEY = 'sk-test';
|
||||||
|
|||||||
Reference in New Issue
Block a user