From 7b868329bd1c1a0b85508b295e5467cbbd22a2e7 Mon Sep 17 00:00:00 2001 From: Telemark Codex Agent Date: Sat, 15 Aug 2026 23:00:31 +0200 Subject: [PATCH] Add bounty AI assistance disclosure guard --- .../README.md | 56 ++++ .../data/sample_submission_packets.json | 224 +++++++++++++ .../package.json | 13 + .../reports/demo.mp4 | Bin 0 -> 49798 bytes .../reports/reviewer-packet.md | 72 ++++ .../reports/summary.json | 198 +++++++++++ .../reports/summary.svg | 14 + .../scripts/demo.js | 77 +++++ .../scripts/render-demo-video.js | 60 ++++ .../src/index.js | 310 ++++++++++++++++++ ...nty-ai-assistance-disclosure-guard.test.js | 173 ++++++++++ 11 files changed, 1197 insertions(+) create mode 100644 bounty-ai-assistance-disclosure-guard/README.md create mode 100644 bounty-ai-assistance-disclosure-guard/data/sample_submission_packets.json create mode 100644 bounty-ai-assistance-disclosure-guard/package.json create mode 100644 bounty-ai-assistance-disclosure-guard/reports/demo.mp4 create mode 100644 bounty-ai-assistance-disclosure-guard/reports/reviewer-packet.md create mode 100644 bounty-ai-assistance-disclosure-guard/reports/summary.json create mode 100644 bounty-ai-assistance-disclosure-guard/reports/summary.svg create mode 100644 bounty-ai-assistance-disclosure-guard/scripts/demo.js create mode 100644 bounty-ai-assistance-disclosure-guard/scripts/render-demo-video.js create mode 100644 bounty-ai-assistance-disclosure-guard/src/index.js create mode 100644 bounty-ai-assistance-disclosure-guard/test/bounty-ai-assistance-disclosure-guard.test.js diff --git a/bounty-ai-assistance-disclosure-guard/README.md b/bounty-ai-assistance-disclosure-guard/README.md new file mode 100644 index 00000000..afe40c54 --- /dev/null +++ b/bounty-ai-assistance-disclosure-guard/README.md @@ -0,0 +1,56 @@ +# Bounty AI Assistance Disclosure Guard + +This module adds a focused solver AI-assistance disclosure and provenance guard for the Scientific Bounty System in issue `#18`. + +Scientific bounty platforms increasingly receive submissions that mix human research, generated code, generated reports, model-assisted analysis, and tool-produced artifacts. Sponsors and reviewers need a deterministic pre-scoring gate that answers a narrow question: is this AI-assisted submission safe and transparent enough to score? + +## Scope + +- Checks whether AI assistance is allowed by the challenge policy. +- Requires explicit AI-use disclosure when the policy requires it. +- Checks approved AI/tool lists and tool license compatibility. +- Flags prohibited data classes sent through AI workflows. +- Holds packets with private sponsor data, prompt leakage, or secret-like text. +- Checks human review attestation for AI-assisted deliverables. +- Checks generated components for disclosure, human review, provenance hashes, and submission-license fit. +- Requires reproducibility commands or environment notes for AI-assisted work. +- Produces JSON, Markdown, SVG, and MP4 reviewer artifacts. +- Uses synthetic data only. +- Uses no external AI APIs, credentials, private submissions, payment systems, or network calls. + +## Requirement Map + +| Issue #18 capability | Implementation | +| --- | --- | +| Submission engine | Validates solver submission packets before sponsor/reviewer scoring. | +| Secure private workspaces | Detects private data and prompt/secret leakage before reviewer handoff. | +| Arbitration and reviewer checklists | Emits severity-coded findings, decisions, confidence, evidence, and remediation. | +| IP and licensing options | Checks generated component and tool licenses against challenge policy. | +| Audit logs and reproducibility | Requires provenance hashes, human review, and reproducibility commands. | +| Multi-phase challenge support | Decisions can be run before proposal, prototype, final scoring, award release, or sponsor handoff. | + +## Run + +```bash +npm test +npm run demo +npm run video +npm run check +``` + +Demo artifacts are written to `reports/`: + +- `summary.json` +- `reviewer-packet.md` +- `summary.svg` +- `demo.mp4` + +## Decisions + +- `RELEASE`: disclosure, provenance, human review, and reproducibility evidence are scoring-ready. +- `REVIEW`: missing or incomplete provenance can be repaired before scoring. +- `HOLD`: critical policy violations such as undisclosed AI, prohibited private data, prompt leakage, or AI use in a no-AI challenge block scoring and award release. + +## AI Assistance Disclosure + +Implemented with Codex assistance and locally verified with deterministic tests and generated artifacts. diff --git a/bounty-ai-assistance-disclosure-guard/data/sample_submission_packets.json b/bounty-ai-assistance-disclosure-guard/data/sample_submission_packets.json new file mode 100644 index 00000000..b8b166f9 --- /dev/null +++ b/bounty-ai-assistance-disclosure-guard/data/sample_submission_packets.json @@ -0,0 +1,224 @@ +[ + { + "id": "release-transparent-ai", + "title": "Transparent AI-assisted climate forecast submission", + "challenge": { + "title": "Regional climate forecasting bounty" + }, + "challengePolicy": { + "allowsAiAssistance": true, + "requiresDisclosure": true, + "requiresAiToolList": true, + "approvedTools": ["Codex", "OpenModelLab"], + "allowedToolLicenses": ["MIT", "Apache-2.0"], + "allowedSubmissionLicenses": ["MIT", "CC-BY-4.0"], + "prohibitedDataClasses": ["private sponsor data", "participant health data", "export controlled data"] + }, + "aiAssistance": { + "used": true, + "disclosed": true, + "tools": [ + { + "name": "Codex", + "type": "coding agent", + "version": "local", + "license": "MIT", + "purpose": "test generation", + "disclosed": true + } + ], + "dataClassesUsed": ["public benchmark data"], + "privateDataUsed": false, + "promptLeakage": false + }, + "humanReview": { + "completed": true, + "reviewer": "team lead", + "reviewedComponents": ["model.py", "report.md"] + }, + "generatedComponents": [ + { + "path": "model.py", + "type": "code", + "aiGenerated": true, + "disclosed": true, + "humanReviewed": true, + "provenanceHash": "sha256:111111", + "license": "MIT" + } + ], + "reproducibility": { + "commands": "python -m pytest && python run_forecast.py --seed 17", + "environment": "python 3.12", + "artifactHash": "sha256:222222" + }, + "submission": { + "license": "MIT", + "includesPrivateData": false, + "promptLeakage": false, + "notes": "AI assistance disclosed in solver packet." + } + }, + { + "id": "review-missing-component-provenance", + "title": "AI-assisted materials model with incomplete provenance", + "challenge": { + "title": "Materials discovery bounty" + }, + "challengePolicy": { + "allowsAiAssistance": true, + "requiresDisclosure": true, + "requiresAiToolList": true, + "approvedTools": ["Codex"], + "allowedToolLicenses": ["MIT"], + "allowedSubmissionLicenses": ["MIT"], + "prohibitedDataClasses": ["private sponsor data"] + }, + "aiAssistance": { + "used": true, + "disclosed": true, + "tools": [ + { + "name": "Codex", + "type": "coding agent", + "license": "MIT", + "purpose": "feature extraction", + "disclosed": true + } + ], + "dataClassesUsed": ["public crystal dataset"], + "privateDataUsed": false, + "promptLeakage": false + }, + "humanReview": { + "completed": true, + "reviewer": "principal investigator" + }, + "generatedComponents": [ + { + "path": "features.py", + "type": "code", + "aiGenerated": true, + "disclosed": true, + "humanReviewed": false, + "license": "MIT" + } + ], + "reproducibility": { + "commands": "python -m pytest", + "environment": "python 3.12" + }, + "submission": { + "license": "MIT", + "includesPrivateData": false, + "promptLeakage": false, + "notes": "Needs final review signoff." + } + }, + { + "id": "hold-undisclosed-ai", + "title": "Undisclosed AI-generated solver report", + "challenge": { + "title": "Single-cell biomarker bounty" + }, + "challengePolicy": { + "allowsAiAssistance": true, + "requiresDisclosure": true, + "requiresAiToolList": true, + "approvedTools": ["Codex"], + "allowedSubmissionLicenses": ["MIT"], + "prohibitedDataClasses": ["private sponsor data", "participant health data"] + }, + "aiAssistance": { + "used": true, + "disclosed": false, + "tools": [ + { + "name": "Unknown LLM", + "type": "LLM", + "purpose": "drafted report", + "disclosed": false + } + ], + "dataClassesUsed": ["public benchmark data"], + "privateDataUsed": false, + "promptLeakage": false + }, + "humanReview": { + "completed": false + }, + "generatedComponents": [ + { + "path": "report.md", + "type": "whitepaper", + "aiGenerated": true, + "disclosed": false, + "humanReviewed": false, + "license": "MIT" + } + ], + "reproducibility": {}, + "submission": { + "license": "MIT", + "includesPrivateData": false, + "promptLeakage": false, + "notes": "No disclosure included." + } + }, + { + "id": "hold-private-data-leak", + "title": "Private sponsor data sent through AI workflow", + "challenge": { + "title": "Protein design bounty" + }, + "challengePolicy": { + "allowsAiAssistance": true, + "requiresDisclosure": true, + "approvedTools": ["Codex"], + "allowedSubmissionLicenses": ["Apache-2.0"], + "prohibitedDataClasses": ["private sponsor data", "export controlled data"] + }, + "aiAssistance": { + "used": true, + "disclosed": true, + "tools": [ + { + "name": "Codex", + "type": "coding agent", + "license": "MIT", + "purpose": "analysis rewrite", + "disclosed": true + } + ], + "dataClassesUsed": ["private sponsor data"], + "privateDataUsed": true, + "promptLeakage": true, + "promptExcerpt": "analysis with bearer abcdefghijklmnopqrstuvwxyz" + }, + "humanReview": { + "completed": true, + "reviewer": "compliance reviewer" + }, + "generatedComponents": [ + { + "path": "analysis.ipynb", + "type": "notebook", + "aiGenerated": true, + "disclosed": true, + "humanReviewed": true, + "provenanceHash": "sha256:333333", + "license": "MIT" + } + ], + "reproducibility": { + "commands": "python rerun.py", + "environment": "python 3.12" + }, + "submission": { + "license": "MIT", + "includesPrivateData": true, + "promptLeakage": true, + "notes": "Reviewer packet includes prompt material." + } + } +] diff --git a/bounty-ai-assistance-disclosure-guard/package.json b/bounty-ai-assistance-disclosure-guard/package.json new file mode 100644 index 00000000..0f93819b --- /dev/null +++ b/bounty-ai-assistance-disclosure-guard/package.json @@ -0,0 +1,13 @@ +{ + "name": "bounty-ai-assistance-disclosure-guard", + "version": "1.0.0", + "description": "Dependency-free AI assistance disclosure and provenance guard for scientific bounty submissions.", + "main": "src/index.js", + "scripts": { + "test": "node --test", + "demo": "node scripts/demo.js", + "video": "node scripts/render-demo-video.js", + "check": "node --check src/index.js && node --check scripts/demo.js && node --check scripts/render-demo-video.js" + }, + "license": "MIT" +} diff --git a/bounty-ai-assistance-disclosure-guard/reports/demo.mp4 b/bounty-ai-assistance-disclosure-guard/reports/demo.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..3e62b34e119885378c757da7240afc2e8fcbde2a GIT binary patch literal 49798 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 + + Bounty AI Assistance Disclosure + Disclosure, approved tools, human review, provenance hashes, private-data leakage, and license fit. + RELEASE: 1 + + + REVIEW: 1 + + + HOLD: 2 + + + \ No newline at end of file diff --git a/bounty-ai-assistance-disclosure-guard/scripts/demo.js b/bounty-ai-assistance-disclosure-guard/scripts/demo.js new file mode 100644 index 00000000..844ff37a --- /dev/null +++ b/bounty-ai-assistance-disclosure-guard/scripts/demo.js @@ -0,0 +1,77 @@ +"use strict"; + +const fs = require("node:fs"); +const path = require("node:path"); +const { analyzePackets } = require("../src"); + +const root = path.resolve(__dirname, ".."); +const packets = JSON.parse(fs.readFileSync(path.join(root, "data", "sample_submission_packets.json"), "utf8")); +const report = analyzePackets(packets); +const outDir = path.join(root, "reports"); + +fs.mkdirSync(outDir, { recursive: true }); +fs.writeFileSync(path.join(outDir, "summary.json"), `${JSON.stringify(report, null, 2)}\n`); +fs.writeFileSync(path.join(outDir, "reviewer-packet.md"), renderMarkdown(report)); +fs.writeFileSync(path.join(outDir, "summary.svg"), renderSvg(report)); + +console.log(JSON.stringify({ + packetCount: report.packetCount, + decisionCounts: report.decisionCounts, + reports: [ + "reports/summary.json", + "reports/reviewer-packet.md", + "reports/summary.svg" + ] +}, null, 2)); + +function renderMarkdown(report) { + const lines = [ + "# Bounty AI Assistance Disclosure Review", + "", + `Generated: ${report.generatedAt}`, + "", + "## Decision Counts", + "", + `- RELEASE: ${report.decisionCounts.RELEASE}`, + `- REVIEW: ${report.decisionCounts.REVIEW}`, + `- HOLD: ${report.decisionCounts.HOLD}`, + "", + "## Packet Reviews", + "" + ]; + + for (const review of report.reviews) { + lines.push(`### ${review.title}`); + lines.push(`Challenge: ${review.challenge}`); + lines.push(`Decision: ${review.decision}`); + lines.push(`Confidence: ${review.confidence}`); + lines.push(`AI used: ${review.aiUsed}`); + lines.push(`Generated components: ${review.componentCount}`); + lines.push(`Reviewer note: ${review.reviewerNote}`); + for (const item of review.findings) { + lines.push(`- ${item.severity.toUpperCase()} ${item.code}: ${item.message}`); + lines.push(` Remediation: ${item.remediation}`); + } + lines.push(""); + } + + return `${lines.join("\n")}\n`; +} + +function renderSvg(report) { + return ` + + Bounty AI Assistance Disclosure + Disclosure, approved tools, human review, provenance hashes, private-data leakage, and license fit. + ${bar("RELEASE", report.decisionCounts.RELEASE, 48, 148, "#36af8f")} + ${bar("REVIEW", report.decisionCounts.REVIEW, 48, 218, "#d4a235")} + ${bar("HOLD", report.decisionCounts.HOLD, 48, 288, "#d65d73")} +`; +} + +function bar(label, count, x, y, color) { + const width = 96 + count * 116; + return `${label}: ${count} + + `; +} diff --git a/bounty-ai-assistance-disclosure-guard/scripts/render-demo-video.js b/bounty-ai-assistance-disclosure-guard/scripts/render-demo-video.js new file mode 100644 index 00000000..b1bfc69b --- /dev/null +++ b/bounty-ai-assistance-disclosure-guard/scripts/render-demo-video.js @@ -0,0 +1,60 @@ +"use strict"; + +const fs = require("node:fs"); +const path = require("node:path"); +const { spawnSync } = require("node:child_process"); + +const root = path.resolve(__dirname, ".."); +const ffmpeg = path.resolve( + root, + "..", + "..", + "tool_downloads", + "video_tools", + "node_modules", + "ffmpeg-static", + "ffmpeg.exe" +); +const outDir = path.join(root, "reports"); +const out = path.join(outDir, "demo.mp4"); +const font = "C\\:/Windows/Fonts/arial.ttf"; + +fs.mkdirSync(outDir, { recursive: true }); + +const draw = [ + `drawtext=fontfile=${font}:text='Bounty AI Assistance Disclosure':x=64:y=56:fontsize=42:fontcolor=black`, + `drawtext=fontfile=${font}:text='Scientific bounty guard for solver AI-use provenance':x=64:y=120:fontsize=25:fontcolor=0x34413b`, + `drawtext=fontfile=${font}:text='Synthetic submissions analyzed 4':x=64:y=210:fontsize=34:fontcolor=black`, + `drawtext=fontfile=${font}:text='RELEASE 1 REVIEW 1 HOLD 2':x=64:y=268:fontsize=38:fontcolor=0x245fd6`, + `drawtext=fontfile=${font}:text='Checks AI disclosure approved tools human review provenance hashes':x=64:y=358:fontsize=26:fontcolor=0x34413b`, + `drawtext=fontfile=${font}:text='Flags private data prompt leakage license mismatch and missing review':x=64:y=422:fontsize=26:fontcolor=0x34413b`, + `drawtext=fontfile=${font}:text='Synthetic generated slate only no desktop capture or private data':x=64:y=486:fontsize=26:fontcolor=0x34413b` +].join(","); + +if (!fs.existsSync(ffmpeg)) { + throw new Error(`ffmpeg binary not found at ${ffmpeg}`); +} + +const result = spawnSync(ffmpeg, [ + "-y", + "-f", + "lavfi", + "-i", + "color=c=0xf8faf9:s=1280x720:d=8:r=30", + "-vf", + draw, + "-c:v", + "libx264", + "-pix_fmt", + "yuv420p", + "-movflags", + "+faststart", + out +], { encoding: "utf8" }); + +if (result.status !== 0) { + process.stderr.write(result.stderr || result.stdout); + process.exit(result.status || 1); +} + +console.log(JSON.stringify({ out, bytes: fs.statSync(out).size }, null, 2)); diff --git a/bounty-ai-assistance-disclosure-guard/src/index.js b/bounty-ai-assistance-disclosure-guard/src/index.js new file mode 100644 index 00000000..fdaf6438 --- /dev/null +++ b/bounty-ai-assistance-disclosure-guard/src/index.js @@ -0,0 +1,310 @@ +"use strict"; + +const SECRET_PATTERNS = [ + /api[_-]?key\s*=/i, + /secret\s*=/i, + /bearer\s+[A-Za-z0-9._-]{16,}/i, + /-----BEGIN [A-Z ]+PRIVATE KEY-----/i, + /sk-[A-Za-z0-9]{16,}/i +]; + +const AI_TOOL_TERMS = [ + "llm", + "gpt", + "claude", + "gemini", + "copilot", + "codex", + "diffusion", + "agent" +]; + +function finding(code, severity, message, evidence, remediation) { + return { code, severity, message, evidence, remediation }; +} + +function normalizeText(value) { + return String(value || "").trim().toLowerCase(); +} + +function normalizePacket(packet) { + return { + id: packet.id, + title: packet.title || "Untitled scientific bounty submission", + challenge: packet.challenge || {}, + challengePolicy: packet.challengePolicy || {}, + aiAssistance: packet.aiAssistance || {}, + humanReview: packet.humanReview || {}, + generatedComponents: Array.isArray(packet.generatedComponents) ? packet.generatedComponents : [], + reproducibility: packet.reproducibility || {}, + submission: packet.submission || {} + }; +} + +function list(value) { + return Array.isArray(value) ? value : []; +} + +function containsSecretLikeText(value) { + const text = String(value || ""); + return SECRET_PATTERNS.some((pattern) => pattern.test(text)); +} + +function toolLooksAi(tool) { + const haystack = `${tool.name || ""} ${tool.type || ""} ${tool.purpose || ""}`; + return AI_TOOL_TERMS.some((term) => normalizeText(haystack).includes(term)); +} + +function licenseAllowed(license, allowedLicenses) { + if (!license || allowedLicenses.length === 0) return true; + return allowedLicenses.map(normalizeText).includes(normalizeText(license)); +} + +function policyAllowsTool(tool, policy) { + const approvedTools = list(policy.approvedTools).map(normalizeText); + if (approvedTools.length === 0) return true; + return approvedTools.includes(normalizeText(tool.name)); +} + +function usedProhibitedData(aiAssistance, policy) { + const used = list(aiAssistance.dataClassesUsed).map(normalizeText); + const prohibited = list(policy.prohibitedDataClasses).map(normalizeText); + return used.filter((item) => prohibited.includes(item)); +} + +function analyzeTools(packet) { + const findings = []; + const policy = packet.challengePolicy; + const tools = list(packet.aiAssistance.tools); + + for (const tool of tools) { + if (!policyAllowsTool(tool, policy)) { + findings.push(finding( + "UNAPPROVED_AI_TOOL_USED", + "major", + "The submission used an AI/tooling system that is not listed in the challenge-approved tool policy.", + { tool: tool.name || null, approvedTools: policy.approvedTools || [] }, + "Add sponsor approval evidence for the tool or rerun the affected work with an approved toolchain." + )); + } + + if (toolLooksAi(tool) && tool.disclosed !== true && packet.challengePolicy.requiresAiToolList === true) { + findings.push(finding( + "AI_TOOL_NOT_LISTED_IN_DISCLOSURE", + "major", + "An apparent AI tool is present in provenance records but not listed as disclosed.", + { tool: tool.name || null, type: tool.type || null }, + "Update the solver disclosure packet so reviewers can see which AI tools touched the deliverable." + )); + } + + if (tool.license && !licenseAllowed(tool.license, list(policy.allowedToolLicenses))) { + findings.push(finding( + "TOOL_LICENSE_NOT_ALLOWED", + "major", + "A tool license is outside the licenses allowed by the challenge policy.", + { tool: tool.name || null, license: tool.license, allowedToolLicenses: policy.allowedToolLicenses || [] }, + "Replace the tool, document a sponsor exception, or update the challenge policy before scoring." + )); + } + } + + return findings; +} + +function analyzeComponents(packet) { + const findings = []; + const generated = packet.generatedComponents; + + for (const component of generated) { + if (component.aiGenerated === true && component.disclosed !== true) { + findings.push(finding( + "GENERATED_COMPONENT_NOT_DISCLOSED", + "major", + "An AI-generated component is not clearly disclosed in the submission packet.", + { path: component.path || null, type: component.type || null }, + "Mark the generated component and explain how it was produced and reviewed." + )); + } + + if (component.aiGenerated === true && component.humanReviewed !== true) { + findings.push(finding( + "GENERATED_COMPONENT_NOT_HUMAN_REVIEWED", + "major", + "An AI-generated component lacks human review attestation.", + { path: component.path || null }, + "Add human review evidence before the component can be scored or released to a sponsor." + )); + } + + if (component.aiGenerated === true && !component.provenanceHash) { + findings.push(finding( + "GENERATED_COMPONENT_MISSING_PROVENANCE_HASH", + "minor", + "An AI-generated component does not include a provenance or artifact hash.", + { path: component.path || null }, + "Add a stable artifact hash, prompt digest, or build provenance digest." + )); + } + + if (component.license && !licenseAllowed(component.license, list(packet.challengePolicy.allowedSubmissionLicenses))) { + findings.push(finding( + "GENERATED_COMPONENT_LICENSE_MISMATCH", + "major", + "A generated component uses a license outside the challenge submission policy.", + { path: component.path || null, license: component.license }, + "Relicense, remove, or obtain sponsor approval before scoring the submission." + )); + } + } + + return findings; +} + +function analyzePacket(input) { + const packet = normalizePacket(input); + const findings = []; + const policy = packet.challengePolicy; + const ai = packet.aiAssistance; + const submission = packet.submission; + + if (policy.allowsAiAssistance === false && ai.used === true) { + findings.push(finding( + "AI_ASSISTANCE_PROHIBITED", + "critical", + "The challenge policy prohibits AI assistance, but the submission declares or shows AI use.", + { allowsAiAssistance: policy.allowsAiAssistance, aiUsed: ai.used }, + "Hold scoring until the sponsor allows AI assistance or the solver resubmits without AI-generated work." + )); + } + + if (ai.used === true && policy.requiresDisclosure !== false && ai.disclosed !== true) { + findings.push(finding( + "AI_ASSISTANCE_UNDISCLOSED", + "critical", + "The submission used AI assistance without the disclosure required by the challenge policy.", + { aiUsed: ai.used, disclosed: ai.disclosed }, + "Require an explicit AI-use disclosure before scoring, award release, or sponsor handoff." + )); + } + + const prohibitedData = usedProhibitedData(ai, policy); + if (prohibitedData.length > 0) { + findings.push(finding( + "PROHIBITED_DATA_SENT_TO_AI", + "critical", + "AI assistance appears to have used data classes prohibited by the challenge policy.", + { prohibitedData }, + "Hold the packet, notify the sponsor, and require a clean-room rerun or documented exception." + )); + } + + if (ai.privateDataUsed === true || submission.includesPrivateData === true) { + findings.push(finding( + "PRIVATE_DATA_IN_AI_WORKFLOW", + "critical", + "The AI workflow or final packet includes private or sponsor-restricted data.", + { privateDataUsed: ai.privateDataUsed, includesPrivateData: submission.includesPrivateData }, + "Remove private data, regenerate affected artifacts, and provide reviewer-safe provenance evidence." + )); + } + + if (ai.promptLeakage === true || submission.promptLeakage === true || containsSecretLikeText(ai.promptExcerpt) || containsSecretLikeText(submission.notes)) { + findings.push(finding( + "PROMPT_OR_SECRET_LEAKAGE", + "critical", + "The packet appears to expose prompts, secrets, bearer tokens, or private workflow instructions.", + { promptLeakage: ai.promptLeakage || submission.promptLeakage || false }, + "Redact the packet, rotate any exposed credentials if real, and resubmit reviewer-safe evidence only." + )); + } + + if (ai.used === true && packet.humanReview.completed !== true) { + findings.push(finding( + "MISSING_HUMAN_REVIEW_ATTESTATION", + "major", + "AI-assisted work lacks a completed human review attestation.", + { humanReviewCompleted: packet.humanReview.completed || false }, + "Add named reviewer, review date, reviewed components, and residual-risk notes." + )); + } + + if (ai.used === true && !packet.reproducibility.commands) { + findings.push(finding( + "AI_ASSISTED_WORK_NOT_REPRODUCIBLE", + "major", + "The submission lacks reproducibility commands or environment notes for AI-assisted deliverables.", + { commands: packet.reproducibility.commands || null }, + "Add commands, environment, seeds, and artifact hashes sufficient for sponsor-side reproduction." + )); + } + + findings.push(...analyzeTools(packet)); + findings.push(...analyzeComponents(packet)); + + const decision = decisionFromFindings(findings); + return { + id: packet.id, + title: packet.title, + challenge: packet.challenge.title || "scientific bounty", + decision, + confidence: confidenceFromFindings(findings), + aiUsed: ai.used === true, + componentCount: packet.generatedComponents.length, + findingCount: findings.length, + findings, + reviewerNote: reviewerNote(packet, decision, findings) + }; +} + +function severityWeight(severity) { + if (severity === "critical") return 5; + if (severity === "major") return 3; + return 1; +} + +function decisionFromFindings(findings) { + if (findings.some((item) => item.severity === "critical")) return "HOLD"; + if (findings.some((item) => item.severity === "major" || item.severity === "minor")) return "REVIEW"; + return "RELEASE"; +} + +function confidenceFromFindings(findings) { + const penalty = findings.reduce((total, item) => total + severityWeight(item.severity), 0); + return Math.max(0.1, Number((0.96 - penalty * 0.065).toFixed(2))); +} + +function reviewerNote(packet, decision, findings) { + if (decision === "RELEASE") { + return `${packet.challenge.title || "scientific bounty"}: AI-assistance disclosure, provenance, human review, and reproducibility evidence are scoring-ready.`; + } + const first = findings[0]; + return `${packet.challenge.title || "scientific bounty"}: ${decision} before scoring because ${first.message.toLowerCase()}`; +} + +function analyzePackets(packets) { + const reviews = packets.map(analyzePacket); + const counts = reviews.reduce((acc, review) => { + acc[review.decision] = (acc[review.decision] || 0) + 1; + return acc; + }, {}); + + return { + generatedAt: new Date().toISOString(), + packetCount: reviews.length, + decisionCounts: { + RELEASE: counts.RELEASE || 0, + REVIEW: counts.REVIEW || 0, + HOLD: counts.HOLD || 0 + }, + reviews + }; +} + +module.exports = { + SECRET_PATTERNS, + analyzePacket, + analyzePackets, + containsSecretLikeText, + decisionFromFindings +}; diff --git a/bounty-ai-assistance-disclosure-guard/test/bounty-ai-assistance-disclosure-guard.test.js b/bounty-ai-assistance-disclosure-guard/test/bounty-ai-assistance-disclosure-guard.test.js new file mode 100644 index 00000000..0ab3d077 --- /dev/null +++ b/bounty-ai-assistance-disclosure-guard/test/bounty-ai-assistance-disclosure-guard.test.js @@ -0,0 +1,173 @@ +"use strict"; + +const assert = require("node:assert/strict"); +const test = require("node:test"); +const { analyzePacket, analyzePackets, containsSecretLikeText, decisionFromFindings } = require("../src"); + +test("detects secret-like prompt leakage", () => { + assert.equal(containsSecretLikeText("bearer abcdefghijklmnopqrstuvwxyz"), true); + assert.equal(containsSecretLikeText("public benchmark summary"), false); +}); + +test("releases transparent AI-assisted submissions with human review and provenance", () => { + const review = analyzePacket({ + id: "clean", + title: "Clean AI-assisted packet", + challenge: { title: "Climate bounty" }, + challengePolicy: { + allowsAiAssistance: true, + requiresDisclosure: true, + requiresAiToolList: true, + approvedTools: ["Codex"], + allowedToolLicenses: ["MIT"], + allowedSubmissionLicenses: ["MIT"], + prohibitedDataClasses: ["private sponsor data"] + }, + aiAssistance: { + used: true, + disclosed: true, + tools: [{ name: "Codex", type: "coding agent", license: "MIT", disclosed: true }], + dataClassesUsed: ["public data"], + privateDataUsed: false, + promptLeakage: false + }, + humanReview: { completed: true, reviewer: "lead" }, + generatedComponents: [ + { + path: "model.py", + aiGenerated: true, + disclosed: true, + humanReviewed: true, + provenanceHash: "sha256:abc", + license: "MIT" + } + ], + reproducibility: { commands: "python -m pytest" }, + submission: { includesPrivateData: false, promptLeakage: false } + }); + + assert.equal(review.decision, "RELEASE"); + assert.equal(review.findingCount, 0); +}); + +test("holds undisclosed AI assistance", () => { + const review = analyzePacket({ + id: "undisclosed", + title: "Undisclosed AI", + challengePolicy: { + allowsAiAssistance: true, + requiresDisclosure: true, + approvedTools: ["Codex"], + prohibitedDataClasses: [] + }, + aiAssistance: { + used: true, + disclosed: false, + tools: [{ name: "Codex", type: "coding agent", disclosed: false }] + }, + humanReview: { completed: true }, + generatedComponents: [], + reproducibility: { commands: "npm test" }, + submission: {} + }); + + assert.equal(review.decision, "HOLD"); + assert.ok(review.findings.some((item) => item.code === "AI_ASSISTANCE_UNDISCLOSED")); +}); + +test("holds prohibited private data and prompt leakage", () => { + const review = analyzePacket({ + id: "private", + title: "Private data AI workflow", + challengePolicy: { + allowsAiAssistance: true, + requiresDisclosure: true, + prohibitedDataClasses: ["private sponsor data"] + }, + aiAssistance: { + used: true, + disclosed: true, + dataClassesUsed: ["private sponsor data"], + privateDataUsed: true, + promptExcerpt: "api_key=abc1234567890" + }, + humanReview: { completed: true }, + generatedComponents: [], + reproducibility: { commands: "python rerun.py" }, + submission: { includesPrivateData: true } + }); + + assert.equal(review.decision, "HOLD"); + assert.ok(review.findings.some((item) => item.code === "PROHIBITED_DATA_SENT_TO_AI")); + assert.ok(review.findings.some((item) => item.code === "PRIVATE_DATA_IN_AI_WORKFLOW")); + assert.ok(review.findings.some((item) => item.code === "PROMPT_OR_SECRET_LEAKAGE")); +}); + +test("routes missing human review and missing provenance to review", () => { + const review = analyzePacket({ + id: "review", + title: "Needs review", + challengePolicy: { + allowsAiAssistance: true, + requiresDisclosure: true, + approvedTools: ["Codex"], + allowedSubmissionLicenses: ["MIT"] + }, + aiAssistance: { + used: true, + disclosed: true, + tools: [{ name: "Codex", type: "coding agent", disclosed: true }] + }, + humanReview: { completed: false }, + generatedComponents: [ + { + path: "features.py", + aiGenerated: true, + disclosed: true, + humanReviewed: false, + license: "MIT" + } + ], + reproducibility: { commands: "npm test" }, + submission: {} + }); + + assert.equal(review.decision, "REVIEW"); + assert.ok(review.findings.some((item) => item.code === "MISSING_HUMAN_REVIEW_ATTESTATION")); + assert.ok(review.findings.some((item) => item.code === "GENERATED_COMPONENT_NOT_HUMAN_REVIEWED")); + assert.ok(review.findings.some((item) => item.code === "GENERATED_COMPONENT_MISSING_PROVENANCE_HASH")); +}); + +test("summarizes packet decisions deterministically", () => { + const report = analyzePackets([ + { + id: "release", + title: "Release", + challengePolicy: { allowsAiAssistance: true, requiresDisclosure: true }, + aiAssistance: { used: false }, + humanReview: {}, + generatedComponents: [], + reproducibility: {}, + submission: {} + }, + { + id: "hold", + title: "Hold", + challengePolicy: { allowsAiAssistance: false, requiresDisclosure: true }, + aiAssistance: { used: true, disclosed: true }, + generatedComponents: [], + reproducibility: { commands: "npm test" }, + submission: {} + } + ]); + + assert.equal(report.packetCount, 2); + assert.equal(report.decisionCounts.RELEASE, 1); + assert.equal(report.decisionCounts.HOLD, 1); +}); + +test("decision helper maps critical findings to HOLD", () => { + assert.equal(decisionFromFindings([{ severity: "critical" }]), "HOLD"); + assert.equal(decisionFromFindings([{ severity: "major" }]), "REVIEW"); + assert.equal(decisionFromFindings([]), "RELEASE"); +});