mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-27 19:35:13 +01:00
Merge master after native tracing landed
Preserve the verified native parent and portable controller additions. Include the separately merged protected Copilot credential selection fix from upstream master. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
commit
af3dcca98d
7 files changed
+67
-3
No files matched your search
@@ -1184,6 +1184,7 @@ jobs:
|
||||
OPENROUTER_API_KEY: ${{ matrix.credentialName == 'OPENROUTER_API_KEY' && secrets.OPENROUTER_API_KEY || '' }}
|
||||
XAI_API_KEY: ${{ matrix.credentialName == 'XAI_API_KEY' && secrets.XAI_API_KEY || '' }}
|
||||
GROK_AUTH_JSON: ${{ matrix.credentialName == 'GROK_AUTH_JSON' && secrets.GROK_AUTH_JSON || '' }}
|
||||
COPILOT_GITHUB_TOKEN: ${{ matrix.credentialName == 'COPILOT_GITHUB_TOKEN' && secrets.COPILOT_GITHUB_TOKEN || '' }}
|
||||
DAYTONA_API_KEY: ${{ matrix.environmentId == 'daytona' && secrets.DAYTONA_API_KEY || '' }}
|
||||
PAPERCLIP_E2E_DAYTONA_IMAGE: ${{ needs.daytona_image.outputs.image }}
|
||||
PAPERCLIP_RUNNER_REMOTE_PROVIDER_PACK_PATH: ${{ github.workspace }}/packages/paperclip-runner/provider-pack
|
||||
|
||||
@@ -553,6 +553,7 @@ jobs:
|
||||
OPENROUTER_API_KEY: ${{ matrix.credentialName == 'OPENROUTER_API_KEY' && secrets.OPENROUTER_API_KEY || '' }}
|
||||
XAI_API_KEY: ${{ matrix.credentialName == 'XAI_API_KEY' && secrets.XAI_API_KEY || '' }}
|
||||
PAPERCLIP_ACPX_GROK_AUTH_JSON_SECRET: ${{ matrix.credentialName == 'PAPERCLIP_ACPX_GROK_AUTH_JSON_SECRET' && secrets.GROK_AUTH_JSON || '' }}
|
||||
COPILOT_GITHUB_TOKEN: ${{ matrix.credentialName == 'COPILOT_GITHUB_TOKEN' && secrets.COPILOT_GITHUB_TOKEN || '' }}
|
||||
PAPERCLIP_CLAUDE_MANAGED_PROFILE_ID: ${{ vars.PAPERCLIP_CLAUDE_MANAGED_PROFILE_ID }}
|
||||
PAPERCLIP_CLAUDE_MANAGED_AGENT_ID: ${{ vars.PAPERCLIP_CLAUDE_MANAGED_AGENT_ID }}
|
||||
PAPERCLIP_CLAUDE_MANAGED_AGENT_VERSION: ${{ vars.PAPERCLIP_CLAUDE_MANAGED_AGENT_VERSION }}
|
||||
|
||||
@@ -72,6 +72,7 @@ export function credentialForConfig(config, grokAuthenticationMode = "api_key")
|
||||
if (config.provider === "acpx") {
|
||||
if (config.acpxAgent === "pi") return "OPENROUTER_API_KEY";
|
||||
if (config.acpxAgent === "claude") return "ANTHROPIC_API_KEY";
|
||||
if (config.acpxAgent === "copilot") return "COPILOT_GITHUB_TOKEN";
|
||||
if (config.acpxAgent === "grok") {
|
||||
return validateGrokAuthenticationMode(grokAuthenticationMode) === "subscription"
|
||||
? "PAPERCLIP_ACPX_GROK_AUTH_JSON_SECRET"
|
||||
|
||||
@@ -104,6 +104,30 @@ test("maps every qualified driver to one explicit credential boundary", () => {
|
||||
);
|
||||
});
|
||||
|
||||
test("selects the Copilot token for the exact protocol roster and execution matrix", async () => {
|
||||
const { root, program, config } = await fixture();
|
||||
await writeFile(join(program, "configs/live-opencode-model.json"), JSON.stringify({
|
||||
...config,
|
||||
provider: "acpx",
|
||||
driver: "acpx_runtime",
|
||||
acpxAgent: "copilot",
|
||||
model: "gpt-5.6-luna",
|
||||
}));
|
||||
const catalog = await buildProtocolEvalCatalog({
|
||||
evalsRoot: root,
|
||||
campaignId: "copilot-credential-01",
|
||||
rosterSelection: "live-opencode-model.json",
|
||||
});
|
||||
assert.equal(catalog.rosters.length, 1);
|
||||
assert.equal(catalog.rosters[0].credentialName, "COPILOT_GITHUB_TOKEN");
|
||||
assert.equal(catalog.cells.length, 1);
|
||||
assert.equal(catalog.cells[0].credentialName, "COPILOT_GITHUB_TOKEN");
|
||||
assert.deepEqual(
|
||||
catalog.matrices.flatMap(matrix => matrix.include).map(cell => cell.credentialName),
|
||||
["COPILOT_GITHUB_TOKEN"],
|
||||
);
|
||||
});
|
||||
|
||||
async function grokFixture() {
|
||||
const value = await fixture();
|
||||
value.config = {
|
||||
|
||||
@@ -38,6 +38,14 @@ test("direct live eval workflow keeps paid execution behind stable actor authori
|
||||
assert.match(workflow, /github\.triggering_actor/u);
|
||||
assert.match(workflow, /refs\/heads\/\$DEFAULT_BRANCH/u);
|
||||
assert.match(workflow, /Reauthorize paid execution before provider access/u);
|
||||
const copilotCredential = "COPILOT_GITHUB_TOKEN: ${{ matrix.credentialName == 'COPILOT_GITHUB_TOKEN' && secrets.COPILOT_GITHUB_TOKEN || '' }}";
|
||||
assert.equal(workflow.split(copilotCredential).length - 1, 1);
|
||||
const paid = workflow.slice(workflow.indexOf(" eval_shard_0:"), workflow.indexOf(" eval_shard_1:"));
|
||||
assert.ok(paid.includes("name: runner-e2e-paid"));
|
||||
assert.ok(paid.includes(copilotCredential));
|
||||
const reauthorizationIndex = paid.indexOf("Reauthorize paid execution before provider access");
|
||||
assert.ok(reauthorizationIndex >= 0);
|
||||
assert.ok(reauthorizationIndex < paid.indexOf(copilotCredential));
|
||||
assert.match(
|
||||
workflow,
|
||||
/Reauthorize paid execution before provider access[\s\S]*actions\/checkout@[0-9a-f]{40}[\s\S]*Run one immutable direct protocol cell/u,
|
||||
|
||||
@@ -129,8 +129,12 @@ and account identifiers inside its JSON join the evidence redaction set. The
|
||||
subscription suite remains manual-only and never falls back to `XAI_API_KEY`.
|
||||
|
||||
Create `runner-e2e-paid`, restrict deployments to the default branch, and put
|
||||
only `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `OPENROUTER_API_KEY`, `XAI_API_KEY`, and
|
||||
`DAYTONA_API_KEY` in it. `KIMI_MODEL_API_KEY` remains a local-only catalog name
|
||||
only provider credentials needed by the selected paid suites, including
|
||||
`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `OPENROUTER_API_KEY`, `XAI_API_KEY`,
|
||||
`COPILOT_GITHUB_TOKEN` and `DAYTONA_API_KEY` in it. Copilot is passed only to
|
||||
cells selecting `credentialName: COPILOT_GITHUB_TOKEN`, in the paid Product or
|
||||
direct-protocol execution step. Never expose it to ordinary PR, build, catalog,
|
||||
image or report jobs. `KIMI_MODEL_API_KEY` remains a local-only catalog name
|
||||
for pending explicit profiles and is intentionally absent from the workflow
|
||||
environment. Product E2E pending-profile admission remains gated separately.
|
||||
Do not duplicate these credentials as repository- or
|
||||
|
||||
@@ -15,6 +15,30 @@ const everydayOracleImage =
|
||||
"python@sha256:9d2e5553305c7c7b0097999bb17187c69b921ccd6bc9d40e4bb5ebe652c00285";
|
||||
|
||||
describe("public repository paid workflow security", () => {
|
||||
it("exposes Copilot only to selected cells in the protected, reauthorized paid jobs", async () => {
|
||||
for (const [filename, jobName, nextJobName, stepName] of [
|
||||
["runner-full-stack-e2e.yml", "test", "report", "Run paid cell"],
|
||||
["runner-protocol-live-evals.yml", "eval_shard_0", "eval_shard_1", "Run one immutable direct protocol cell"],
|
||||
] as const) {
|
||||
const workflow = await readFile(path.join(repositoryRoot, ".github/workflows", filename), "utf8");
|
||||
const credentialSteps = workflow.split(/\n(?= {6}- )/u).filter(step => step.includes("secrets.COPILOT_GITHUB_TOKEN"));
|
||||
expect(credentialSteps, filename).toHaveLength(1);
|
||||
const credentialStep = credentialSteps[0]!;
|
||||
expect(credentialStep, filename).toContain(`name: ${stepName}`);
|
||||
expect(credentialStep, filename).toContain("COPILOT_GITHUB_TOKEN: ${{ matrix.credentialName == 'COPILOT_GITHUB_TOKEN' && secrets.COPILOT_GITHUB_TOKEN || '' }}");
|
||||
const jobStart = workflow.indexOf(`\n ${jobName}:`);
|
||||
const jobEnd = workflow.indexOf(`\n ${nextJobName}:`, jobStart + 1);
|
||||
expect(jobStart, filename).toBeGreaterThanOrEqual(0);
|
||||
expect(jobEnd, filename).toBeGreaterThan(jobStart);
|
||||
const paidJob = workflow.slice(jobStart, jobEnd);
|
||||
expect(paidJob, filename).toContain("name: runner-e2e-paid");
|
||||
expect(paidJob, filename).toContain(credentialStep);
|
||||
const reauthorizationIndex = paidJob.indexOf("Reauthorize paid execution before provider access");
|
||||
expect(reauthorizationIndex, filename).toBeGreaterThanOrEqual(0);
|
||||
expect(reauthorizationIndex, filename).toBeLessThan(paidJob.indexOf("secrets.COPILOT_GITHUB_TOKEN"));
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps the manual hosted Linux image build credential-free and pins the authorized target", async () => {
|
||||
const workflow = await readFile(path.join(repositoryRoot, ".github/workflows/docker-runner-check.yml"), "utf8");
|
||||
const manual = workflow.slice(workflow.indexOf(" authorize_manual:"));
|
||||
@@ -549,6 +573,7 @@ describe("public repository paid workflow security", () => {
|
||||
OPENROUTER_API_KEY: "matrix.credentialName == 'OPENROUTER_API_KEY'",
|
||||
XAI_API_KEY: "matrix.credentialName == 'XAI_API_KEY'",
|
||||
GROK_AUTH_JSON: "matrix.credentialName == 'GROK_AUTH_JSON'",
|
||||
COPILOT_GITHUB_TOKEN: "matrix.credentialName == 'COPILOT_GITHUB_TOKEN'",
|
||||
DAYTONA_API_KEY: "matrix.environmentId == 'daytona'",
|
||||
})) {
|
||||
expect(fullStack).toContain(
|
||||
@@ -576,7 +601,7 @@ describe("public repository paid workflow security", () => {
|
||||
);
|
||||
const providerSecretReferences = [
|
||||
...contents.matchAll(
|
||||
/secrets(?:\.(?:OPENAI_API_KEY|ANTHROPIC_API_KEY|OPENROUTER_API_KEY|XAI_API_KEY|GROK_AUTH_JSON|DAYTONA_API_KEY)\b|\[['"](?:OPENAI_API_KEY|ANTHROPIC_API_KEY|OPENROUTER_API_KEY|XAI_API_KEY|GROK_AUTH_JSON|DAYTONA_API_KEY)['"]\])/g,
|
||||
/secrets(?:\.(?:OPENAI_API_KEY|ANTHROPIC_API_KEY|OPENROUTER_API_KEY|XAI_API_KEY|GROK_AUTH_JSON|DAYTONA_API_KEY|COPILOT_GITHUB_TOKEN)\b|\[['"](?:OPENAI_API_KEY|ANTHROPIC_API_KEY|OPENROUTER_API_KEY|XAI_API_KEY|GROK_AUTH_JSON|DAYTONA_API_KEY|COPILOT_GITHUB_TOKEN)['"]\])/g,
|
||||
),
|
||||
];
|
||||
if (providerSecretReferences.length > 0) {
|
||||
|
||||
Reference in new issue
Block a user