Skip to content

Commit 3752622

Browse files
wip v28
1 parent cf1fe02 commit 3752622

8 files changed

Lines changed: 102 additions & 4 deletions

File tree

BITCODE_SPEC_V28.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,12 @@ Read/Fit result review remains fail-closed:
521521
- secrets for wallets, GitHub, model providers, Supabase service roles, or
522522
other systems may be passed into a sandbox only by explicit allowlist or
523523
brokered network policy; routine QA artifacts must show only redacted names.
524+
- pull-request delivery should normally use the reader's authenticated GitHub
525+
App connection. For trusted local/staging harness QA, an operator token may be
526+
supplied only by setting `BITCODE_VCS_ALLOW_ENV_TOKEN_FALLBACK=1` and
527+
explicitly forwarding `GITHUB_TOKEN` through the sandbox allowlist; VCS tools
528+
must read that token from process environment without placing it in tool
529+
inputs, artifact telemetry, or database stream rows.
524530
- `BITCODE_LLM_PROVIDER` and `BITCODE_LLM_MODEL` may pin the model path for
525531
reproducible generation; otherwise the runtime selects the available provider
526532
from credential posture, with OpenAI accepted when `OPENAI_API_KEY` is the

BITCODE_V28_QA.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1155,9 +1155,21 @@ BITCODE_SANDBOX_SOURCE_COMMIT=31bbc0c5227b6b3aed5d107fd8507d35ec22970a \
11551155
BITCODE_SANDBOX_SOURCE_REVISION=31bbc0c5227b6b3aed5d107fd8507d35ec22970a \
11561156
BITCODE_SANDBOX_DEPOSIT_HAS_PROOF=1 \
11571157
BITCODE_SANDBOX_DEPOSIT_HAS_MEASUREMENT=1 \
1158+
BITCODE_ASSET_PACK_REAL_INFERENCE=1 \
1159+
BITCODE_ASSET_PACK_REAL_INFERENCE_PROFILE=bounded \
1160+
BITCODE_VCS_ALLOW_ENV_TOKEN_FALLBACK=1 \
1161+
BITCODE_SANDBOX_ENV_KEYS=SUPABASE_URL,SUPABASE_SERVICE_ROLE_KEY,OPENAI_API_KEY,BITCODE_ASSET_PACK_REAL_INFERENCE,BITCODE_ASSET_PACK_REAL_INFERENCE_PROFILE,BITCODE_PIPELINE_HARNESS_MAX_RUNTIME_MS,GITHUB_TOKEN \
1162+
BITCODE_PIPELINE_HARNESS_MAX_RUNTIME_MS=600000 \
11581163
pnpm run qa:pipeline-harness:sandbox
11591164
```
11601165
1166+
`BITCODE_VCS_ALLOW_ENV_TOKEN_FALLBACK=1` is only for trusted local/staging
1167+
operator runs where the stored GitHub App installation token has expired and
1168+
cannot be refreshed from app credentials. It lets VCS tools read `GITHUB_TOKEN`
1169+
from process environment without adding the token to tool input payloads,
1170+
artifact telemetry, or database stream rows. Product runtime should still use
1171+
the authenticated user's GitHub App connection whenever possible.
1172+
11611173
Before promotion, operators may add `BITCODE_SANDBOX_APPLY_LOCAL_PATCH=1` to
11621174
overlay the current worktree on the cloned source revision. This is only for
11631175
debugging the harness and pipeline implementation before deployment. Overlay
@@ -1185,7 +1197,8 @@ BITCODE_PIPELINE_STREAM_TO_DATABASE=1 \
11851197
BITCODE_PIPELINE_STRUCTURED_DB=1 \
11861198
BITCODE_ASSET_PACK_REAL_INFERENCE=1 \
11871199
BITCODE_ASSET_PACK_REAL_INFERENCE_PROFILE=bounded \
1188-
BITCODE_SANDBOX_ENV_KEYS=SUPABASE_URL,SUPABASE_SERVICE_ROLE_KEY,OPENAI_API_KEY,BITCODE_ASSET_PACK_REAL_INFERENCE,BITCODE_ASSET_PACK_REAL_INFERENCE_PROFILE,BITCODE_PIPELINE_HARNESS_MAX_RUNTIME_MS
1200+
BITCODE_VCS_ALLOW_ENV_TOKEN_FALLBACK=1 \
1201+
BITCODE_SANDBOX_ENV_KEYS=SUPABASE_URL,SUPABASE_SERVICE_ROLE_KEY,OPENAI_API_KEY,BITCODE_ASSET_PACK_REAL_INFERENCE,BITCODE_ASSET_PACK_REAL_INFERENCE_PROFILE,BITCODE_PIPELINE_HARNESS_MAX_RUNTIME_MS,GITHUB_TOKEN
11891202
```
11901203
11911204
These Supabase values must be real staging credentials. Placeholder hosts such

