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
19 changes: 13 additions & 6 deletions docs/remote-bridge/worker-runbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -327,12 +327,19 @@ Environment=LIBRECHAT_CODE_GITHUB_PRIVATE_KEY_FILE=/home/librechat-code/.config/
```

Install the same App separately on every personal account or organization the
worker is allowed to use. The trusted worker resolves the correct installation
from the repository containing each command's working directory, then mints and
caches a repository-scoped token. Cross-repository work therefore does not
require changing an installation ID or restarting the worker. Set
`LIBRECHAT_CODE_GITHUB_INSTALLATION_ID` only as a legacy fixed-installation
fallback.
worker is allowed to use. By default, the worker binds each admitted workspace
root to its repository at startup, then mints and caches repository-scoped
tokens. Different admitted roots can use different installations without
restarting the worker. For a trusted VM with multiple checkouts under one root,
set `LIBRECHAT_CODE_GITHUB_REPOSITORY_ROUTING=checkout` and use the `trusted-vm`
command policy. This opt-in resolves the local `origin` URL of each command's
current checkout, including linked worktrees. It remains inside the admitted
filesystem root, but anyone able to alter a checkout's remote can select any
repository where the App is installed; keep the App's installation scope narrow.
Pass the checkout as the command working directory; changing directories only
inside the shell cannot change the token chosen before command launch.
Set `LIBRECHAT_CODE_GITHUB_INSTALLATION_ID` only as a legacy
fixed-installation fallback; it cannot be combined with checkout routing.

Sandboxed commands receive masked Git/`gh` credentials only for the configured
GitHub hosts; the token is not written to the repository, remote URL, or Git
Expand Down
26 changes: 21 additions & 5 deletions packages/code/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -293,9 +293,23 @@ installation tokens. At startup, the worker binds each explicitly admitted
workspace root to its Git repository. Commands in those independent roots can
use simultaneous installations on personal accounts and organizations without
being restarted or reconfigured, while a command cannot gain access by changing
its workspace's remote URL. Tokens are scoped and cached per repository. For
compatibility with deployments
that intentionally bind a worker to one installation, set the optional legacy
its workspace's remote URL. Tokens are scoped and cached per repository.

For trusted VMs that intentionally work in multiple Git checkouts beneath one
admitted root, opt in to `--github-repository-routing checkout` (or
`LIBRECHAT_CODE_GITHUB_REPOSITORY_ROUTING=checkout`) together with the
`trusted-vm` command policy. Each command then uses the repository identified
by its current checkout's local `origin` URL, including linked worktrees.
The command must set its working directory to that checkout; a shell `cd`
inside a command does not change which credential was selected before launch.
This does not widen the admitted filesystem roots, but a command able to alter
a checkout's remote can obtain a token for **any repository where the App is
installed**. Use this mode only where the machine operator trusts the VM and
the App's installation scope; the default `admitted` mode keeps the startup
binding. Checkout routing requires an App without a fixed installation ID.

For compatibility with deployments that intentionally bind a worker to one
installation, set the optional legacy
`LIBRECHAT_CODE_GITHUB_INSTALLATION_ID` fallback.

App-authenticated commits use the GitHub App bot's canonical no-reply identity,
Expand Down Expand Up @@ -758,8 +772,10 @@ Also archive any adjacent `.source` staging directory. Pre-release version-1
completion records are deliberately preserved but not admitted by this version;
they do not contain the required source Git identity binding.

GitHub App routing is inherited from the operator-admitted source repository;
commands cannot select a different installation by rewriting a worktree remote.
By default, GitHub App routing is inherited from the operator-admitted source
repository; commands cannot select a different installation by rewriting a
worktree remote. On trusted VMs, the opt-in checkout routing mode above instead
uses the current worktree's local `origin` URL, within the admitted root.
Legacy requests without a conversation identity continue to use the selected
source root. Older Code API deployments do not negotiate the capability, so the
worker omits it until every request path understands the isolation boundary.
Expand Down
86 changes: 77 additions & 9 deletions packages/code/src/cli.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import { generateKeyPairSync } from 'node:crypto';
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { fileURLToPath } from 'node:url';
Expand Down Expand Up @@ -242,6 +242,8 @@ test('CLI accepts repository-routed GitHub App authentication without a fixed in
t.after(() => rm(directory, { recursive: true, force: true }));
const privateKeyPath = join(directory, 'app.pem');
const preload = join(directory, 'fetch.mjs');
const workspace = join(directory, 'workspace');
await mkdir(workspace);
const { privateKey } = generateKeyPairSync('rsa', { modulusLength: 2048 });
await writeFile(
privateKeyPath,
Expand All @@ -251,13 +253,8 @@ test('CLI accepts repository-routed GitHub App authentication without a fixed in
await writeFile(
preload,
`
globalThis.fetch = async (input) => {
const url = String(input);
if (url.endsWith('/app')) return Response.json({ slug: 'lia-by-librechat' });
if (url.endsWith('/users/lia-by-librechat%5Bbot%5D')) {
return Response.json({ id: 328778573, login: 'lia-by-librechat[bot]', type: 'Bot' });
}
throw new Error('test stopped after GitHub App validation');
globalThis.fetch = async () => {
throw new Error('test reached GitHub App validation');
};
`,
);
Expand All @@ -276,7 +273,7 @@ test('CLI accepts repository-routed GitHub App authentication without a fixed in
LIBRECHAT_CODE_URL: 'http://127.0.0.1:1/v1',
LIBRECHAT_CODE_WORKER_TOKEN: 'worker-secret',
LIBRECHAT_CODE_WORKER_ID: 'engineering-vm',
LIBRECHAT_CODE_WORKER_DIR: directory,
LIBRECHAT_CODE_WORKER_DIR: workspace,
LIBRECHAT_CODE_ALLOW_WORKSPACE_COMMANDS: 'true',
LIBRECHAT_CODE_GITHUB_TOKEN: undefined,
LIBRECHAT_CODE_GITHUB_APP_ID: '123',
Expand All @@ -288,6 +285,77 @@ test('CLI accepts repository-routed GitHub App authentication without a fixed in
assert.notEqual(result.status, 0);
assert.doesNotMatch(result.stderr, /GitHub App authentication requires/);
assert.doesNotMatch(result.stderr, /installation ID/i);
assert.match(result.stderr, /test reached GitHub App validation/);

const checkout = spawnSync(
process.execPath,
['--import', preload, fileURLToPath(new URL('./cli.js', import.meta.url))],
{
encoding: 'utf8',
timeout: 10_000,
env: {
...process.env,
LIBRECHAT_CODE_URL: 'http://127.0.0.1:1/v1',
LIBRECHAT_CODE_WORKER_TOKEN: 'worker-secret',
LIBRECHAT_CODE_WORKER_ID: 'engineering-vm',
LIBRECHAT_CODE_WORKER_DIR: workspace,
LIBRECHAT_CODE_ALLOW_WORKSPACE_COMMANDS: 'true',
LIBRECHAT_CODE_COMMAND_POLICY_PRESET: 'trusted-vm',
LIBRECHAT_CODE_GITHUB_TOKEN: undefined,
LIBRECHAT_CODE_GITHUB_APP_ID: '123',
LIBRECHAT_CODE_GITHUB_INSTALLATION_ID: undefined,
LIBRECHAT_CODE_GITHUB_PRIVATE_KEY_FILE: privateKeyPath,
LIBRECHAT_CODE_GITHUB_REPOSITORY_ROUTING: 'checkout',
},
},
);
assert.notEqual(checkout.status, 0);
assert.doesNotMatch(
checkout.stderr,
/Checkout GitHub repository routing requires/,
);
assert.match(checkout.stderr, /test reached GitHub App validation/);
});

test('CLI rejects checkout routing outside a trusted VM or without repository-scoped App auth', () => {
const base = {
...process.env,
LIBRECHAT_CODE_URL: 'http://127.0.0.1:1/v1',
LIBRECHAT_CODE_WORKER_TOKEN: 'worker-secret',
LIBRECHAT_CODE_WORKER_ID: 'engineering-vm',
LIBRECHAT_CODE_WORKER_DIR: process.cwd(),
LIBRECHAT_CODE_ALLOW_WORKSPACE_COMMANDS: 'true',
LIBRECHAT_CODE_GITHUB_APP_ID: '123',
LIBRECHAT_CODE_GITHUB_PRIVATE_KEY_FILE: '/does/not/matter',
LIBRECHAT_CODE_GITHUB_INSTALLATION_ID: undefined,
LIBRECHAT_CODE_GITHUB_TOKEN: undefined,
LIBRECHAT_CODE_GITHUB_REPOSITORY_ROUTING: 'checkout',
};
const cli = fileURLToPath(new URL('./cli.js', import.meta.url));
const restricted = spawnSync(process.execPath, [cli], {
encoding: 'utf8',
env: base,
});
assert.notEqual(restricted.status, 0);
assert.match(restricted.stderr, /requires the trusted-vm command policy/);

const fixed = spawnSync(process.execPath, [cli], {
encoding: 'utf8',
env: {
...base,
LIBRECHAT_CODE_GITHUB_INSTALLATION_ID: '456',
LIBRECHAT_CODE_COMMAND_POLICY_PRESET: 'trusted-vm',
},
});
assert.notEqual(fixed.status, 0);
assert.match(fixed.stderr, /without a fixed installation ID/);

const invalid = spawnSync(process.execPath, [cli], {
encoding: 'utf8',
env: { ...base, LIBRECHAT_CODE_GITHUB_REPOSITORY_ROUTING: 'unknown' },
});
assert.notEqual(invalid.status, 0);
assert.match(invalid.stderr, /must be admitted or checkout/);
});

test('CLI requires a runtime image for Docker supervision', () => {
Expand Down
39 changes: 31 additions & 8 deletions packages/code/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ import type { LocalWorkspaceConfig } from './workspace.js';
import {
GITHUB_ALLOWED_DOMAINS,
GitHubAppCredentialProvider,
gitHubRepositoryForAdmittedDirectory,
gitHubRepositoryForCommand,
gitHubRepositoryForDirectory,
gitHubCommandCredentialEnvironment,
gitHubMaskedCredentialVariables,
Expand Down Expand Up @@ -150,19 +150,27 @@ function nonEmpty(value: string | undefined): string | undefined {
return value?.trim().length ? value : undefined;
}

function githubCredentials(): {
function githubCredentials(args: string[]): {
provider?: GitHubCredentialProvider;
host: string;
privateKeyPath?: string;
mode?: 'app' | 'token';
repositoryRouting?: boolean;
checkoutRouting?: boolean;
policyIdentity: string;
} {
const token = nonEmpty(process.env.LIBRECHAT_CODE_GITHUB_TOKEN);
const appId = nonEmpty(process.env.LIBRECHAT_CODE_GITHUB_APP_ID);
const installationId = nonEmpty(
process.env.LIBRECHAT_CODE_GITHUB_INSTALLATION_ID,
);
const routing =
option(args, '--github-repository-routing')?.trim().toLowerCase() ??
process.env.LIBRECHAT_CODE_GITHUB_REPOSITORY_ROUTING?.trim().toLowerCase() ??
'admitted';
if (routing !== 'admitted' && routing !== 'checkout') {
throw new Error('GitHub repository routing must be admitted or checkout');
}
const privateKeyPath = nonEmpty(
process.env.LIBRECHAT_CODE_GITHUB_PRIVATE_KEY_FILE,
);
Expand All @@ -178,6 +186,11 @@ function githubCredentials(): {
'Configure either GitHub App authentication or a GitHub token, not both',
);
}
if (routing === 'checkout' && (!hasApp || installationId)) {
throw new Error(
'Checkout GitHub repository routing requires a GitHub App without a fixed installation ID',
);
}
const configuredHostValue = nonEmpty(
process.env.LIBRECHAT_CODE_GITHUB_HOST,
);
Expand Down Expand Up @@ -211,12 +224,13 @@ function githubCredentials(): {
host,
mode: 'app',
repositoryRouting: !installationId,
checkoutRouting: routing === 'checkout',
policyIdentity: gitHubAuthenticationPolicyIdentity({
mode: 'app',
host,
appId,
installationId,
}),
}) + (routing === 'checkout' ? ':routing:checkout' : ''),
privateKeyPath,
provider: new GitHubAppCredentialProvider({
appId: appId!,
Expand Down Expand Up @@ -530,9 +544,11 @@ async function run(
}
const github =
runtimeSessionId == null
? githubCredentials()
? githubCredentials(args)
: {
host: 'github.com',
repositoryRouting: false,
checkoutRouting: false,
policyIdentity: gitHubAuthenticationPolicyIdentity({
host: 'github.com',
}),
Expand All @@ -547,6 +563,11 @@ async function run(
'GitHub authentication currently requires the native-srt command sandbox',
);
}
if (github.checkoutRouting && commandPolicy.preset !== 'trusted-vm') {
throw new Error(
'Checkout GitHub repository routing requires the trusted-vm command policy',
);
}
const githubDomains = github.provider
? github.host === 'github.com'
? [...GITHUB_ALLOWED_DOMAINS]
Expand Down Expand Up @@ -759,9 +780,8 @@ async function run(
}),
]),
);
// Bind credentials to immutable, explicitly admitted roots. The repository
// remote is operator input at startup, never an authorization input that a
// sandboxed command may change for its next invocation.
// Keep an admission boundary even when trusted-VM checkout routing uses a
// nested repository's remote for the current command.
const admittedGitHubRepositories = github.provider && github.repositoryRouting
? new Map(
await Promise.all(
Expand Down Expand Up @@ -998,9 +1018,12 @@ async function run(
),
async resolve(signal?: AbortSignal, cwd?: string) {
const repository = cwd && admittedGitHubRepositories
? gitHubRepositoryForAdmittedDirectory(
? await gitHubRepositoryForCommand(
cwd,
admittedGitHubRepositories,
github.checkoutRouting ? 'checkout' : 'admitted',
github.host,
signal,
)
: undefined;
if (!repository && github.repositoryRouting) {
Expand Down
Loading
Loading