From 472a82f8a4fba2c6562a6fbecc35b0629e581690 Mon Sep 17 00:00:00 2001 From: Jiahui Wu Date: Fri, 5 Jun 2026 20:45:56 +0800 Subject: [PATCH] Add export-control data transfer guard --- .../package.json | 11 ++ export-control-data-transfer-guard/readme.md | 58 ++++++ .../reports/export-control-transfer-demo.mp4 | Bin 0 -> 28279 bytes .../export-control-transfer-manifest.json | 98 ++++++++++ .../reports/export-control-transfer-report.md | 51 ++++++ .../reports/summary.svg | 25 +++ .../scripts/demo.js | 13 ++ .../src/index.js | 171 ++++++++++++++++++ .../test/index.test.js | 59 ++++++ 9 files changed, 486 insertions(+) create mode 100644 export-control-data-transfer-guard/package.json create mode 100644 export-control-data-transfer-guard/readme.md create mode 100644 export-control-data-transfer-guard/reports/export-control-transfer-demo.mp4 create mode 100644 export-control-data-transfer-guard/reports/export-control-transfer-manifest.json create mode 100644 export-control-data-transfer-guard/reports/export-control-transfer-report.md create mode 100644 export-control-data-transfer-guard/reports/summary.svg create mode 100644 export-control-data-transfer-guard/scripts/demo.js create mode 100644 export-control-data-transfer-guard/src/index.js create mode 100644 export-control-data-transfer-guard/test/index.test.js diff --git a/export-control-data-transfer-guard/package.json b/export-control-data-transfer-guard/package.json new file mode 100644 index 00000000..2a373528 --- /dev/null +++ b/export-control-data-transfer-guard/package.json @@ -0,0 +1,11 @@ +{ + "name": "export-control-data-transfer-guard", + "version": "1.0.0", + "description": "Deterministic export-control and restricted-data transfer readiness guard for scientific bounty challenges.", + "type": "module", + "scripts": { + "demo": "node scripts/demo.js", + "test": "node --test test/*.test.js" + }, + "license": "MIT" +} diff --git a/export-control-data-transfer-guard/readme.md b/export-control-data-transfer-guard/readme.md new file mode 100644 index 00000000..a407742e --- /dev/null +++ b/export-control-data-transfer-guard/readme.md @@ -0,0 +1,58 @@ +# Export-Control Data Transfer Guard + +This module adds an export-control and restricted-data transfer readiness guard for the Scientific Bounty System in issue #18. + +The slice focuses on a gap that appears before a global scientific challenge is published: whether the sponsor can safely open challenge materials, data rooms, participation, and prize payouts across jurisdictions. + +## Why This Matters + +Scientific bounty challenges can involve dual-use research, controlled biological material, genomic data, clinical trial data, geolocation data, or cross-border teams. A platform should not release these materials simply because a challenge has a prize and rubric. It needs a deterministic hold/revise/release check before: + +- public challenge publication, +- private data-room access, +- solver workspace provisioning, +- reviewer access, +- payout release, +- IP handoff after payment. + +## Implemented Scope + +- Detects sensitive challenge topics such as dual-use, controlled biological agent, encryption research, satellite imagery, advanced semiconductor, and autonomous weapons. +- Detects high-risk data types such as human-subject data, genomic data, clinical trial data, geolocation precision, and critical infrastructure data. +- Blocks restricted participant jurisdictions until legal review clears eligibility. +- Requires export classification for sensitive topics. +- Requires a data-use agreement for restricted data. +- Requires auditable cross-border data-room access logs. +- Requires an NDA workflow when a challenge says an NDA is needed. +- Requires payout sanctions or payout-eligibility screening before reward release. +- Generates a deterministic reviewer transfer manifest. + +## Decision Model + +- `hold`: at least one blocker exists, so publication or data-room access should not open. +- `revise`: no blockers, but missing audit controls should be remediated before launch. +- `release`: no blockers or warnings. + +## Local Validation + +```bash +npm test +npm run demo +``` + +## Files + +- `src/index.js`: no-dependency evaluator and manifest builder. +- `test/index.test.js`: node:test coverage for hold, revise, release, and manifest generation. +- `scripts/demo.js`: emits blocked and releasable sample manifests. +- `reports/export-control-transfer-report.md`: reviewer-facing summary. +- `reports/export-control-transfer-manifest.json`: deterministic sample manifest output. + +## Out of Scope + +- Live legal advice. +- Payment processing. +- Wallets, Stripe, tax systems, or sanctions APIs. +- Private challenge data, participant PII, or credentials. +- Automated publication to external portals. + diff --git a/export-control-data-transfer-guard/reports/export-control-transfer-demo.mp4 b/export-control-data-transfer-guard/reports/export-control-transfer-demo.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..32b372c67fddcb4d3f3ceabf1c3480d5190befac GIT binary patch literal 28279 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 BBu)ST literal 0 HcmV?d00001 diff --git a/export-control-data-transfer-guard/reports/export-control-transfer-manifest.json b/export-control-data-transfer-guard/reports/export-control-transfer-manifest.json new file mode 100644 index 00000000..7ff6186d --- /dev/null +++ b/export-control-data-transfer-guard/reports/export-control-transfer-manifest.json @@ -0,0 +1,98 @@ +{ + "blocked": { + "challengeId": "bio-agent-open-prize", + "generatedAt": "2026-06-05T12:40:00.000Z", + "decision": "hold", + "dataRoom": { + "crossBorder": true, + "dataTypes": [ + "genomic-data", + "clinical-trial-data" + ], + "accessLogRequired": true + }, + "eligibility": { + "participantCountries": [ + "US", + "DE", + "IR" + ], + "restrictedJurisdictionPresent": true, + "payoutScreeningRequired": true + }, + "controls": { + "exportClassification": "", + "dataUseAgreement": false, + "dataRoomAccessLog": false, + "ndaWorkflow": false, + "payoutSanctionsScreening": false + }, + "findings": [ + { + "severity": "blocker", + "code": "missing-export-classification", + "message": "Sensitive challenge topics require export classification before release: controlled-biological-agent.", + "remediation": "Hold publication until a responsible reviewer records export-control classification or confirms the challenge is not controlled." + }, + { + "severity": "blocker", + "code": "restricted-participant-jurisdiction", + "message": "Participant country list includes restricted jurisdictions: IR.", + "remediation": "Do not open the challenge to these participants until legal review approves eligibility and payout routing." + }, + { + "severity": "blocker", + "code": "missing-data-use-agreement", + "message": "Restricted or privacy-sensitive data types require a data-use agreement: genomic-data, clinical-trial-data.", + "remediation": "Require a signed data-use agreement before granting access to challenge data or submission workspaces." + }, + { + "severity": "warning", + "code": "missing-transfer-audit-log", + "message": "Cross-border participation or shared data rooms require an auditable access log.", + "remediation": "Enable immutable data-room access logging before releasing controlled datasets." + }, + { + "severity": "warning", + "code": "missing-nda-workflow", + "message": "Challenge requires NDA handling, but no NDA workflow is attached.", + "remediation": "Attach NDA routing, acceptance timestamps, and revocation steps before submissions open." + }, + { + "severity": "warning", + "code": "missing-payout-screening", + "message": "Prize payout is configured without sanctions or payout-eligibility screening.", + "remediation": "Screen recipients before reward release and record the screening decision with the payout manifest." + } + ] + }, + "releasable": { + "challengeId": "open-climate-forecast", + "generatedAt": "2026-06-05T12:40:00.000Z", + "decision": "release", + "dataRoom": { + "crossBorder": false, + "dataTypes": [ + "public-weather-data" + ], + "accessLogRequired": false + }, + "eligibility": { + "participantCountries": [ + "US", + "CA", + "GB" + ], + "restrictedJurisdictionPresent": false, + "payoutScreeningRequired": true + }, + "controls": { + "exportClassification": "public-ear99-confirmed", + "dataUseAgreement": true, + "dataRoomAccessLog": true, + "ndaWorkflow": true, + "payoutSanctionsScreening": true + }, + "findings": [] + } +} diff --git a/export-control-data-transfer-guard/reports/export-control-transfer-report.md b/export-control-data-transfer-guard/reports/export-control-transfer-report.md new file mode 100644 index 00000000..9409e822 --- /dev/null +++ b/export-control-data-transfer-guard/reports/export-control-transfer-report.md @@ -0,0 +1,51 @@ +# Export-Control Data Transfer Guard Report + +Generated: 2026-06-05T12:40:00.000Z + +## Reviewer Summary + +This guard prevents a Scientific Bounty System challenge from opening data-room access, solver workspaces, or reward release before export-control and restricted-data transfer risks are cleared. + +## Blocked Sample + +Challenge: `bio-agent-open-prize` + +Decision: `hold` + +Blockers: + +- Missing export classification for `controlled-biological-agent`. +- Restricted participant jurisdiction present: `IR`. +- Missing data-use agreement for `genomic-data` and `clinical-trial-data`. + +Warnings: + +- Missing cross-border transfer audit log. +- Missing NDA workflow. +- Missing payout sanctions or payout-eligibility screening. + +Required action: + +Resolve all blocker findings before challenge publication or data-room access. Add audit controls before opening private workspaces or payout routing. + +## Releasable Sample + +Challenge: `open-climate-forecast` + +Decision: `release` + +Reason: + +The challenge uses public weather data, has no restricted topic hits, records export classification, includes a data-use agreement, enables access logging, and screens payout eligibility. + +## Issue #18 Fit + +This maps to the Scientific Bounty System requirements by adding a pre-publication trust gate for: + +- challenge posting readiness, +- secure submission workspaces, +- private data-room access, +- arbitration evidence, +- prize payout routing, +- IP handoff after payment. + diff --git a/export-control-data-transfer-guard/reports/summary.svg b/export-control-data-transfer-guard/reports/summary.svg new file mode 100644 index 00000000..46999e37 --- /dev/null +++ b/export-control-data-transfer-guard/reports/summary.svg @@ -0,0 +1,25 @@ + + Export-control transfer guard summary + A three stage hold revise release flow for scientific bounty challenge data transfer readiness. + + Export-Control Data Transfer Guard + Pre-publication risk check for global scientific bounty challenges + + + HOLD + Controlled topics, restricted + jurisdictions, missing DUAs + + + + REVISE + No blockers, but audit or + NDA controls are missing + + + + RELEASE + Classification, DUA, logging, + NDA, and payout checks pass + + diff --git a/export-control-data-transfer-guard/scripts/demo.js b/export-control-data-transfer-guard/scripts/demo.js new file mode 100644 index 00000000..d69f86b0 --- /dev/null +++ b/export-control-data-transfer-guard/scripts/demo.js @@ -0,0 +1,13 @@ +import { + buildTransferManifest, + evaluateExportControlReadiness, + sampleChallenges, +} from '../src/index.js'; + +const blockedEvaluation = evaluateExportControlReadiness(sampleChallenges.blocked); +const releasableEvaluation = evaluateExportControlReadiness(sampleChallenges.releasable); + +console.log(JSON.stringify({ + blocked: buildTransferManifest(sampleChallenges.blocked, blockedEvaluation), + releasable: buildTransferManifest(sampleChallenges.releasable, releasableEvaluation), +}, null, 2)); diff --git a/export-control-data-transfer-guard/src/index.js b/export-control-data-transfer-guard/src/index.js new file mode 100644 index 00000000..d34b418f --- /dev/null +++ b/export-control-data-transfer-guard/src/index.js @@ -0,0 +1,171 @@ +const RESTRICTED_COUNTRIES = new Set(['CU', 'IR', 'KP', 'SY', 'RU', 'BY']); +const SENSITIVE_TOPICS = new Set([ + 'dual-use', + 'controlled-biological-agent', + 'encryption-research', + 'satellite-imagery', + 'advanced-semiconductor', + 'autonomous-weapons', +]); +const HIGH_RISK_DATA_TYPES = new Set([ + 'human-subject-data', + 'genomic-data', + 'clinical-trial-data', + 'geolocation-precision', + 'critical-infrastructure', +]); + +function list(value) { + return Array.isArray(value) ? value : []; +} + +function normalizedSet(values) { + return new Set(list(values).map((value) => String(value).trim()).filter(Boolean)); +} + +function includesAny(values, reference) { + return [...normalizedSet(values)].filter((value) => reference.has(value)); +} + +function addFinding(findings, severity, code, message, remediation) { + findings.push({ severity, code, message, remediation }); +} + +export function evaluateExportControlReadiness(challenge) { + const findings = []; + const topicHits = includesAny(challenge.topics, SENSITIVE_TOPICS); + const dataHits = includesAny(challenge.dataTypes, HIGH_RISK_DATA_TYPES); + const participantCountries = normalizedSet(challenge.participantCountries); + const restrictedParticipants = [...participantCountries].filter((country) => RESTRICTED_COUNTRIES.has(country)); + const hasCrossBorderTransfer = participantCountries.size > 1 || Boolean(challenge.crossBorderDataRoom); + const controls = challenge.controls || {}; + + if (topicHits.length > 0 && !controls.exportClassification) { + addFinding( + findings, + 'blocker', + 'missing-export-classification', + `Sensitive challenge topics require export classification before release: ${topicHits.join(', ')}.`, + 'Hold publication until a responsible reviewer records export-control classification or confirms the challenge is not controlled.' + ); + } + + if (restrictedParticipants.length > 0) { + addFinding( + findings, + 'blocker', + 'restricted-participant-jurisdiction', + `Participant country list includes restricted jurisdictions: ${restrictedParticipants.join(', ')}.`, + 'Do not open the challenge to these participants until legal review approves eligibility and payout routing.' + ); + } + + if (dataHits.length > 0 && !controls.dataUseAgreement) { + addFinding( + findings, + 'blocker', + 'missing-data-use-agreement', + `Restricted or privacy-sensitive data types require a data-use agreement: ${dataHits.join(', ')}.`, + 'Require a signed data-use agreement before granting access to challenge data or submission workspaces.' + ); + } + + if (hasCrossBorderTransfer && !controls.dataRoomAccessLog) { + addFinding( + findings, + 'warning', + 'missing-transfer-audit-log', + 'Cross-border participation or shared data rooms require an auditable access log.', + 'Enable immutable data-room access logging before releasing controlled datasets.' + ); + } + + if (challenge.requiresNda && !controls.ndaWorkflow) { + addFinding( + findings, + 'warning', + 'missing-nda-workflow', + 'Challenge requires NDA handling, but no NDA workflow is attached.', + 'Attach NDA routing, acceptance timestamps, and revocation steps before submissions open.' + ); + } + + if (challenge.prizeAmount > 0 && !controls.payoutSanctionsScreening) { + addFinding( + findings, + 'warning', + 'missing-payout-screening', + 'Prize payout is configured without sanctions or payout-eligibility screening.', + 'Screen recipients before reward release and record the screening decision with the payout manifest.' + ); + } + + const blockers = findings.filter((finding) => finding.severity === 'blocker'); + const warnings = findings.filter((finding) => finding.severity === 'warning'); + + return { + challengeId: challenge.id, + decision: blockers.length > 0 ? 'hold' : warnings.length > 0 ? 'revise' : 'release', + blockers: blockers.length, + warnings: warnings.length, + findings, + releaseConditions: blockers.length > 0 + ? ['Resolve all blocker findings before challenge publication or data-room access.'] + : warnings.map((finding) => finding.remediation), + }; +} + +export function buildTransferManifest(challenge, evaluation) { + return { + challengeId: challenge.id, + generatedAt: '2026-06-05T12:40:00.000Z', + decision: evaluation.decision, + dataRoom: { + crossBorder: Boolean(challenge.crossBorderDataRoom), + dataTypes: list(challenge.dataTypes), + accessLogRequired: evaluation.findings.some((finding) => finding.code === 'missing-transfer-audit-log'), + }, + eligibility: { + participantCountries: list(challenge.participantCountries), + restrictedJurisdictionPresent: evaluation.findings.some((finding) => finding.code === 'restricted-participant-jurisdiction'), + payoutScreeningRequired: Boolean(challenge.prizeAmount), + }, + controls: challenge.controls || {}, + findings: evaluation.findings, + }; +} + +export const sampleChallenges = { + blocked: { + id: 'bio-agent-open-prize', + topics: ['controlled-biological-agent', 'ml-modeling'], + dataTypes: ['genomic-data', 'clinical-trial-data'], + participantCountries: ['US', 'DE', 'IR'], + crossBorderDataRoom: true, + requiresNda: true, + prizeAmount: 250000, + controls: { + exportClassification: '', + dataUseAgreement: false, + dataRoomAccessLog: false, + ndaWorkflow: false, + payoutSanctionsScreening: false, + }, + }, + releasable: { + id: 'open-climate-forecast', + topics: ['climate-modeling'], + dataTypes: ['public-weather-data'], + participantCountries: ['US', 'CA', 'GB'], + crossBorderDataRoom: false, + requiresNda: false, + prizeAmount: 10000, + controls: { + exportClassification: 'public-ear99-confirmed', + dataUseAgreement: true, + dataRoomAccessLog: true, + ndaWorkflow: true, + payoutSanctionsScreening: true, + }, + }, +}; diff --git a/export-control-data-transfer-guard/test/index.test.js b/export-control-data-transfer-guard/test/index.test.js new file mode 100644 index 00000000..9e20a85b --- /dev/null +++ b/export-control-data-transfer-guard/test/index.test.js @@ -0,0 +1,59 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { + buildTransferManifest, + evaluateExportControlReadiness, + sampleChallenges, +} from '../src/index.js'; + +test('holds challenges with controlled topics, sensitive data, and restricted jurisdictions', () => { + const result = evaluateExportControlReadiness(sampleChallenges.blocked); + + assert.equal(result.decision, 'hold'); + assert.equal(result.blockers, 3); + assert.ok(result.findings.some((finding) => finding.code === 'missing-export-classification')); + assert.ok(result.findings.some((finding) => finding.code === 'missing-data-use-agreement')); + assert.ok(result.findings.some((finding) => finding.code === 'restricted-participant-jurisdiction')); +}); + +test('returns revise when blockers are resolved but audit controls are incomplete', () => { + const challenge = { + ...sampleChallenges.blocked, + participantCountries: ['US', 'DE'], + controls: { + exportClassification: 'requires-controlled-data-room', + dataUseAgreement: true, + dataRoomAccessLog: false, + ndaWorkflow: true, + payoutSanctionsScreening: true, + }, + }; + + const result = evaluateExportControlReadiness(challenge); + + assert.equal(result.decision, 'revise'); + assert.equal(result.blockers, 0); + assert.equal(result.warnings, 1); + assert.equal(result.findings[0].code, 'missing-transfer-audit-log'); +}); + +test('releases public challenges when all controls are present', () => { + const result = evaluateExportControlReadiness(sampleChallenges.releasable); + + assert.equal(result.decision, 'release'); + assert.equal(result.blockers, 0); + assert.equal(result.warnings, 0); + assert.deepEqual(result.findings, []); +}); + +test('builds a deterministic transfer manifest for reviewer audit', () => { + const evaluation = evaluateExportControlReadiness(sampleChallenges.blocked); + const manifest = buildTransferManifest(sampleChallenges.blocked, evaluation); + + assert.equal(manifest.generatedAt, '2026-06-05T12:40:00.000Z'); + assert.equal(manifest.challengeId, 'bio-agent-open-prize'); + assert.equal(manifest.decision, 'hold'); + assert.equal(manifest.dataRoom.accessLogRequired, true); + assert.equal(manifest.eligibility.restrictedJurisdictionPresent, true); + assert.equal(manifest.eligibility.payoutScreeningRequired, true); +});