packages/generic-tools/vcs/__tests__/createOrUpdateFileTool.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ const baseInput = {
5151
describe('createOrUpdateFileTool gating', () => {
5252
beforeEach(() => {
5353
jest.clearAllMocks();
54+
delete process.env.BITCODE_VCS_ALLOW_ENV_TOKEN_FALLBACK;
55+
delete process.env.GITHUB_TOKEN;
5456
(VCSConnections as jest.Mock).mockImplementation(() => mockConnectionManager);
5557
(VCSProviderFactory.create as jest.Mock).mockResolvedValue(mockProvider);
5658
mockConnectionManager.getConnectionById.mockResolvedValue({ id: 'conn-1' });
@@ -130,4 +132,29 @@ describe('createOrUpdateFileTool gating', () => {
130132
'abc123'
131133
);
132134
});
135+
136+
it('uses explicit environment delivery authority without loading a stored connection', async () => {
137+
process.env.BITCODE_VCS_ALLOW_ENV_TOKEN_FALLBACK = '1';
138+
process.env.GITHUB_TOKEN = 'gh-test-token';
139+
140+
await createBranchTool.use({
141+
provider: 'github',
142+
owner: 'bitcode-labs',
143+
repo: 'repo',
144+
branch: 'feature/asset-pack',
145+
from: 'abc123',
146+
});
147+
148+
expect(VCSConnections).not.toHaveBeenCalled();
149+
expect(mockProvider.createBranch).toHaveBeenCalledWith(
150+
expect.objectContaining({
151+
provider: 'github',
152+
accessToken: 'gh-test-token',
153+
}),
154+
'bitcode-labs',
155+
'repo',
156+
'feature/asset-pack',
157+
'abc123'
158+
);
159+
});
133160
});

packages/generic-tools/vcs/src/index.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,11 @@ async function resolveConnectionContext(input: VcsInputBase): Promise<ResolvedCo
7777
throw new Error('VCS provider is required');
7878
}
7979

80+
const environmentAuth = resolveEnvironmentAuth(input.provider);
81+
if (environmentAuth) {
82+
return { auth: environmentAuth };
83+
}
84+
8085
const connectionManager = await createConnectionManager();
8186
let auth: VCSAuth | null = null;
8287
let instanceUrl: string | undefined;
@@ -100,6 +105,18 @@ async function resolveConnectionContext(input: VcsInputBase): Promise<ResolvedCo
100105
return { auth, instanceUrl };
101106
}
102107

