openvibers/openvibe.codes Commits

Codes CLI 0.6.1: private repositories clone with a token the machine holds, never through OpenVibe (#220)

OP OpenVibers committed 12 days ago · d80b8ac9ae · 5 files, +101 −6

Changes

ddiff --git a/STATUS.json b/STATUS.json
iindex eefaea1..edd9fff 100644
---- a/STATUS.json
++++ b/STATUS.json
@@@ -58,7 +58,8 @@
"user JSON API for signed-in people to list, start, read and cancel their own tasks",
"proof of a finished task: its tests, screenshots, diff and a second-model review, and the outcome in plain words",
"the conductor (C9b): one Claude Code session on the person's own machine, driven from the browser or the phone, with its approvals",
-- "the job runner (CLI 0.6.0): each turn at the effort a person picks, in each agent's own words, and a conversation's next turn starting from the work before it (the last diff applied with git apply --3way, and the conversation so far), on any agent"
++ "the job runner (CLI 0.6.0): each turn at the effort a person picks, in each agent's own words, and a conversation's next turn starting from the work before it (the last diff applied with git apply --3way, and the conversation so far), on any agent",
++ "the job runner (CLI 0.6.1): \"edit\" means edits on the person's own computer (OpenCode and Command Code run unattended only in a Node sandbox or for full; Aider suggests no shell commands there), and a private repository clones with a token the machine holds (GITHUB_TOKEN, GITLAB_TOKEN, OPENVIBE_GIT_CREDENTIALS) sent as a header, never in the URL or .git/config"
],
"updated": "2026-10-31"
}
ddiff --git a/harness/job.js b/harness/job.js
iindex ce54a03..2bd64ad 100644
---- a/harness/job.js
++++ b/harness/job.js
@@@ -65,6 +65,48 @@ function pushTarget(requested, base, { exists = false, now = Date.now() } = {})
}
/** Any credential an URL or a message might carry, replaced. */
++/**
++ * A private repository clones with a token the machine holds (the owner, 2026-11-01: "some people might have private
++ * Gits"). The tokens are the person's, in their Node's config for this runner (docs/worker.md): they never pass through
++ * OpenVibe. Inside the job's sandbox nothing else of theirs is reachable, so these are the only way in:
++ *
++ * GITHUB_TOKEN github.com (the same token that pushes the branch)
++ * GITLAB_TOKEN gitlab.com
++ * OPENVIBE_GIT_CREDENTIALS any host, one per line in git's credential-store form: https://user:token@host
++ *
++ * The token goes to git as an Authorization header for that origin only, through GIT_CONFIG_* variables: never in the
++ * command line, never in the clone's remote URL, never in .git/config the agent can read.
++ * → { env } to add to git's environment, or {} when no token is for this host.
++ */
++function gitAuthFor(repo, env = process.env) {
++ let u;
++ try { u = new URL(String(repo || '')); } catch { return {}; }
++ if (u.protocol !== 'https:') return {};
++ const host = u.host.toLowerCase();
++ const creds = new Map();
++ if (env.GITHUB_TOKEN) creds.set('github.com', { user: 'x-access-token', token: String(env.GITHUB_TOKEN) });
++ if (env.GITLAB_TOKEN) creds.set('gitlab.com', { user: 'oauth2', token: String(env.GITLAB_TOKEN) });
++ String(env.OPENVIBE_GIT_CREDENTIALS || '').split(/[\n,]+/).map((l) => l.trim()).filter(Boolean).forEach((line) => {
++ try {
++ const c = new URL(line);
++ if (c.protocol === 'https:' && c.password) creds.set(c.host.toLowerCase(), { user: decodeURIComponent(c.username || 'git'), token: decodeURIComponent(c.password) });
++ } catch { /* a line that is not https://user:token@host is skipped */ }
++ });
++ const c = creds.get(host);
++ if (!c || !c.token || /[\r\n]/.test(c.token + c.user)) return {};
++ const basic = Buffer.from(`${c.user}:${c.token}`).toString('base64');
++ return { env: { GIT_CONFIG_COUNT: '1', GIT_CONFIG_KEY_0: `http.https://${host}/.extraheader`, GIT_CONFIG_VALUE_0: `Authorization: Basic ${basic}` } };
++}
++
++/** Why a clone failed, in words a person can act on (a private repository with no token for its host). */
++function cloneProblem(message, repo) {
++ const m = String(message || '');
++ if (!/could not read Username|Authentication failed|terminal prompts disabled|403|401|not found/i.test(m)) return m;
++ let host = 'its host';
++ try { host = new URL(String(repo)).host; } catch { /* keep the words */ }
++ return `${m} — if the repository is private, give this machine's Codes runner a token for ${host} (GITHUB_TOKEN, GITLAB_TOKEN, or an OPENVIBE_GIT_CREDENTIALS line https://you:token@${host}) in the Node's config.json env, then restart the Node`;
++}
++
function scrub(s) {
return String(s).replace(/(https?:\/\/)[^@/\s]+@/g, '$1***@').replace(/gh[pousr]_[A-Za-z0-9]{20,}/g, '***');
}
@@@ -463,7 +505,11 @@ async function runJob({ input, harness, emit, writeResult, env = process.env, tm
let adapterOptions = null;
if (job.kind === 'code') {
say({ t: 'step', text: `Cloning ${scrub(job.repo)}${job.ref ? ` (${job.ref})` : ''}` });
-- await git(['clone', '--depth', '50', ...(job.ref ? ['--branch', job.ref] : []), '--', job.repo, work], { timeout: 300_000 });
++ const auth = gitAuthFor(job.repo, env);
++ try {
++ await git(['clone', '--depth', '50', ...(job.ref ? ['--branch', job.ref] : []), '--', job.repo, work], { timeout: 300_000, ...(auth.env ? { env: auth.env } : {}) });
++ } catch (e) { throw new Error(cloneProblem(e.message, job.repo)); }
++ if (auth.env) say({ t: 'step', text: 'Cloned with this machine\'s token for a private repository' });
base = (await git(['rev-parse', '--abbrev-ref', 'HEAD'], { cwd: work })).trim();
result.base = base;
say({ t: 'step', text: `Working on ${base}` });
@@@ -594,4 +640,4 @@ function resultWriter(emit) {
};
}
--module.exports = { runJob, parseArgs, compact, sessionLog, stepInput, resultWriter, scrub, githubRepo, pushTarget, jobEnv, ENV_NAMES, JOB_PROTOCOL, codesToken, conductorMcpConfig, writeAttachments };
++module.exports = { gitAuthFor, cloneProblem, runJob, parseArgs, compact, sessionLog, stepInput, resultWriter, scrub, githubRepo, pushTarget, jobEnv, ENV_NAMES, JOB_PROTOCOL, codesToken, conductorMcpConfig, writeAttachments };
ddiff --git a/package-lock.json b/package-lock.json
iindex c29f010..dc8b03d 100644
---- a/package-lock.json
++++ b/package-lock.json
@@@ -1,12 +1,12 @@
{
"name": "openvibe-codes",
-- "version": "0.6.0",
++ "version": "0.6.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "openvibe-codes",
-- "version": "0.6.0",
++ "version": "0.6.1",
"license": "AGPL-3.0-only",
"dependencies": {
"cookie-parser": "^1.4.7",
ddiff --git a/package.json b/package.json
iindex b2027a7..669bf4b 100644
---- a/package.json
++++ b/package.json
@@@ -1,6 +1,6 @@
{
"name": "openvibe-codes",
-- "version": "0.6.0",
++ "version": "0.6.1",
"private": true,
"description": "OpenVibe.Codes: the open coding-agent harness. Run a coding task on Claude Code, Codex, OpenCode, Command Code, Aider, the DeepSeek API or your own model, with routing by capability and cost and hand-offs between agents; plus the site with the agent catalog, a public routing API and the way to improve OpenVibe itself.",
"main": "harness/index.js",
ddiff --git a/test/job-private-git.test.js b/test/job-private-git.test.js
nnew file mode 100644
iindex 0000000..8150067
---- /dev/null
++++ b/test/job-private-git.test.js
@@@ -0,0 +1,48 @@
++'use strict';
++/**
++ * Private repositories (the owner, 2026-11-01: "some people might have private Gits"): a job clones with a token the
++ * machine holds (GITHUB_TOKEN, GITLAB_TOKEN, or an OPENVIBE_GIT_CREDENTIALS line https://user:token@host from the Node's
++ * config), as an Authorization header for that origin only, through GIT_CONFIG_* variables — never on the command
++ * line, never in the remote URL or .git/config. A failed clone of a private repository says what to do.
++ */
++const assert = require('assert');
++const { execFileSync } = require('child_process');
++const { check, done } = require('./helpers/boot');
++const { gitAuthFor, cloneProblem } = require('../harness/job');
++
++const header = (auth) => auth.env && auth.env.GIT_CONFIG_VALUE_0;
++const basicOf = (auth) => Buffer.from(String(header(auth)).replace(/^Authorization: Basic /, ''), 'base64').toString();
++
++(async () => {
++ await check('a GitHub, GitLab or any-host token becomes a header for that origin only', () => {
++ const env = { GITHUB_TOKEN: 'ghp_fake0123456789abcdefghij', GITLAB_TOKEN: 'glpat-fake', OPENVIBE_GIT_CREDENTIALS: 'https://ada:tok%[email protected]\nhttps://bob:[email protected]' };
++ const gh = gitAuthFor('https://github.com/someone/private-thing', env);
++ assert.strictEqual(gh.env.GIT_CONFIG_COUNT, '1');
++ assert.strictEqual(gh.env.GIT_CONFIG_KEY_0, 'http.https://github.com/.extraheader');
++ assert.strictEqual(basicOf(gh), 'x-access-token:ghp_fake0123456789abcdefghij');
++ assert.strictEqual(basicOf(gitAuthFor('https://gitlab.com/group/sub/project.git', env)), 'oauth2:glpat-fake');
++ assert.strictEqual(basicOf(gitAuthFor('https://git.openvibe.codes/ada/site', env)), 'ada:tok/en', 'a credential line, decoded');
++ assert.strictEqual(basicOf(gitAuthFor('https://code.example.org/team/app', env)), 'bob:pw');
++ assert.deepStrictEqual(gitAuthFor('https://bitbucket.org/x/y', env), {}, 'no token for that host, no header');
++ assert.deepStrictEqual(gitAuthFor('http://github.com/x/y', env), {}, 'never over plain http');
++ assert.deepStrictEqual(gitAuthFor('https://github.com/x/y', {}), {}, 'a machine with no token sends none');
++ assert.deepStrictEqual(gitAuthFor('https://evil.example/x', { OPENVIBE_GIT_CREDENTIALS: 'https://a:[email protected]/%0d%0aX-Injected: 1' }).env ? 'header' : 'none', 'header', 'the path of a line is not part of the header');
++ assert.deepStrictEqual(gitAuthFor('https://evil.example/x', { OPENVIBE_GIT_CREDENTIALS: 'https://a%0d%0aX:[email protected]' }), {}, 'a line that would break the header is refused');
++ assert.deepStrictEqual(gitAuthFor('not a url', env), {});
++ });
++
++ await check('the installed git reads the header from the environment, and nothing writes it to a config file', () => {
++ const auth = gitAuthFor('https://github.com/someone/private-thing', { GITHUB_TOKEN: 'ghp_fake0123456789abcdefghij' });
++ const got = execFileSync('git', ['config', '--get', 'http.https://github.com/.extraheader'], { env: { ...process.env, ...auth.env }, encoding: 'utf8' }).trim();
++ assert.strictEqual(got, header(auth));
++ assert.ok(!Object.values(auth.env).some((v) => v.includes('ghp_fake')), 'the raw token is in no variable: only its Basic form');
++ });
++
++ await check('a failed clone of a private repository says what to do, and other failures are left as they are', () => {
++ const said = cloneProblem("fatal: could not read Username for 'https://github.com': terminal prompts disabled", 'https://github.com/someone/private-thing');
++ assert.match(said, /if the repository is private, give this machine's Codes runner a token for github\.com/);
++ assert.match(said, /GITHUB_TOKEN, GITLAB_TOKEN, or an OPENVIBE_GIT_CREDENTIALS line https:\/\/you:token@github\.com/);
++ assert.strictEqual(cloneProblem('fatal: unable to access: Could not resolve host', 'https://x.y/a'), 'fatal: unable to access: Could not resolve host');
++ });
++ done();
++})().catch((e) => { console.error(e); process.exit(1); });