Skip to content
Closed
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
208 changes: 208 additions & 0 deletions .github/workflows/sandbox-features.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
name: Sandbox Feature Matrix

on:
pull_request:
paths:
- 'src/sandbox/**'
- 'src/util/**'
- 'src/run.ts'
- 'src/merge-results.ts'
- '.github/workflows/sandbox-features.yml'
- 'package.json'
schedule:
- cron: '0 6 * * *'
workflow_dispatch:

permissions: {}

jobs:
bench:
name: Features ${{ matrix.provider }}
runs-on: namespace-profile-default

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0: Self-hosted runner reachable by fork PRs in sandbox-features workflow

Self-hosted runner used with pull_request trigger exposes runner to fork PRs.

Verify runner scoping or use GitHub-hosted runners for PR jobs.

AI prompt
Check if this security scanner issue is valid. If so, understand the root cause and fix it. If appropriate, update or add tests. Keep the change focused and preserve intended behavior.

<file name=".github/workflows/sandbox-features.yml">
<violation number="1" location=".github/workflows/sandbox-features.yml:21">
<priority>P0</priority>
<title>Self-hosted runner reachable by fork PRs in sandbox-features workflow</title>
<evidence>The workflow triggers on pull_request and runs on a self-hosted runner labeled namespace-profile-default. Fork PRs can execute code on this runner, which shares state across jobs and has been the root cause of critical supply-chain compromises (e.g., PyTorch). The bench job runs arbitrary benchmark code from the checked-out repository on this runner.</evidence>
<recommendation>Restrict the self-hosted runner to non-fork PRs by verifying scoping in repository Settings &gt; Actions &gt; Runners, or switch PR jobs to GitHub-hosted runners (e.g., ubuntu-latest). If the runner must be used, add an explicit if: github.event.pull_request.head.repo.full_name == github.repository guard or move the PR job to a separate workflow that uses GitHub-hosted runners.</recommendation>
</violation>
</file>

timeout-minutes: 25
permissions:
contents: read
concurrency:
group: sandbox-features-${{ github.event_name }}-${{ github.ref }}-${{ matrix.provider }}
cancel-in-progress: true
strategy:
fail-fast: false
matrix:
provider:
- archil
- blaxel
- cloudflare
- codesandbox
- daytona
- declaw
- e2b
- hopx
- isorun
- modal
- northflank
- runloop
- tensorlake
- upstash
- vercel
steps:
- uses: actions/checkout@v4

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Actions pinned to mutable tags instead of commit SHAs in sandbox-features workflow

Workflow actions use mutable version tags instead of immutable commit SHAs.

Pin all actions to commit SHA plus version comment.

AI prompt
Check if this security scanner issue is valid. If so, understand the root cause and fix it. If appropriate, update or add tests. Keep the change focused and preserve intended behavior.

<file name=".github/workflows/sandbox-features.yml">
<violation number="1" location=".github/workflows/sandbox-features.yml:48">
<priority>P1</priority>
<title>Actions pinned to mutable tags instead of commit SHAs in sandbox-features workflow</title>
<evidence>actions/checkout@v4, actions/setup-node@v4, actions/upload-artifact@v4, actions/download-artifact@v4, and actions/github-script@v7 are all pinned to mutable tags. A compromised action repository can force-push a tag to inject malicious code into the workflow.</evidence>
<recommendation>Pin every third-party and official action to a full 40-character commit SHA and add a version comment, e.g., uses: actions/checkout@&lt;sha&gt; # v4.1.1.</recommendation>
</violation>
</file>

with:
persist-credentials: false
- uses: actions/setup-node@v4
with:
node-version: 24
- name: Install dependencies
run: npm ci
- name: Clear stale results from checkout
run: rm -rf results/
- name: Run feature matrix benchmark
env:
COMPUTESDK_API_KEY: ${{ github.event_name != 'pull_request' && secrets.COMPUTESDK_API_KEY || '' }}
ARCHIL_API_KEY: ${{ github.event_name != 'pull_request' && secrets.ARCHIL_API_KEY || '' }}
ARCHIL_REGION: ${{ github.event_name != 'pull_request' && secrets.ARCHIL_REGION || '' }}
ARCHIL_DISK_ID: ${{ github.event_name != 'pull_request' && secrets.ARCHIL_DISK_ID || '' }}
BL_API_KEY: ${{ github.event_name != 'pull_request' && secrets.BL_API_KEY || '' }}
BL_WORKSPACE: ${{ github.event_name != 'pull_request' && secrets.BL_WORKSPACE || '' }}
CLOUDFLARE_SANDBOX_URL: ${{ github.event_name != 'pull_request' && secrets.CLOUDFLARE_SANDBOX_URL || '' }}
CLOUDFLARE_SANDBOX_SECRET: ${{ github.event_name != 'pull_request' && secrets.CLOUDFLARE_SANDBOX_SECRET || '' }}
CSB_API_KEY: ${{ github.event_name != 'pull_request' && secrets.CSB_API_KEY || '' }}
DAYTONA_API_KEY: ${{ github.event_name != 'pull_request' && secrets.DAYTONA_API_KEY || '' }}
DECLAW_API_KEY: ${{ github.event_name != 'pull_request' && secrets.DECLAW_API_KEY || '' }}
E2B_API_KEY: ${{ github.event_name != 'pull_request' && secrets.E2B_API_KEY || '' }}
HOPX_API_KEY: ${{ github.event_name != 'pull_request' && secrets.HOPX_API_KEY || '' }}
ISORUN_API_KEY: ${{ github.event_name != 'pull_request' && secrets.ISORUN_API_KEY || '' }}
MODAL_TOKEN_ID: ${{ github.event_name != 'pull_request' && secrets.MODAL_TOKEN_ID || '' }}
MODAL_TOKEN_SECRET: ${{ github.event_name != 'pull_request' && secrets.MODAL_TOKEN_SECRET || '' }}
NORTHFLANK_TOKEN: ${{ github.event_name != 'pull_request' && secrets.NORTHFLANK_TOKEN || '' }}
NORTHFLANK_PROJECT_ID: ${{ github.event_name != 'pull_request' && secrets.NORTHFLANK_PROJECT_ID || '' }}
RUNLOOP_API_KEY: ${{ github.event_name != 'pull_request' && secrets.RUNLOOP_API_KEY || '' }}
TENSORLAKE_API_KEY: ${{ github.event_name != 'pull_request' && secrets.TENSORLAKE_API_KEY || '' }}
UPSTASH_BOX_API_KEY: ${{ github.event_name != 'pull_request' && secrets.UPSTASH_BOX_API_KEY || '' }}
VERCEL_TOKEN: ${{ github.event_name != 'pull_request' && secrets.VERCEL_TOKEN || '' }}
VERCEL_TEAM_ID: ${{ github.event_name != 'pull_request' && secrets.VERCEL_TEAM_ID || '' }}
VERCEL_PROJECT_ID: ${{ github.event_name != 'pull_request' && secrets.VERCEL_PROJECT_ID || '' }}
run: |
npm run bench:features -- --provider ${{ matrix.provider }}
- name: Upload results
if: always()
uses: actions/upload-artifact@v4
with:
name: sandbox-features-${{ matrix.provider }}
path: results/
if-no-files-found: ignore
retention-days: 7

