Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion MANIFEST.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ systems or across all harnesses.
| Component | Source | Bundled pin / recorded reference | License | Local changes |
|---|---|---|---|---|
| zcode-proxy | https://github.com/TriDefender/zcode-api | v4.6.4, commit `9a5cebe07c5255faa675075fa37632d4dea733fa` (2026-09-11) | MIT (declared in upstream README; upstream has no LICENSE file) | The vendored base is v4.6.4. At this snapshot (2026-09-22), upstream's latest published release was v4.6.9; it is not incorporated in this tree. The root Git history records local changes, including credential, streaming, control-port, and test fixes. `zcode-proxy-src/README.md` describes the Kit context; its `.de`, `.es`, `.ja`, and `.zh-CN` files are translations. The upstream README is available at the pinned commit. |
| zcode-harness-mcp | This repository (MCP bridge for ZCode Desktop `app-server`) | 0.1.0 | MIT | Embedded and maintained locally in `mcp/zcode-harness-mcp/`. Audit fixes cover workspace scope, task lifecycle, argument validation, and storage limits. The compiled `dist/` output is committed; runtime dependencies are `@modelcontextprotocol/sdk` and `zod`. The English README is the original; German, Spanish, Japanese, and Simplified Chinese translations are included. |
| zcode-harness-mcp | This repository (MCP bridge for ZCode Desktop `app-server`) | 0.1.0 | MIT | Embedded and maintained locally in `mcp/zcode-harness-mcp/`. Workspace safeguards cover scope, task lifecycle, argument validation, and storage limits. The compiled `dist/` output is committed; runtime dependencies are `@modelcontextprotocol/sdk` and `zod`. The English README is the original; German, Spanish, Japanese, and Simplified Chinese translations are included. |
| OMP (target harness, reference check) | https://github.com/can1357/oh-my-pi | 18.1.18 (Canary) | — | No core changes; integration is limited to `models.yml`, `config.yml`, and an extension. |
| Claude Code (target harness, wrapper) | Anthropic | 2.1.269 | — | No changes to `~/.claude`; opt-in wrapper and generated settings file. |
| Codex CLI (target harness, wrapper) | OpenAI | 0.153.4 | — | No changes to `~/.codex`; isolated `CODEX_HOME` under `generated/`. |
Expand Down
2 changes: 1 addition & 1 deletion cli/adapters/kilo-code.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// "Custom provider" dialog (Provider API incl. Anthropic Messages). The kit
// prepares the exact values; entering them is manual-confirmation-required.
// kilo.jsonc model-level tuning is documented only sparsely upstream — the
// kit does not guess its schema (audit: no invented interfaces).
// kit does not guess its schema.
import { existsSync } from "node:fs";
import { join } from "node:path";
import { commitFile, ensureDir } from "../../lib/edit.mjs";
Expand Down
2 changes: 1 addition & 1 deletion cli/context.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Shared CLI context: paths, bootstrap (migrated from setup.mjs unchanged in
// behavior, with the audit fixes).
// behavior).
import { readFileSync, writeFileSync, existsSync, mkdirSync, openSync, closeSync, renameSync, statSync } from "node:fs";
import { createHash, randomBytes } from "node:crypto";
import { dirname, join, resolve } from "node:path";
Expand Down
2 changes: 1 addition & 1 deletion cli/upstream-config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export function readProxyTarget(configPath) {
try { if (configPath && existsSync(configPath)) text = readFileSync(configPath, "utf8"); } catch { text = ""; }
const top = (key) => text.match(new RegExp(`^${key}:[ \\t]*["']?([A-Za-z0-9._-]+)["']?[ \\t]*(?:#.*)?$`, "m"))?.[1];
const appVersion = text.match(/^identity:[ \t]*\r?\n(?:[ \t]+.*\r?\n)*?[ \t]+appVersion:[ \t]*["']?([0-9A-Za-z._-]+)["']?/m)?.[1];
return { provider: top("provider") ?? "zai", plan: top("plan") ?? "start-plan", appVersion: appVersion ?? "3.11.2", models: configuredModelIds(configPath) };
return { provider: top("provider") ?? "zai", plan: top("plan") ?? "start-plan", appVersion: appVersion ?? REFERENCE_APP_VERSION, models: configuredModelIds(configPath) };
}

function isLoopbackUrl(url) {
Expand Down
13 changes: 10 additions & 3 deletions cli/zcode-kit.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
// zcode-kit auth status|login|logout
// zcode-kit accounts [--json|--live]
// zcode-kit accounts remove|pause|resume ID [--yes]
// zcode-kit accounts unlock [--force]
// zcode-kit accounts explain --model MODEL --operation OP
// zcode-kit accounts doctor [--json] | accounts quota | accounts health [--json]
// zcode-kit update [--version vX.Y.Z] checkout: fast-forward; npm: npm install; release: verified tarball mirror
Expand Down Expand Up @@ -136,7 +137,7 @@ function usage(code) {
zcode-kit proxy start|stop|restart|status [--json]|logs [n] manage the local proxy service
zcode-kit auth status|login [zai|bigmodel] [--import] [--account ID] [--replace]|logout
zcode-kit accounts enable|disable
zcode-kit accounts [--json|--live] | accounts remove|pause|resume ID [--yes]
zcode-kit accounts [--json|--live] | accounts remove|pause|resume ID [--yes] | accounts unlock [--force]
zcode-kit accounts explain --model MODEL --operation OP
zcode-kit accounts doctor [--json] | accounts quota | accounts health [--json]
zcode-kit update [--version vX.Y.Z] | rollback [tx-id] | uninstall
Expand Down Expand Up @@ -863,6 +864,12 @@ async function cmdAccounts() {
const res = runProxyCli(args, { stdio: "inherit" });
return res.status ?? 1;
}
if (sub === "unlock") {
const args = ["auth", "accounts", "unlock"];
if (flags.force === true) args.push("--force");
const res = runProxyCli(args, { stdio: "inherit" });
return res.status ?? 1;
}
if (sub === "explain") {
const args = ["auth", "accounts", "explain"];
if (flags.model !== undefined) args.push("--model", String(flags.model));
Expand All @@ -878,7 +885,7 @@ async function cmdAccounts() {
return res.status ?? 1;
}
if (sub !== undefined && sub !== "--json" && sub !== "--live") {
console.error("Usage: zcode-kit accounts [--json|--live] | accounts remove|pause|resume ID [--yes]");
console.error("Usage: zcode-kit accounts [--json|--live] | accounts remove|pause|resume ID [--yes] | accounts unlock [--force]");
return 2;
}
const args = ["auth", "accounts"];
Expand Down Expand Up @@ -1032,7 +1039,7 @@ function stopProxyIfRunning() {

function finishUpdate(harnessArgs, txId = null) {
console.log("update: refreshing consented integrations (stored answers are respected; new harnesses are asked only on a terminal)...");
// Audit H3: explicitly running `update` IS the opt-in the checkout-write
// Explicitly running `update` is the opt-in the checkout-write
// guard asks for. Fresh process so the updated modules (not the ones
// already loaded by this process) apply the integrations.
const res = spawnSync(process.execPath, [join(ROOT, "cli", "zcode-kit.mjs"), "setup", ...harnessArgs], {
Expand Down
2 changes: 1 addition & 1 deletion install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ if ! command -v bun >/dev/null 2>&1; then
printf ' Installing Bun v1.4.2 for your user...\n'
BUN_DIR="$HOME/.bun"
# SHA256 of the bun-v1.4.2 release artifacts (upstream SHASUMS256.txt).
# Audit H2c: native arm64 assets so Apple Silicon / ARM Linux do not fall
# Native arm64 assets keep Apple Silicon / ARM Linux from falling
# back to x64-under-emulation.
BUN_LINUX_X64_SHA256="36368faef7527875d5ffa52e53cd48021741f2a83eb6208a8dd64068d422a913"
BUN_LINUX_AARCH64_SHA256="54328bbc2d9c8e0c9f892c544d66c57a83b84139e34909e5ee81758f1ac8fda7"
Expand Down
2 changes: 1 addition & 1 deletion lib/detect.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Harness detection for the zcode-kit CLI (audit §3: adapters act only for
// Harness detection for the zcode-kit CLI (adapters act only for
// detected harnesses; detection is read-only).
import { existsSync, readdirSync } from "node:fs";
import { resolveCommand } from './process.mjs';
Expand Down
10 changes: 9 additions & 1 deletion lib/proxy-env.mjs
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
export function proxyEnv(ctx, source = process.env) {
const env = { ...source };
const ambientProxy = /^(?:HTTP_PROXY|HTTPS_PROXY|ALL_PROXY|NO_PROXY)$/i;
const packageProxy = /^(?:npm|yarn|pnpm)_(?:http_proxy|https_proxy|all_proxy|no_proxy|proxy)$/i;
// The kit's config is authoritative; ambient vendor debug and trial flags
// must not silently change the service that the manager authenticates.
for (const name of Object.keys(env)) {
// A daemon must not inherit a short-lived shell/VPN/CI proxy. Operators
// who need an egress proxy opt in through ZCODE_PROXY_UPSTREAM_PROXY.
if (ambientProxy.test(name) || packageProxy.test(name)) {
delete env[name];
continue;
}
// Cache lifetime/location are explicit operator controls, not solver or
// security overrides. The cache validates their values in the child.
if (['CAPTCHA_CDN_CACHE_TTL_MS', 'CAPTCHA_CDN_CACHE_DIR'].includes(name.toUpperCase())) {
if (!Object.hasOwn(source, name.toUpperCase())) env[name.toUpperCase()] = env[name];
if (name !== name.toUpperCase()) delete env[name];
continue;
}
if ((/^ZCODE_(?:PROXY_|CLAIM_|ASYNC_|DUMP_|CAPTCHA_)/i.test(name) && !['ZCODE_PROXY_CREDENTIALS_PATH', 'ZCODE_PROXY_CREDENTIAL_SECRET', 'ZCODE_PROXY_CREDENTIAL_MASTER_KEY', 'ZCODE_PROXY_ACCOUNTS_PATH', 'ZCODE_PROXY_QUOTA_RETRY_DELAYS_MS', 'ZCODE_PROXY_TRANSIENT_RETRY_UNIT_MS'].includes(name.toUpperCase())) || /^(?:CAPTCHA_|PE_PATCH$)/i.test(name)) delete env[name];
if ((/^ZCODE_(?:PROXY_|CLAIM_|ASYNC_|DUMP_|CAPTCHA_)/i.test(name) && !['ZCODE_PROXY_CREDENTIALS_PATH', 'ZCODE_PROXY_CREDENTIAL_SECRET', 'ZCODE_PROXY_CREDENTIAL_MASTER_KEY', 'ZCODE_PROXY_ACCOUNTS_PATH', 'ZCODE_PROXY_UPSTREAM_PROXY', 'ZCODE_PROXY_QUOTA_RETRY_DELAYS_MS', 'ZCODE_PROXY_TRANSIENT_RETRY_UNIT_MS'].includes(name.toUpperCase())) || /^(?:CAPTCHA_|PE_PATCH$)/i.test(name)) delete env[name];
}
env.ZCODE_PROXY_CONFIG = ctx.config;
env.ZCODE_IDENTITY_ENV_CWD = '/workspace';
Expand Down
2 changes: 1 addition & 1 deletion mcp/zcode-harness-mcp/dist/mcp/tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ function parseModelRef(args) {
return null;
}
/** Invoke a whitelisted read-only native operation. */
// Audit H5: session/goal was removed — its action enum includes mutating
// session/goal was removed — its action enum includes mutating
// verbs (set/replace/pause/resume/clear), so listing it here let --read-only
// bypass requireWritable. Mutating access goes through zcode_session_goal.
const INVOKE_ALLOWLIST = new Set([
Expand Down
2 changes: 1 addition & 1 deletion mcp/zcode-harness-mcp/dist/mcp/validate.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
* Minimal JSON-Schema argument validation for the tool inputSchemas declared
* in tools.ts (audit D-09: the low-level SDK Server does not validate
* in tools.ts (the low-level SDK Server does not validate
* `arguments`, so enum/type/required/additionalProperties were never
* enforced). Supports exactly the subset those schemas use: object with
* properties/required/additionalProperties, string (enum), number/integer
Expand Down
2 changes: 1 addition & 1 deletion mcp/zcode-harness-mcp/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export interface BridgeConfig {
/**
* Whether MCP clients may select the harness "yolo" permission mode
* (no permission prompts). Off by default: it would let a client bypass
* the interaction policy entirely (audit D-02). Operator opt-in only.
* the interaction policy entirely. Operator opt-in only.
*/
allowYolo: boolean;
/** Bearer token required by HTTP transport (stdio needs none). */
Expand Down
2 changes: 1 addition & 1 deletion mcp/zcode-harness-mcp/src/mcp/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ function parseModelRef(args: Record<string, unknown>): { providerId: string; mod
}

/** Invoke a whitelisted read-only native operation. */
// Audit H5: session/goal was removed — its action enum includes mutating
// session/goal was removed — its action enum includes mutating
// verbs (set/replace/pause/resume/clear), so listing it here let --read-only
// bypass requireWritable. Mutating access goes through zcode_session_goal.
const INVOKE_ALLOWLIST = new Set([
Expand Down
2 changes: 1 addition & 1 deletion mcp/zcode-harness-mcp/src/mcp/validate.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
* Minimal JSON-Schema argument validation for the tool inputSchemas declared
* in tools.ts (audit D-09: the low-level SDK Server does not validate
* in tools.ts (the low-level SDK Server does not validate
* `arguments`, so enum/type/required/additionalProperties were never
* enforced). Supports exactly the subset those schemas use: object with
* properties/required/additionalProperties, string (enum), number/integer
Expand Down
6 changes: 3 additions & 3 deletions mcp/zcode-harness-mcp/test/integration/http-gate.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ test("http gate: declared oversized content-length refused without body", async
}
});

// Audit backlog (AUD-006): slow writer — the 413 must arrive BEFORE the
// Slow writer: slow writer — the 413 must arrive BEFORE the
// client finishes the upload, so the request slot is freed immediately.
test("http gate: oversized slow writer receives 413 before EOF", async () => {
const probe = http.createServer();
Expand Down Expand Up @@ -218,7 +218,7 @@ test("http gate: oversized slow writer receives 413 before EOF", async () => {
}
});

// Audit backlog: exact raw-byte boundary — a body of exactly MAX_BODY_BYTES
// Exact raw-byte boundary — a body of exactly MAX_BODY_BYTES
// (valid JSON padded with whitespace) passes; one byte more is refused.
test("http gate: raw-byte boundary at exactly 4 MiB", async () => {
const probe = http.createServer();
Expand Down Expand Up @@ -268,7 +268,7 @@ test("http gate: raw-byte boundary at exactly 4 MiB", async () => {
}
});

// Audit backlog: abandoned upload — a client that dies mid-body must not
// Abandoned upload — a client that dies mid-body must not
// wedge the bridge; the next request is served normally.
test("http gate: abandoned upload leaves the bridge healthy", async () => {
const probe = http.createServer();
Expand Down
4 changes: 2 additions & 2 deletions mcp/zcode-harness-mcp/test/integration/robustness.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ test("persistence: task survives bridge restart as interrupted, results stay rea
}
});

test("read-only invoke gate: session/goal is not invocable via zcode_operation_invoke (H5)", async () => {
// Audit H5: the invoke allowlist contained session/goal, whose action enum
test("read-only invoke gate: session/goal is not invocable via zcode_operation_invoke", async () => {
// The invoke allowlist contained session/goal, whose action enum
// includes mutating verbs (set/replace/pause/resume/clear) — so --read-only
// was bypassable. Mutating access must go through zcode_session_goal, which
// enforces requireWritable.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Audit regressions (D-01, D-02, D-04, D-05, D-07, D-09, D-11, V3-04):
* Security regressions:
* every scenario is driven through the real MCP interface against the fixture
* harness. The fixture seeds one session in a NON-allowlisted workspace.
*/
Expand Down
2 changes: 1 addition & 1 deletion pack/build.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Assembles the npm launcher package under pack/dist from the tracked repo
// sources (allowlist-driven — audit §11: explicit package allowlists, never
// sources (allowlist-driven — explicit package allowlists, never
// "publish the directory"). The launcher package contains the kit sources;
// `npm install -g zcode-agent-kit` then runs the kit's setup, which installs
// the proxy/bridge dependencies with bun (versioned, frozen lockfile).
Expand Down
24 changes: 18 additions & 6 deletions proxy/config.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ models:
- glm-5.3
- glm-5.3-flash
identity:
appVersion: "3.11.2"
appVersion: "3.14.3"
sourceTitle: "cli"
refererOrigin: "https://zcode.z.ai"
# Generated once at first `auth login` (stable device identity).
Expand All @@ -41,15 +41,27 @@ responses:
store:
maxEntries: 1000
ttlMs: 86400000
# Trial auto-claim and off-peak queue deliberately disabled.
async:
enabled: false
# Builder Event token auto-claim is enabled for authenticated sessions. The
# scheduler stays idle until a JWT is available and polls the same preview
# endpoint used by the Claim button.
claim:
enabled: true
auto: true
origin: "https://zcode.z.ai"
pollIntervalMs: 300000
cooldownMs: 600000
# Empty prefers Builder Event tokens, then highest-priority campaigns.
planId: ""

# Off-peak queue deliberately disabled.
# Optional CAPTCHA egress proxy (only for CAPTCHA requests; ambient
# HTTP_PROXY/HTTPS_PROXY variables are ignored):
# ZCODE_PROXY_UPSTREAM_PROXY=http://proxy.example:8080
async:
enabled: false
auto: false
endpointRouting:
enabled: true
clientSigning:
enabled: true
enabled: false
logging:
level: info
2 changes: 1 addition & 1 deletion proxy/zcode-proxy-autostart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const PREFLIGHT = ROOT + "/cli/heal.mjs";
const FAILURE_DETAILS: Record<string, string> = JSON.parse("__ZCODE_OM_FAILURE_DETAILS__");
const WARNINGS: Record<string, string> = JSON.parse("__ZCODE_OM_WARNINGS__");
// Above the manager's bounded wait: do not interrupt normal detached-child
// bookkeeping (audit F-10). All output is captured; -p stdout is model-only.
// bookkeeping. All output is captured; -p stdout is model-only.
const START_TIMEOUT_MS = 120_000;
const HEALTH_TTL_MS = 60_000;

Expand Down
4 changes: 2 additions & 2 deletions proxy/zcode-proxy-manager.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ export function createManager({
if (m) bootTimeMs = Number(m[1]) * 1000;
}
const stat = readFileSync(`/proc/${pid}/stat`, "utf8");
const starttime = Number(stat.slice(stat.lastIndexOf(")") + 2).split(" ")[19]);
const starttime = Number(stat.slice(stat.lastIndexOf(")") + 2).trim().split(/\s+/)[19]);
if (Number.isFinite(starttime) && bootTimeMs !== null) {
return bootTimeMs + (starttime * 1000) / 100; // CLK_TCK is 100 on Linux
}
Expand Down Expand Up @@ -729,7 +729,7 @@ try {
return 4;
}
if (verdict === "start-unknown") {
// Fail-closed (audit §7): when the live start time cannot be determined,
// Fail-closed: when the live start time cannot be determined,
// PID-reuse cannot be ruled out — never kill on doubtful evidence.
logLine(`ERROR: start time of pid ${pidInfo.pid} is not determinable — PID reuse cannot be ruled out, refusing to kill. Find the process manually: netstat -ano | findstr :${portStr}`);
return 4;
Expand Down
2 changes: 1 addition & 1 deletion setup.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
// node setup.mjs --list-transactions # show recorded transaction ids
//
// Since the zcode-kit CLI exists, this file is a thin shim delegating to
// cli/zcode-kit.mjs — same behavior, one implementation (audit §6: no
// cli/zcode-kit.mjs — same behavior, one implementation (no
// duplicated setup logic). Legacy flags map as: --only=<list> → --harness=<list>.
import { spawnSync } from "node:child_process";
import { join, dirname } from "node:path";
Expand Down
2 changes: 1 addition & 1 deletion tests/auto-heal.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ test('manager doctor honors isolated ZCODE_PROXY_CREDENTIALS_PATH', async t => {
try { await createManager({ root: ctx.root, home: ctx.home }).doctor(); }
finally { console.log = original; if (previous === undefined) delete process.env.ZCODE_PROXY_CREDENTIALS_PATH; else process.env.ZCODE_PROXY_CREDENTIALS_PATH = previous; }
// The explicit store path is honored, and a file that merely exists is no
// longer reported as a working credential store (audit F-05).
// longer reported as a working credential store.
const line = messages.find(m => /^\w+\s+credentials store/.test(m));
assert.ok(line, messages.join('\n'));
assert.match(line, /ZCODE_PROXY_CREDENTIALS_PATH \(explicit store\)/);
Expand Down
Loading
Loading