fix(runner-e2e): prepare frozen Daytona plugin dependencies (#12791)
## Thinking Path
> - Paperclip manages AI agents and their provider runtimes.
> - The paid runner workflow installs target dependencies with lifecycle
scripts disabled.
> - The bundled Daytona plugin depends on an audited repo-local plugin
SDK link.
> - The lifecycle-safe install path did not create that link.
> - This pull request restores only the trusted Daytona preparation step
before provider secrets are exposed.
> - The benefit is a working Daytona canary without enabling dependency
lifecycle scripts.
## Linked Issues or Issue Description
**What happened?**
The Daytona paid canary stopped before lease or provider startup. The
trusted paid job disabled root lifecycle scripts, so the repo-local
plugin SDK link was absent. The plugin install returned a missing
runtime dependency error for @paperclipai/plugin-sdk.
**Expected behavior**
The trusted workflow must prepare the bundled Daytona plugin without
running untrusted dependency lifecycle scripts. The paid cell must start
only after its runtime dependencies and entrypoints pass validation.
**Steps to reproduce**
1. Dispatch the runner full-stack paid workflow for
core-compatibility.runner-acpx-claude.daytona.message-marker.
2. Let the trusted job install root dependencies with lifecycle scripts
disabled.
3. Observe the Daytona plugin installation fail before a lease or
provider process starts.
**Paperclip version or commit**
Feature head 781ac7e08c. The failed canary
is Actions run 33803959325.
**Deployment mode**
GitHub Actions paid runner validation.
**Agent adapter(s) involved**
ACPX Claude through the bundled Daytona plugin.
**Additional context**
This is a small trusted-workflow prerequisite for public PR #12769. Old
green run 33118525827 created the SDK link through root postinstall.
This change keeps lifecycle scripts disabled and restores only the
audited prerequisite.
## What Changed
- Install standalone Daytona dependencies with lifecycle scripts
disabled.
- Run the audited repo-local plugin SDK linker before provider secrets
are exposed.
- Build the bundled Daytona plugin and verify its runtime dependency
plus both entrypoints.
- Add a security regression for ordering, scope, and secret isolation.
## Verification
- Five focused workflow-security tests passed.
- Seven focused linker tests passed.
- The exact Daytona preparation command completed locally in nine
seconds.
- Prettier and diff whitespace checks passed.
## Risks
Risk is low and limited to Daytona paid cells. The setup still disables
dependency lifecycle scripts. The trusted step runs before provider
credentials enter the job. Any missing or mismatched path fails closed
before provider startup.
## Model Used
OpenAI GPT-5 Codex with repository tools and code execution.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used with version and capability
details
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have described the issue in this PR with the bug template labels
- [x] I have not referenced internal or instance-local Paperclip issues
or links
- [x] My branch name describes the change
- [x] Focused local tests pass
- [x] I added tests for the change
- [x] I updated the relevant trusted-workflow security regression
- [x] I documented the risks above
This commit is contained in:
parent
e0d5f02b8e
commit
d3c04d8932
|
|
@ -766,6 +766,35 @@ jobs:
|
|||
# the protected environment during setup.
|
||||
- run: pnpm install --frozen-lockfile --ignore-scripts
|
||||
|
||||
# Sandbox-provider plugins are intentionally excluded from the root
|
||||
# workspace. The ordinary root postinstall links the in-repo plugin SDK,
|
||||
# but that lifecycle hook is deliberately disabled above. Prepare the
|
||||
# one host plugin needed by Daytona explicitly, before this job receives
|
||||
# provider credentials, and keep dependency lifecycle scripts disabled.
|
||||
- name: Prepare bundled Daytona plugin without dependency lifecycle scripts
|
||||
if: matrix.environmentId == 'daytona'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
daytona_root="packages/plugins/sandbox-providers/daytona"
|
||||
sdk_root="packages/plugins/sdk"
|
||||
test -d "$daytona_root"
|
||||
test -d "$sdk_root"
|
||||
test ! -L "$daytona_root"
|
||||
test ! -L "$sdk_root"
|
||||
test -f "$daytona_root/pnpm-lock.yaml"
|
||||
test "$(jq -r .name "$daytona_root/package.json")" = "@paperclipai/plugin-daytona"
|
||||
test "$(jq -r .name "$sdk_root/package.json")" = "@paperclipai/plugin-sdk"
|
||||
(
|
||||
cd "$daytona_root"
|
||||
pnpm install --ignore-workspace --frozen-lockfile --ignore-scripts
|
||||
)
|
||||
node scripts/link-plugin-dev-sdk.mjs
|
||||
test "$(realpath "$daytona_root/node_modules/@paperclipai/plugin-sdk")" = "$(realpath "$sdk_root")"
|
||||
pnpm --dir "$daytona_root" build
|
||||
test -f "$daytona_root/dist/manifest.js"
|
||||
test -f "$daytona_root/dist/worker.js"
|
||||
test -e "$daytona_root/node_modules/@daytonaio/sdk"
|
||||
|
||||
- name: Materialize verified pinned OpenCode executable
|
||||
if: matrix.environmentId == 'local' && (matrix.profileId == 'legacy-opencode' || matrix.profileId == 'runner-opencode' || matrix.suiteId == 'openrouter-model-breadth')
|
||||
run: node packages/paperclip-runner/scripts/materialize-opencode-binary.mjs
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -125,9 +125,42 @@ describe("public repository paid workflow security", () => {
|
|||
const paidInstall = paidJob.indexOf(
|
||||
"pnpm install --frozen-lockfile --ignore-scripts",
|
||||
);
|
||||
const daytonaPluginPreparation = paidJob.indexOf(
|
||||
"Prepare bundled Daytona plugin without dependency lifecycle scripts",
|
||||
);
|
||||
const paidExecution = paidJob.indexOf("- name: Run paid cell");
|
||||
expect(paidInstall).toBeGreaterThan(0);
|
||||
expect(paidExecution).toBeGreaterThan(paidInstall);
|
||||
expect(daytonaPluginPreparation).toBeGreaterThan(paidInstall);
|
||||
expect(paidExecution).toBeGreaterThan(daytonaPluginPreparation);
|
||||
const preparedBeforeProviderAccess = paidJob.slice(
|
||||
daytonaPluginPreparation,
|
||||
paidExecution,
|
||||
);
|
||||
expect(preparedBeforeProviderAccess).toContain(
|
||||
"if: matrix.environmentId == 'daytona'",
|
||||
);
|
||||
expect(preparedBeforeProviderAccess).toContain(
|
||||
'test -f "$daytona_root/pnpm-lock.yaml"',
|
||||
);
|
||||
expect(preparedBeforeProviderAccess).toContain(
|
||||
"pnpm install --ignore-workspace --frozen-lockfile --ignore-scripts",
|
||||
);
|
||||
expect(preparedBeforeProviderAccess).not.toContain("--no-lockfile");
|
||||
expect(preparedBeforeProviderAccess).toContain(
|
||||
"node scripts/link-plugin-dev-sdk.mjs",
|
||||
);
|
||||
expect(preparedBeforeProviderAccess).toContain(
|
||||
'"@paperclipai/plugin-daytona"',
|
||||
);
|
||||
expect(preparedBeforeProviderAccess).toContain('"@paperclipai/plugin-sdk"');
|
||||
expect(preparedBeforeProviderAccess).toContain(
|
||||
'realpath "$daytona_root/node_modules/@paperclipai/plugin-sdk"',
|
||||
);
|
||||
expect(preparedBeforeProviderAccess).toContain(
|
||||
'pnpm --dir "$daytona_root" build',
|
||||
);
|
||||
expect(preparedBeforeProviderAccess).not.toContain("secrets.");
|
||||
expect(preparedBeforeProviderAccess).not.toContain("pnpm rebuild");
|
||||
expect(paidJob.slice(0, paidExecution)).not.toMatch(
|
||||
/secrets\.(?:OPENAI|ANTHROPIC|OPENROUTER|DAYTONA)_API_KEY/,
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in New Issue