collect:
name: Collect Feature Matrix Results
runs-on: namespace-profile-default
needs: bench
if: always()
permissions:
contents: write
issues: write
pull-requests: write
steps:
- uses: actions/checkout@v4

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Missing persist-credentials on collect job checkout in sandbox-features workflow

Collect job checkout persists credentials unnecessarily.

Add persist-credentials: false to collect job checkout.

AI prompt
Check if this security scanner issue is valid. If so, understand the root cause and fix it. If appropriate, update or add tests. Keep the change focused and preserve intended behavior.

<file name=".github/workflows/sandbox-features.yml">
<violation number="1" location=".github/workflows/sandbox-features.yml:105">
<priority>P2</priority>
<title>Missing persist-credentials on collect job checkout in sandbox-features workflow</title>
<evidence>The collect job uses actions/checkout@v4 with fetch-depth: 0 but does not set persist-credentials: false. This leaves the repository token persisted in the git config for the remainder of the job, expanding the blast radius if any step is compromised.</evidence>
<recommendation>Add persist-credentials: false to the collect job checkout step to match the bench job hardening.</recommendation>
</violation>
</file>

with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: 24
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts/
pattern: sandbox-features-*
- name: Merge results
run: npx tsx src/merge-results.ts --input artifacts --mode sandbox-features
- name: Post results to PR
if: github.event_name == 'pull_request'
continue-on-error: true
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const path = require('path');

const latestPath = path.join('results', 'sandbox-features', 'latest.json');
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
let body = '## Sandbox Feature Matrix Results\n\n';

if (!fs.existsSync(latestPath)) {
body += '> No sandbox feature matrix results were generated.\n\n';
} else {
const data = JSON.parse(fs.readFileSync(latestPath, 'utf-8'));
const results = data.results
.filter(r => !r.skipped)
.sort((a, b) => a.provider.localeCompare(b.provider));

if (results.length === 0) {
body += '> No sandbox feature matrix results were generated.\n\n';
} else {
body += '| Provider | Features | Node | Python | Docker | TCP | UDP | FS IO | CPU Ops/s |\n';
body += '|----------|----------|------|--------|--------|-----|-----|-------|-----------|\n';

for (const r of results) {
const features = r.summary?.featureMatrix || {};
const supported = Object.values(features).filter(f => f.supportRate === 1).length;
const total = Object.keys(features).length;
const probes = r.iterations?.[0]?.probes || {};
const mark = (value) => value === true ? 'yes' : value === false ? 'no' : '--';
const fsIo = probes.fsWrite1MbMs && probes.fsRead1MbMs ? `${probes.fsWrite1MbMs}/${probes.fsRead1MbMs}ms` : '--';
const cpu = probes.cpuSha256OpsPerSecond ? Math.round(probes.cpuSha256OpsPerSecond).toLocaleString() : '--';
body += `| ${r.provider} | ${supported}/${total} | ${mark(probes.hasNode)} | ${mark(probes.hasPython)} | ${mark(probes.dockerUsable)} | ${mark(probes.tcpExample)} | ${mark(probes.udpDns)} | ${fsIo} | ${cpu} |\n`;
}

body += '\n';
}
}

body += `---\n*[View full run](${runUrl}) · Results available as build artifacts*`;

const marker = '## Sandbox Feature Matrix Results';
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});

const existing = comments.find(c => c.body.startsWith(marker));
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body,
});
}
- name: Commit and push
if: github.event_name != 'pull_request'
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add results/sandbox-features/
git diff --cached --quiet && echo "No changes to commit" && exit 0
git commit -m "chore: update sandbox feature matrix results [skip ci]"

branch="${GITHUB_REF#refs/heads/}"
for attempt in 1 2 3 4 5; do
git fetch origin "${branch}"
git rebase "origin/${branch}" || { git rebase --abort; exit 1; }
if git push origin "HEAD:${branch}"; then
echo "Pushed on attempt ${attempt}"
exit 0
fi
echo "Push rejected (attempt ${attempt}); will rebase and retry"
done
echo "Failed to push after multiple attempts" >&2
exit 1
Loading
Loading