diff --git a/bounty-submission-quarantine-guard/demo.js b/bounty-submission-quarantine-guard/demo.js
new file mode 100644
index 00000000..124052cb
--- /dev/null
+++ b/bounty-submission-quarantine-guard/demo.js
@@ -0,0 +1,56 @@
+const fs = require("fs");
+const path = require("path");
+const { submissionBatch } = require("./sample-data");
+const { evaluateBatch } = require("./index");
+
+const reportsDir = path.join(__dirname, "reports");
+fs.mkdirSync(reportsDir, { recursive: true });
+
+const report = evaluateBatch(submissionBatch);
+fs.writeFileSync(path.join(reportsDir, "submission-quarantine-report.json"), `${JSON.stringify(report, null, 2)}\n`);
+
+const markdown = [
+ "# Scientific Bounty Submission Quarantine Report",
+ "",
+ `Batch: ${report.batchId}`,
+ `Generated: ${report.generatedAt}`,
+ "",
+ "## Summary",
+ "",
+ `- Submissions evaluated: ${report.summary.total}`,
+ `- Released to review: ${report.summary["release-to-review"] || 0}`,
+ `- Quarantined: ${report.summary["quarantine-submission"] || 0}`,
+ `- Findings: ${report.summary.findingCount}`,
+ "",
+ "## Decisions",
+ "",
+ ...report.submissions.flatMap((item) => [
+ `### ${item.id}`,
+ "",
+ `- Challenge: ${item.challengeId}`,
+ `- Team: ${item.teamId}`,
+ `- Decision: ${item.decision}`,
+ `- Findings: ${item.findings.length ? item.findings.join("; ") : "none"}`,
+ `- Actions: ${item.actions.join("; ")}`,
+ ""
+ ])
+].join("\n").trim();
+
+fs.writeFileSync(path.join(reportsDir, "submission-quarantine-report.md"), `${markdown}\n`);
+
+const svg = `
+`;
+fs.writeFileSync(path.join(reportsDir, "summary.svg"), svg);
+console.log(JSON.stringify(report.summary, null, 2));
diff --git a/bounty-submission-quarantine-guard/index.js b/bounty-submission-quarantine-guard/index.js
new file mode 100644
index 00000000..4154166a
--- /dev/null
+++ b/bounty-submission-quarantine-guard/index.js
@@ -0,0 +1,38 @@
+function evaluateSubmission(submission) {
+ const findings = [];
+
+ if (!submission.manifestComplete) findings.push("submission package manifest is incomplete");
+ if (submission.artifactHashes.length === 0) findings.push("submission artifacts are missing hashes");
+ if (submission.unsafeFlags.length > 0) findings.push(`unsafe artifact flags: ${submission.unsafeFlags.join(", ")}`);
+ if (submission.sponsorVisible && findings.length > 0) findings.push("sponsor visibility is enabled before quarantine clearance");
+ if (!submission.reviewerPreview) findings.push("reviewer-safe preview is missing");
+ if (submission.ipStatement === "missing") findings.push("IP handoff statement is missing");
+
+ const decision = findings.length === 0 ? "release-to-review" : "quarantine-submission";
+ const actions = decision === "release-to-review"
+ ? ["release package to reviewer queue", "keep sponsor access gated until award decision"]
+ : ["quarantine package", "disable sponsor visibility", "request manifest and safety remediation"];
+
+ return {
+ id: submission.id,
+ challengeId: submission.challengeId,
+ teamId: submission.teamId,
+ decision,
+ findings,
+ actions
+ };
+}
+
+function evaluateBatch(batch) {
+ const submissions = batch.submissions.map(evaluateSubmission);
+ const summary = submissions.reduce((acc, submission) => {
+ acc.total += 1;
+ acc[submission.decision] = (acc[submission.decision] || 0) + 1;
+ acc.findingCount += submission.findings.length;
+ return acc;
+ }, { total: 0, findingCount: 0 });
+
+ return { batchId: batch.id, generatedAt: batch.generatedAt, summary, submissions };
+}
+
+module.exports = { evaluateSubmission, evaluateBatch };
diff --git a/bounty-submission-quarantine-guard/readme.md b/bounty-submission-quarantine-guard/readme.md
new file mode 100644
index 00000000..679059aa
--- /dev/null
+++ b/bounty-submission-quarantine-guard/readme.md
@@ -0,0 +1,33 @@
+# Scientific Bounty Submission Quarantine Guard
+
+This module adds a focused quarantine layer for the Scientific Bounty System. It decides whether solver submissions are safe and complete enough to reach reviewers or sponsors.
+
+It is intentionally separate from challenge intake, scoring and arbitration, payout routing, evidence freeze, anonymous review packets, prequalification, data-use agreements, closeout, reviewer workload, deadline fairness, and package security scanning. This guard handles submission quarantine and sponsor-visibility gating.
+
+## What It Checks
+
+- Package manifest completeness
+- Artifact hash coverage
+- Unsafe artifact flags such as executable binaries or macro-enabled documents
+- Sponsor visibility before clearance
+- Reviewer-safe preview availability
+- IP handoff statement presence
+
+## Demo
+
+Run:
+
+```bash
+node bounty-submission-quarantine-guard/test.js
+node bounty-submission-quarantine-guard/demo.js
+node bounty-submission-quarantine-guard/render-video.js
+```
+
+Generated artifacts:
+
+- `reports/submission-quarantine-report.json`
+- `reports/submission-quarantine-report.md`
+- `reports/summary.svg`
+- `reports/demo.mp4`
+
+All data is synthetic. The module does not execute solver files, call storage providers, expose sponsor data, process payment details, use credentials, or contact SCIBASE production services.
diff --git a/bounty-submission-quarantine-guard/render-video.js b/bounty-submission-quarantine-guard/render-video.js
new file mode 100644
index 00000000..64b46c8f
--- /dev/null
+++ b/bounty-submission-quarantine-guard/render-video.js
@@ -0,0 +1,38 @@
+const fs = require("fs");
+const path = require("path");
+const { spawnSync } = require("child_process");
+
+const reportsDir = path.join(__dirname, "reports");
+fs.mkdirSync(reportsDir, { recursive: true });
+const width = 960;
+const height = 540;
+const ppm = path.join(reportsDir, "demo-frame.ppm");
+const mp4 = path.join(reportsDir, "demo.mp4");
+
+function pixel(x, y) {
+ if (y < 112) return [23, 32, 51];
+ if (x > 46 && x < 914 && y > 126 && y < 188) return [255, 255, 255];
+ if (x > 46 && x < 914 && y > 210 && y < 272) return [255, 255, 255];
+ if (x > 46 && x < 914 && y > 294 && y < 356) return [255, 255, 255];
+ if (x > 46 && x < 914 && y > 378 && y < 440) return [255, 255, 255];
+ if (x > 64 && x < 238 && y > 140 && y < 174) return [4, 120, 87];
+ if (x > 64 && x < 238 && y > 224 && y < 258) return [180, 83, 9];
+ if (x > 64 && x < 238 && y > 308 && y < 342) return [180, 83, 9];
+ if (x > 64 && x < 238 && y > 392 && y < 426) return [4, 120, 87];
+ return [248, 250, 252];
+}
+const header = `P6\n${width} ${height}\n255\n`;
+const body = Buffer.alloc(width * height * 3);
+for (let y = 0; y < height; y += 1) {
+ for (let x = 0; x < width; x += 1) {
+ const offset = (y * width + x) * 3;
+ const [r, g, b] = pixel(x, y);
+ body[offset] = r;
+ body[offset + 1] = g;
+ body[offset + 2] = b;
+ }
+}
+fs.writeFileSync(ppm, Buffer.concat([Buffer.from(header), body]));
+const result = spawnSync("ffmpeg", ["-y", "-loop", "1", "-framerate", "24", "-i", ppm, "-t", "5", "-vf", "format=yuv420p", "-movflags", "+faststart", mp4], { stdio: "inherit" });
+if (result.status !== 0) throw new Error("ffmpeg failed to render demo video");
+console.log(mp4);
diff --git a/bounty-submission-quarantine-guard/reports/demo.mp4 b/bounty-submission-quarantine-guard/reports/demo.mp4
new file mode 100644
index 00000000..638c1cc1
Binary files /dev/null and b/bounty-submission-quarantine-guard/reports/demo.mp4 differ
diff --git a/bounty-submission-quarantine-guard/reports/submission-quarantine-report.json b/bounty-submission-quarantine-guard/reports/submission-quarantine-report.json
new file mode 100644
index 00000000..24904074
--- /dev/null
+++ b/bounty-submission-quarantine-guard/reports/submission-quarantine-report.json
@@ -0,0 +1,67 @@
+{
+ "batchId": "bounty-submission-quarantine-2026-05-29",
+ "generatedAt": "2026-05-29T14:34:00.000Z",
+ "summary": {
+ "total": 4,
+ "findingCount": 7,
+ "release-to-review": 2,
+ "quarantine-submission": 2
+ },
+ "submissions": [
+ {
+ "id": "sub-1",
+ "challengeId": "challenge-bio-17",
+ "teamId": "team-alpha",
+ "decision": "release-to-review",
+ "findings": [],
+ "actions": [
+ "release package to reviewer queue",
+ "keep sponsor access gated until award decision"
+ ]
+ },
+ {
+ "id": "sub-2",
+ "challengeId": "challenge-ml-04",
+ "teamId": "team-beta",
+ "decision": "quarantine-submission",
+ "findings": [
+ "submission package manifest is incomplete",
+ "unsafe artifact flags: macro-enabled-document",
+ "sponsor visibility is enabled before quarantine clearance",
+ "reviewer-safe preview is missing",
+ "IP handoff statement is missing"
+ ],
+ "actions": [
+ "quarantine package",
+ "disable sponsor visibility",
+ "request manifest and safety remediation"
+ ]
+ },
+ {
+ "id": "sub-3",
+ "challengeId": "challenge-climate-09",
+ "teamId": "team-gamma",
+ "decision": "quarantine-submission",
+ "findings": [
+ "submission artifacts are missing hashes",
+ "unsafe artifact flags: executable-binary"
+ ],
+ "actions": [
+ "quarantine package",
+ "disable sponsor visibility",
+ "request manifest and safety remediation"
+ ]
+ },
+ {
+ "id": "sub-4",
+ "challengeId": "challenge-quantum-02",
+ "teamId": "team-delta",
+ "decision": "release-to-review",
+ "findings": [],
+ "actions": [
+ "release package to reviewer queue",
+ "keep sponsor access gated until award decision"
+ ]
+ }
+ ]
+}
diff --git a/bounty-submission-quarantine-guard/reports/submission-quarantine-report.md b/bounty-submission-quarantine-guard/reports/submission-quarantine-report.md
new file mode 100644
index 00000000..0cf7070c
--- /dev/null
+++ b/bounty-submission-quarantine-guard/reports/submission-quarantine-report.md
@@ -0,0 +1,45 @@
+# Scientific Bounty Submission Quarantine Report
+
+Batch: bounty-submission-quarantine-2026-05-29
+Generated: 2026-05-29T14:34:00.000Z
+
+## Summary
+
+- Submissions evaluated: 4
+- Released to review: 2
+- Quarantined: 2
+- Findings: 7
+
+## Decisions
+
+### sub-1
+
+- Challenge: challenge-bio-17
+- Team: team-alpha
+- Decision: release-to-review
+- Findings: none
+- Actions: release package to reviewer queue; keep sponsor access gated until award decision
+
+### sub-2
+
+- Challenge: challenge-ml-04
+- Team: team-beta
+- Decision: quarantine-submission
+- Findings: submission package manifest is incomplete; unsafe artifact flags: macro-enabled-document; sponsor visibility is enabled before quarantine clearance; reviewer-safe preview is missing; IP handoff statement is missing
+- Actions: quarantine package; disable sponsor visibility; request manifest and safety remediation
+
+### sub-3
+
+- Challenge: challenge-climate-09
+- Team: team-gamma
+- Decision: quarantine-submission
+- Findings: submission artifacts are missing hashes; unsafe artifact flags: executable-binary
+- Actions: quarantine package; disable sponsor visibility; request manifest and safety remediation
+
+### sub-4
+
+- Challenge: challenge-quantum-02
+- Team: team-delta
+- Decision: release-to-review
+- Findings: none
+- Actions: release package to reviewer queue; keep sponsor access gated until award decision
diff --git a/bounty-submission-quarantine-guard/reports/summary.svg b/bounty-submission-quarantine-guard/reports/summary.svg
new file mode 100644
index 00000000..fe661761
--- /dev/null
+++ b/bounty-submission-quarantine-guard/reports/summary.svg
@@ -0,0 +1,21 @@
+
diff --git a/bounty-submission-quarantine-guard/sample-data.js b/bounty-submission-quarantine-guard/sample-data.js
new file mode 100644
index 00000000..c0603258
--- /dev/null
+++ b/bounty-submission-quarantine-guard/sample-data.js
@@ -0,0 +1,52 @@
+const submissionBatch = {
+ id: "bounty-submission-quarantine-2026-05-29",
+ generatedAt: "2026-05-29T14:34:00.000Z",
+ submissions: [
+ {
+ id: "sub-1",
+ challengeId: "challenge-bio-17",
+ teamId: "team-alpha",
+ manifestComplete: true,
+ artifactHashes: ["sha256:aa01", "sha256:bb02"],
+ unsafeFlags: [],
+ sponsorVisible: false,
+ reviewerPreview: true,
+ ipStatement: "solver-retains-until-paid"
+ },
+ {
+ id: "sub-2",
+ challengeId: "challenge-ml-04",
+ teamId: "team-beta",
+ manifestComplete: false,
+ artifactHashes: ["sha256:cc03"],
+ unsafeFlags: ["macro-enabled-document"],
+ sponsorVisible: true,
+ reviewerPreview: false,
+ ipStatement: "missing"
+ },
+ {
+ id: "sub-3",
+ challengeId: "challenge-climate-09",
+ teamId: "team-gamma",
+ manifestComplete: true,
+ artifactHashes: [],
+ unsafeFlags: ["executable-binary"],
+ sponsorVisible: false,
+ reviewerPreview: true,
+ ipStatement: "open-license-after-award"
+ },
+ {
+ id: "sub-4",
+ challengeId: "challenge-quantum-02",
+ teamId: "team-delta",
+ manifestComplete: true,
+ artifactHashes: ["sha256:dd04"],
+ unsafeFlags: [],
+ sponsorVisible: false,
+ reviewerPreview: true,
+ ipStatement: "sponsor-transfer-on-payout"
+ }
+ ]
+};
+
+module.exports = { submissionBatch };
diff --git a/bounty-submission-quarantine-guard/test.js b/bounty-submission-quarantine-guard/test.js
new file mode 100644
index 00000000..78c328b0
--- /dev/null
+++ b/bounty-submission-quarantine-guard/test.js
@@ -0,0 +1,20 @@
+const assert = require("assert");
+const { submissionBatch } = require("./sample-data");
+const { evaluateBatch } = require("./index");
+
+const report = evaluateBatch(submissionBatch);
+
+assert.strictEqual(report.summary.total, 4);
+assert.strictEqual(report.summary["release-to-review"], 2);
+assert.strictEqual(report.summary["quarantine-submission"], 2);
+
+const beta = report.submissions.find((item) => item.id === "sub-2");
+assert(beta.findings.some((finding) => finding.includes("manifest")));
+assert(beta.findings.some((finding) => finding.includes("sponsor visibility")));
+assert(beta.findings.some((finding) => finding.includes("IP handoff")));
+
+const gamma = report.submissions.find((item) => item.id === "sub-3");
+assert(gamma.findings.some((finding) => finding.includes("missing hashes")));
+assert(gamma.findings.some((finding) => finding.includes("executable-binary")));
+
+console.log("bounty-submission-quarantine-guard tests passed");