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
9 changes: 9 additions & 0 deletions docs/remote-bridge/worker-runbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,15 @@ 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.
For a trusted VM that needs to switch among repositories in the same installed
account or organization inside one command, set
`LIBRECHAT_CODE_GITHUB_TOKEN_SCOPE=installation`. The resolved installation
token covers only repositories and permissions GitHub granted to that App
installation. It refreshes after two minutes so newly approved permissions
become available without a worker restart. The default is `repository`.
Commands spanning different accounts or organizations must start in a checkout
from the target account or organization; a shell `cd` cannot switch the
installation chosen at command launch.
Set `LIBRECHAT_CODE_GITHUB_INSTALLATION_ID` only as a legacy
fixed-installation fallback; it cannot be combined with checkout routing.

Expand Down
11 changes: 11 additions & 0 deletions packages/code/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,17 @@ 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.

On a trusted VM, `--github-token-scope installation` (or
`LIBRECHAT_CODE_GITHUB_TOKEN_SCOPE=installation`) mints one token for all
repositories GitHub grants to the resolved App installation. This lets a
command started in one checkout push to another repository in the same account
or organization, including through `cd` or `git -C`, and use organization
Projects. GitHub still enforces the installation's selected repositories and
permissions. Tokens are shared by installation, refreshed after two minutes,
and kept out of the sandbox's readable environment. The default remains
`repository`. A command crossing to another account or organization still
needs to start in a checkout belonging to that account or organization.

For compatibility with deployments that intentionally bind a worker to one
installation, set the optional legacy
`LIBRECHAT_CODE_GITHUB_INSTALLATION_ID` fallback.
Expand Down
29 changes: 29 additions & 0 deletions packages/code/src/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,35 @@ test('CLI rejects checkout routing outside a trusted VM or without repository-sc
assert.match(invalid.stderr, /must be admitted or checkout/);
});

test('CLI permits installation-scoped GitHub tokens only for a trusted VM with routed App auth', () => {
const cli = fileURLToPath(new URL('./cli.js', import.meta.url));
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_TOKEN: undefined,
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_SCOPE: 'installation',
};
const restricted = spawnSync(process.execPath, [cli], { encoding: 'utf8', env: base });
assert.match(restricted.stderr, /Installation-scoped GitHub tokens require the trusted-vm/);
const trusted = spawnSync(process.execPath, [cli], {
encoding: 'utf8',
env: { ...base, LIBRECHAT_CODE_COMMAND_POLICY_PRESET: 'trusted-vm' },
});
assert.doesNotMatch(trusted.stderr, /Installation-scoped GitHub tokens require/);
const fixed = spawnSync(process.execPath, [cli], {
encoding: 'utf8',
env: { ...base, LIBRECHAT_CODE_GITHUB_INSTALLATION_ID: '456' },
});
assert.match(fixed.stderr, /without a fixed installation ID/);
});

test('CLI requires a runtime image for Docker supervision', () => {
const result = spawnSync(
process.execPath,
Expand Down
23 changes: 22 additions & 1 deletion packages/code/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ function githubCredentials(args: string[]): {
mode?: 'app' | 'token';
repositoryRouting?: boolean;
checkoutRouting?: boolean;
installationTokenScope?: boolean;
policyIdentity: string;
} {
const token = nonEmpty(process.env.LIBRECHAT_CODE_GITHUB_TOKEN);
Expand Down Expand Up @@ -191,6 +192,18 @@ function githubCredentials(args: string[]): {
'Checkout GitHub repository routing requires a GitHub App without a fixed installation ID',
);
}
const tokenScope =
option(args, '--github-token-scope')?.trim().toLowerCase() ??
process.env.LIBRECHAT_CODE_GITHUB_TOKEN_SCOPE?.trim().toLowerCase() ??
'repository';
if (tokenScope !== 'repository' && tokenScope !== 'installation') {
throw new Error('GitHub token scope must be repository or installation');
}
if (tokenScope === 'installation' && (!hasApp || installationId)) {
throw new Error(
'Installation-scoped GitHub tokens require a GitHub App without a fixed installation ID',
);
}
const configuredHostValue = nonEmpty(
process.env.LIBRECHAT_CODE_GITHUB_HOST,
);
Expand Down Expand Up @@ -225,16 +238,19 @@ function githubCredentials(args: string[]): {
mode: 'app',
repositoryRouting: !installationId,
checkoutRouting: routing === 'checkout',
installationTokenScope: tokenScope === 'installation',
policyIdentity: gitHubAuthenticationPolicyIdentity({
mode: 'app',
host,
appId,
installationId,
}) + (routing === 'checkout' ? ':routing:checkout' : ''),
}) + (routing === 'checkout' ? ':routing:checkout' : '') +
(tokenScope === 'installation' ? ':scope:installation' : ''),
privateKeyPath,
provider: new GitHubAppCredentialProvider({
appId: appId!,
installationId,
tokenScope,
privateKeyPath: privateKeyPath!,
host,
apiUrl,
Expand Down Expand Up @@ -568,6 +584,11 @@ async function run(
'Checkout GitHub repository routing requires the trusted-vm command policy',
);
}
if (github.installationTokenScope && commandPolicy.preset !== 'trusted-vm') {
throw new Error(
'Installation-scoped GitHub tokens require the trusted-vm command policy',
);
}
const githubDomains = github.provider
? github.host === 'github.com'
? [...GITHUB_ALLOWED_DOMAINS]
Expand Down
Loading
Loading