108+
function resolveEnvironmentAuth(provider: VCSProviderType): VCSAuth | null {
109+
if (process.env.BITCODE_VCS_ALLOW_ENV_TOKEN_FALLBACK !== '1') return null;
110+
if (provider !== 'github') return null;
111+
112+
const accessToken =
113+
process.env.GITHUB_TOKEN ||
114+
process.env.GITHUB_PAT ||
115+
process.env.GH_TOKEN;
116+
117+
return accessToken ? { provider, accessToken } : null;
118+
}
119+
103120
function buildProviderConfig(
104121
provider: VCSProviderType,
105122
instanceUrl?: string,

packages/pipeline-hosts/README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,13 @@ trusted command environment. For local Vercel Sandbox creation, either pull
8686
token tuple `VERCEL_TOKEN`, `VERCEL_TEAM_ID`, and `VERCEL_PROJECT_ID`. Deployed
8787
Vercel code should use automatic OIDC rather than storing a Vercel token when
8888
possible.
89+
Pull-request delivery normally uses the authenticated user's stored GitHub App
90+
connection. GitHub installation tokens are short lived; if staging readback
91+
shows the stored token has expired and the local harness is intentionally using
92+
a trusted operator token, set `BITCODE_VCS_ALLOW_ENV_TOKEN_FALLBACK=1` and pass
93+
`GITHUB_TOKEN` only through `BITCODE_SANDBOX_ENV_KEYS=GITHUB_TOKEN`. The VCS
94+
tools read that token from process environment and do not include it in tool
95+
inputs, artifact telemetry, or database stream rows.
8996
Staging-testnet Read/Fit QA must also set
9097
`BITCODE_ASSET_PACK_REAL_INFERENCE=1`. On the deployed streaming route,
9198
`BITCODE_ASSET_PACK_REAL_INFERENCE_PROFILE=bounded` is the expected profile:
@@ -210,6 +217,8 @@ BITCODE_SANDBOX_DEPOSIT_HAS_PROOF=1 \
210217
BITCODE_SANDBOX_DEPOSIT_HAS_MEASUREMENT=1 \
211218
BITCODE_ASSET_PACK_REAL_INFERENCE=1 \
212219
BITCODE_ASSET_PACK_REAL_INFERENCE_PROFILE=bounded \
220+
BITCODE_VCS_ALLOW_ENV_TOKEN_FALLBACK=1 \
221+
BITCODE_SANDBOX_ENV_KEYS=GITHUB_TOKEN \
213222
BITCODE_PIPELINE_HARNESS_MAX_RUNTIME_MS=600000 \
214223
pnpm -C packages/pipeline-hosts run qa:asset-pack:sandbox
215224
```

packages/pipeline-hosts/src/dev/run-asset-pack-sandbox-harness.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ const TRUSTED_SANDBOX_ENV_KEYS = [
3939
'BITCODE_ASSET_PACK_READY_TO_INSTRUCT_USE_PTRR',
4040
'BITCODE_ASSET_PACK_VALIDATION_READY_TO_FINISH_USE_PTRR',
4141
'BITCODE_ASSET_PACK_FINISH_DELIVER_USE_PTRR',
42+
'BITCODE_VCS_ALLOW_ENV_TOKEN_FALLBACK',
4243
'BITCODE_PIPELINE_HARNESS_MAX_RUNTIME_MS',
4344
] as const;
4445

packages/pipelines/asset-pack/src/__tests__/finish-delivery.test.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import deliverAssetPackToDestination from '../agents/finish/deliver-asset-pack-t
2121
describe('finish pull-request delivery', () => {
2222
beforeEach(() => {
2323
jest.resetAllMocks();
24+
delete process.env.BITCODE_VCS_ALLOW_ENV_TOKEN_FALLBACK;
25+
delete process.env.GITHUB_TOKEN;
2426
createBranchUse.mockResolvedValue({ name: 'bitcode/asset-pack-run-123' });
2527
createOrUpdateFileUse.mockResolvedValue({ path: '.bitcode/asset-packs/run-123.md', content: 'written' });
2628
createPullRequestUse.mockResolvedValue({
@@ -118,11 +120,28 @@ describe('finish pull-request delivery', () => {
118120
const result = await deliverAssetPackToDestination({}, exec);
119121

120122
expect(result.status).toBe('blocked_readiness');
121-
expect(result.reason).toContain('GitHub connectionId or pipeline userId');
123+
expect(result.reason).toContain('GitHub connectionId, pipeline userId, or explicit environment delivery authority');
122124
expect(createBranchUse).not.toHaveBeenCalled();
123125
expect(exec.get('finish', 'deliveryReadiness')).toMatchObject({
124126
status: 'blocked_readiness',
125127
prUrl: null,
126128
});
127129
});
130+
131+
it('allows explicit environment delivery authority without emitting token material', async () => {
132+
process.env.BITCODE_VCS_ALLOW_ENV_TOKEN_FALLBACK = '1';
133+
process.env.GITHUB_TOKEN = 'gh-test-token';
134+
const exec = execution();
135+
exec.store('pipeline', 'userId', undefined);
136+
137+
const result = await deliverAssetPackToDestination({}, exec);
138+
139+
expect(result.status).toBe('delivered');
140+
expect(createBranchUse).toHaveBeenCalledWith(expect.not.objectContaining({
141+
accessToken: expect.anything(),
142+
auth: expect.anything(),
143+
}));
144+
expect(exec.get('tools', 'vcs_create_branch:0').input).not.toHaveProperty('accessToken');
145+
expect(emitToolUsage.mock.calls[0][2]).not.toHaveProperty('accessToken');
146+
});
128147
});

packages/pipelines/asset-pack/src/agents/finish/deliver-asset-pack-to-destination-agent.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,8 @@ async function deliverPullRequest(
137137
return blockDelivery(execution, context, 'Pull-request delivery requires owner, repository, branch, and source commit.');
138138
}
139139

140-
if (!delivery.userId && !delivery.connectionId) {
141-
return blockDelivery(execution, context, 'Pull-request delivery requires a GitHub connectionId or pipeline userId.');
140+
if (!delivery.userId && !delivery.connectionId && !hasEnvironmentDeliveryAuth(repository.provider)) {
141+
return blockDelivery(execution, context, 'Pull-request delivery requires a GitHub connectionId, pipeline userId, or explicit environment delivery authority.');
142142
}
143143

144144
const common = {
@@ -234,6 +234,12 @@ async function deliverPullRequest(
234234
}
235235
}
236236

237+
function hasEnvironmentDeliveryAuth(provider: string): boolean {
238+
if (process.env.BITCODE_VCS_ALLOW_ENV_TOKEN_FALLBACK !== '1') return false;
239+
if (provider !== 'github') return false;
240+
return Boolean(process.env.GITHUB_TOKEN || process.env.GITHUB_PAT || process.env.GH_TOKEN);
241+
}
242+
237243
function resolveRepositoryContext(input: any, execution: any) {
238244
const storedPipelineInput = findExecutionValue(execution, 'pipeline', 'input') || {};
239245
const sourceRevision =

0 commit comments

Comments
 (0)