From a07097f7ff8931322bc23fa7eb9a4612b838399e Mon Sep 17 00:00:00 2001 From: GHX5T-SOL <200635707+GHX5T-SOL@users.noreply.github.com> Date: Fri, 22 May 2026 07:02:40 +0200 Subject: [PATCH] Add repository sensitive artifact guard --- repository-sensitive-artifact-guard/README.md | 51 ++ repository-sensitive-artifact-guard/demo.js | 136 +++++ repository-sensitive-artifact-guard/index.js | 335 +++++++++++++ .../package.json | 13 + .../reports/demo.mp4 | Bin 0 -> 59273 bytes .../reports/sensitive-artifact-packet.json | 467 ++++++++++++++++++ .../reports/sensitive-artifact-report.md | 27 + .../reports/summary.svg | 20 + .../sample-data.js | 126 +++++ repository-sensitive-artifact-guard/test.js | 129 +++++ 10 files changed, 1304 insertions(+) create mode 100644 repository-sensitive-artifact-guard/README.md create mode 100644 repository-sensitive-artifact-guard/demo.js create mode 100644 repository-sensitive-artifact-guard/index.js create mode 100644 repository-sensitive-artifact-guard/package.json create mode 100644 repository-sensitive-artifact-guard/reports/demo.mp4 create mode 100644 repository-sensitive-artifact-guard/reports/sensitive-artifact-packet.json create mode 100644 repository-sensitive-artifact-guard/reports/sensitive-artifact-report.md create mode 100644 repository-sensitive-artifact-guard/reports/summary.svg create mode 100644 repository-sensitive-artifact-guard/sample-data.js create mode 100644 repository-sensitive-artifact-guard/test.js diff --git a/repository-sensitive-artifact-guard/README.md b/repository-sensitive-artifact-guard/README.md new file mode 100644 index 00000000..f82957b0 --- /dev/null +++ b/repository-sensitive-artifact-guard/README.md @@ -0,0 +1,51 @@ +# Repository Sensitive Artifact Guard + +This module is a dependency-free, synthetic-data-only guard for SCIBASE Project +Repository & Version Control. It evaluates commit, tag, merge request, and +export bundle metadata before sensitive artifacts become durable in scientific +repository history. + +## What It Checks + +- Synthetic credential and private-key indicators. +- PHI-like or raw participant identifier signals. +- Restricted datasets accidentally routed to public export or DOI snapshot + surfaces. +- Notebook output leakage before reproducibility artifacts are tagged. +- Sensitive path names such as `.env`, credentials files, or key material. +- Large datasets, model weights, and binary results that should be routed + through Git LFS. +- Deterministic rewrite, remediation, LFS routing, and rollback packets. + +## Local Commands + +```bash +npm run check +npm test +npm run demo +``` + +The demo writes reviewer artifacts under `reports/`: + +- `sensitive-artifact-packet.json` +- `sensitive-artifact-report.md` +- `summary.svg` +- `demo.mp4` + +## Requirements Map + +| Issue #10 requirement | Coverage in this slice | +| --- | --- | +| Repository structure and components | Evaluates manuscript, data, code, notebook, results, protocols, and metadata paths. | +| File and metadata versioning | Produces commit-level decisions, audit digests, and rollback packets before merge/tag/export. | +| Git LFS support | Flags large datasets, model weights, and binary results that are not LFS pointers. | +| Collaboration and merge requests | Blocks merge/tag/export surfaces until steward remediation is complete. | +| Export bundles and DOI snapshots | Prevents restricted or sensitive synthetic artifacts from entering public release surfaces. | + +## Safety Boundaries + +- Uses only synthetic fixtures in `sample-data.js`. +- Does not scan real repositories, real secrets, patient data, private projects, + credentials, Git providers, or external services. +- Does not include real secret values, participant identifiers, or institutional + data. diff --git a/repository-sensitive-artifact-guard/demo.js b/repository-sensitive-artifact-guard/demo.js new file mode 100644 index 00000000..14453848 --- /dev/null +++ b/repository-sensitive-artifact-guard/demo.js @@ -0,0 +1,136 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const { spawnSync } = require('child_process'); +const sampleBundles = require('./sample-data'); +const { evaluateRepository } = require('./index'); + +const reportDir = path.join(__dirname, 'reports'); +const asOfDate = '2026-05-22'; + +fs.mkdirSync(reportDir, { recursive: true }); + +const portfolio = evaluateRepository(sampleBundles, { asOfDate }); +const jsonPath = path.join(reportDir, 'sensitive-artifact-packet.json'); +const markdownPath = path.join(reportDir, 'sensitive-artifact-report.md'); +const svgPath = path.join(reportDir, 'summary.svg'); +const mp4Path = path.join(reportDir, 'demo.mp4'); + +fs.writeFileSync(jsonPath, `${JSON.stringify(portfolio, null, 2)}\n`); +fs.writeFileSync(markdownPath, renderMarkdown(portfolio)); +fs.writeFileSync(svgPath, renderSvg(portfolio)); +renderVideo(portfolio, mp4Path); + +console.log(`Wrote ${path.relative(process.cwd(), jsonPath)}`); +console.log(`Wrote ${path.relative(process.cwd(), markdownPath)}`); +console.log(`Wrote ${path.relative(process.cwd(), svgPath)}`); +console.log(`Wrote ${path.relative(process.cwd(), mp4Path)}`); + +function renderMarkdown(portfolio) { + return [ + '# Repository Sensitive Artifact Commit Guard Report', + '', + `As of: ${portfolio.asOfDate}`, + `Repository digest: \`${portfolio.auditDigest}\``, + '', + '## Summary', + '', + `- Commit bundles reviewed: ${portfolio.bundleCount}`, + `- Findings: ${portfolio.findingCount}`, + `- Held commits: ${portfolio.heldCommits.join(', ') || 'none'}`, + `- Actions: ${Object.entries(portfolio.byAction).map(([action, count]) => `${action}=${count}`).join(', ')}`, + '', + '## Bundle Decisions', + '', + '| Commit | Branch | Surface | Action | Severity | Findings | Held paths |', + '| --- | --- | --- | --- | --- | ---: | --- |', + ...portfolio.packets.map((packet) => `| ${packet.commitId} | ${packet.branch} | ${packet.targetSurface} | ${packet.action} | ${packet.severity} | ${packet.findingCount} | ${packet.heldPaths.join('
') || 'none'} |`), + '', + '## Guardrails', + '', + '- Uses synthetic commit, file, signal, and export metadata only.', + '- Does not scan real repositories, real secrets, patient data, private projects, or external services.', + '- Blocks release surfaces when synthetic secret, restricted-data, or patient-identifier indicators appear.', + '- Emits deterministic rewrite, LFS routing, remediation, and rollback packets for reviewers.', + '' + ].join('\n'); +} + +function renderSvg(portfolio) { + const actions = Object.entries(portfolio.byAction) + .map(([action, count]) => `${escapeXml(action)} (${count})`) + .join(' / '); + + return ` + + Repository sensitive artifact guard summary + Synthetic commit bundle decisions for sensitive artifact gating. + + + Repository sensitive artifact guard + Commit, tag, and export checks before sensitive artifacts become durable + + ${portfolio.bundleCount} bundles + commit/tag/export + + ${portfolio.heldCommits.length} held + rewrite or review + + ${portfolio.findingCount} findings + deterministic packets + Actions: ${actions} + Digest: ${portfolio.auditDigest.slice(0, 32)}... + +`; +} + +function renderVideo(portfolio, outputPath) { + const font = '/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf'; + const filters = [ + drawText(font, 'SCIBASE #10 Project Repository & Version Control', 64, 72, 37, 'white'), + drawText(font, 'Repository sensitive artifact commit guard', 64, 136, 35, 'white'), + drawText(font, `Reviewed ${portfolio.bundleCount} synthetic commit bundles`, 84, 238, 33, '0xdbeafe'), + drawText(font, `${portfolio.heldCommits.length} commits held before merge tag or export`, 84, 304, 32, '0xfecaca'), + drawText(font, `${portfolio.findingCount} findings with rollback and remediation packets`, 84, 370, 30, '0xdcfce7'), + drawText(font, 'Checks synthetic credentials PHI markers restricted data notebook outputs and LFS gaps', 84, 462, 25, '0xe0f2fe'), + drawText(font, 'No real repository scans patient data secrets credentials or external services', 84, 522, 25, '0xfef3c7'), + drawText(font, `Audit digest ${portfolio.auditDigest.slice(0, 24)}`, 84, 596, 24, '0xcbd5e1') + ].join(','); + + const result = spawnSync('ffmpeg', [ + '-y', + '-f', 'lavfi', + '-i', 'color=c=0x1f2933:s=1280x720:d=4:r=25', + '-vf', filters, + '-c:v', 'libx264', + '-pix_fmt', 'yuv420p', + outputPath + ], { encoding: 'utf8' }); + + if (result.status !== 0) { + const message = [result.stdout, result.stderr].filter(Boolean).join('\n'); + throw new Error(`ffmpeg failed to render demo.mp4:\n${message}`); + } +} + +function drawText(font, text, x, y, size, color) { + return `drawtext=fontfile='${font}':text='${escapeDrawText(text)}':x=${x}:y=${y}:fontsize=${size}:fontcolor=${color}`; +} + +function escapeDrawText(value) { + return String(value) + .replace(/\\/g, '\\\\') + .replace(/:/g, '\\:') + .replace(/'/g, "\\'") + .replace(/,/g, '\\,') + .replace(/&/g, '\\&'); +} + +function escapeXml(value) { + return String(value) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"'); +} diff --git a/repository-sensitive-artifact-guard/index.js b/repository-sensitive-artifact-guard/index.js new file mode 100644 index 00000000..c3e5b011 --- /dev/null +++ b/repository-sensitive-artifact-guard/index.js @@ -0,0 +1,335 @@ +'use strict'; + +const crypto = require('crypto'); + +const DEFAULT_POLICY = Object.freeze({ + asOfDate: '2026-05-22', + largeFileBytes: 25 * 1024 * 1024, + publicExposureVisibilities: ['public', 'preprint_export', 'doi_snapshot'], + criticalSignals: ['credential_marker', 'private_key_marker', 'raw_patient_identifier', 'restricted_raw_dataset'], + highSignals: ['notebook_output_leak', 'consent_form_marker', 'human_subjects_column', 'license_restricted_data'], + lfsRequiredKinds: ['dataset', 'model_weights', 'binary_result'], + releaseSurfaces: ['tag', 'merge_request', 'export_bundle', 'doi_snapshot'] +}); + +function stableStringify(value) { + if (Array.isArray(value)) return `[${value.map(stableStringify).join(',')}]`; + if (value && typeof value === 'object') { + return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`).join(',')}}`; + } + return JSON.stringify(value); +} + +function digest(value) { + return crypto.createHash('sha256').update(stableStringify(value)).digest('hex'); +} + +function normalizeToken(value) { + return String(value || '').trim().toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, ''); +} + +function normalizePath(path) { + return String(path || '').replace(/\\/g, '/').replace(/^\/+/, ''); +} + +function inferComponent(path) { + const normalized = normalizePath(path); + const first = normalized.split('/')[0] || 'root'; + if (['manuscript', 'data', 'code', 'notebooks', 'results', 'protocols'].includes(first)) return first; + if (normalized === 'metadata.json') return 'metadata'; + return first; +} + +function isPublicExposure(artifact, policy) { + const visibility = normalizeToken(artifact.visibility || artifact.exportVisibility || 'private'); + return policy.publicExposureVisibilities.includes(visibility); +} + +function artifactSignals(artifact) { + return new Set((artifact.signals || []).map(normalizeToken).filter(Boolean)); +} + +function addFinding(findings, code, severity, message, remediation) { + findings.push({ code, severity, message, remediation }); +} + +function evaluateArtifact(artifact, options = {}) { + const policy = Object.assign({}, DEFAULT_POLICY, options.policy || {}); + const path = normalizePath(artifact.path); + const component = artifact.component || inferComponent(path); + const kind = normalizeToken(artifact.kind || component); + const signals = artifactSignals(artifact); + const findings = []; + const publicExposure = isPublicExposure(artifact, policy); + const sizeBytes = Number(artifact.sizeBytes || 0); + const lfsPointer = Boolean(artifact.lfsPointer); + + for (const signal of signals) { + if (policy.criticalSignals.includes(signal)) { + addFinding( + findings, + signal, + publicExposure ? 'critical' : 'high', + `${path} contains ${signal.replace(/_/g, ' ')} evidence`, + publicExposure + ? 'remove artifact from public/tag/export surfaces and rewrite the commit before merge' + : 'route artifact to restricted storage and require steward approval before merge' + ); + } else if (policy.highSignals.includes(signal)) { + addFinding( + findings, + signal, + publicExposure ? 'high' : 'medium', + `${path} contains ${signal.replace(/_/g, ' ')} evidence`, + publicExposure + ? 'redact or replace the artifact before creating a public snapshot' + : 'attach restricted-data review evidence before merge' + ); + } + } + + if (/(\.env|\.pem|id_rsa|credentials|secrets?)($|[./_-])/i.test(path)) { + addFinding( + findings, + 'sensitive_path_name', + publicExposure ? 'critical' : 'high', + `${path} matches a sensitive path naming rule`, + 'move the file out of version control and add a deny rule before rewriting the commit' + ); + } + + if (sizeBytes > policy.largeFileBytes && !lfsPointer && policy.lfsRequiredKinds.includes(kind)) { + addFinding( + findings, + 'missing_lfs_pointer', + 'medium', + `${path} is ${sizeBytes} bytes and is not routed through Git LFS`, + 'replace inline blob with an LFS pointer and preserve the content hash in the manifest' + ); + } + + if (publicExposure && normalizeToken(artifact.dataClass) === 'restricted') { + addFinding( + findings, + 'restricted_data_public_export', + 'critical', + `${path} is restricted but is included in a public release surface`, + 'block public export and create a restricted-access manifest entry' + ); + } + + const selected = selectArtifactAction(findings); + const packet = { + path, + component, + kind, + visibility: normalizeToken(artifact.visibility || artifact.exportVisibility || 'private'), + dataClass: normalizeToken(artifact.dataClass || 'unspecified'), + sizeBytes, + lfsPointer, + publicExposure, + action: selected.action, + severity: selected.severity, + reason: selected.reason, + findingCount: findings.length, + findings + }; + + packet.auditDigest = digest(Object.assign({}, packet, { auditDigest: undefined })); + return packet; +} + +function selectArtifactAction(findings) { + if (findings.some((finding) => finding.severity === 'critical')) { + return { + action: 'reject_commit_and_quarantine_export', + severity: 'critical', + reason: 'critical sensitive artifact exposure is present in a release surface' + }; + } + + if (findings.some((finding) => finding.severity === 'high')) { + return { + action: 'hold_tag_for_steward_review', + severity: 'high', + reason: 'sensitive artifact evidence needs steward review before merge or tag' + }; + } + + if (findings.some((finding) => finding.severity === 'medium')) { + return { + action: 'require_lfs_or_redaction_before_merge', + severity: 'medium', + reason: 'repository hygiene remediation is required before merge' + }; + } + + return { + action: 'allow_commit_tag_export', + severity: 'low', + reason: 'artifact satisfies sensitive-data and LFS policy' + }; +} + +function evaluateCommitBundle(bundle, options = {}) { + const policy = Object.assign({}, DEFAULT_POLICY, options.policy || {}, { + asOfDate: options.asOfDate || (options.policy && options.policy.asOfDate) || DEFAULT_POLICY.asOfDate + }); + + const artifacts = (bundle.artifacts || []).map((artifact) => evaluateArtifact(artifact, { policy })); + const selected = selectBundleAction(artifacts); + const rollbackPacket = buildRollbackPacket(bundle, artifacts, selected); + const summary = artifacts.reduce((acc, artifact) => { + acc.byAction[artifact.action] = (acc.byAction[artifact.action] || 0) + 1; + acc.bySeverity[artifact.severity] = (acc.bySeverity[artifact.severity] || 0) + 1; + if (artifact.severity === 'critical' || artifact.severity === 'high') { + acc.heldPaths.push(artifact.path); + } + acc.findingCount += artifact.findingCount; + return acc; + }, { + byAction: {}, + bySeverity: {}, + heldPaths: [], + findingCount: 0 + }); + + const packet = { + asOfDate: policy.asOfDate, + repositoryId: bundle.repositoryId, + branch: bundle.branch, + targetSurface: normalizeToken(bundle.targetSurface || 'merge_request'), + commitId: bundle.commitId, + action: selected.action, + severity: selected.severity, + reason: selected.reason, + artifactCount: artifacts.length, + findingCount: summary.findingCount, + heldPaths: summary.heldPaths, + byAction: summary.byAction, + bySeverity: summary.bySeverity, + artifacts, + rollbackPacket, + policySnapshot: { + largeFileBytes: policy.largeFileBytes, + releaseSurfaces: policy.releaseSurfaces.slice(), + publicExposureVisibilities: policy.publicExposureVisibilities.slice() + } + }; + + packet.auditDigest = digest(Object.assign({}, packet, { auditDigest: undefined })); + return packet; +} + +function selectBundleAction(artifacts) { + if (artifacts.some((artifact) => artifact.severity === 'critical')) { + return { + action: 'reject_bundle_and_require_history_rewrite', + severity: 'critical', + reason: 'commit bundle contains critical sensitive artifact exposure' + }; + } + + if (artifacts.some((artifact) => artifact.severity === 'high')) { + return { + action: 'hold_release_for_steward_review', + severity: 'high', + reason: 'commit bundle contains sensitive artifacts that need steward approval' + }; + } + + if (artifacts.some((artifact) => artifact.severity === 'medium')) { + return { + action: 'require_repository_hygiene_fix', + severity: 'medium', + reason: 'commit bundle needs LFS routing or redaction before merge' + }; + } + + return { + action: 'allow_merge_tag_export', + severity: 'low', + reason: 'commit bundle satisfies sensitive-artifact policy' + }; +} + +function buildRollbackPacket(bundle, artifacts, selected) { + const rejectedArtifacts = artifacts.filter((artifact) => artifact.severity === 'critical' || artifact.severity === 'high'); + const hygieneArtifacts = artifacts.filter((artifact) => artifact.severity === 'medium'); + const remediationActions = []; + + for (const artifact of rejectedArtifacts) { + for (const finding of artifact.findings) { + remediationActions.push({ + path: artifact.path, + code: finding.code, + action: finding.remediation + }); + } + } + + for (const artifact of hygieneArtifacts) { + remediationActions.push({ + path: artifact.path, + code: 'repository_hygiene', + action: artifact.findings.map((finding) => finding.remediation).join('; ') + }); + } + + return { + commitId: bundle.commitId, + branch: bundle.branch, + decision: selected.action, + rewriteRequired: selected.severity === 'critical', + releaseHoldRequired: selected.severity === 'critical' || selected.severity === 'high', + heldPaths: rejectedArtifacts.map((artifact) => artifact.path), + lfsPaths: hygieneArtifacts.filter((artifact) => artifact.findings.some((finding) => finding.code === 'missing_lfs_pointer')).map((artifact) => artifact.path), + remediationActions, + rollbackDigest: digest({ + commitId: bundle.commitId, + rejected: rejectedArtifacts.map((artifact) => artifact.auditDigest), + hygiene: hygieneArtifacts.map((artifact) => artifact.auditDigest) + }) + }; +} + +function evaluateRepository(bundles, options = {}) { + const packets = bundles.map((bundle) => evaluateCommitBundle(bundle, options)); + const summary = packets.reduce((acc, packet) => { + acc.byAction[packet.action] = (acc.byAction[packet.action] || 0) + 1; + acc.bySeverity[packet.severity] = (acc.bySeverity[packet.severity] || 0) + 1; + if (packet.severity === 'critical' || packet.severity === 'high') { + acc.heldCommits.push(packet.commitId); + } + acc.findingCount += packet.findingCount; + return acc; + }, { + byAction: {}, + bySeverity: {}, + heldCommits: [], + findingCount: 0 + }); + + const portfolio = { + asOfDate: options.asOfDate || DEFAULT_POLICY.asOfDate, + bundleCount: packets.length, + findingCount: summary.findingCount, + heldCommits: summary.heldCommits, + byAction: summary.byAction, + bySeverity: summary.bySeverity, + packets + }; + + portfolio.auditDigest = digest(portfolio); + return portfolio; +} + +module.exports = { + DEFAULT_POLICY, + digest, + evaluateArtifact, + evaluateCommitBundle, + evaluateRepository, + inferComponent, + normalizePath +}; diff --git a/repository-sensitive-artifact-guard/package.json b/repository-sensitive-artifact-guard/package.json new file mode 100644 index 00000000..f0d4b0cb --- /dev/null +++ b/repository-sensitive-artifact-guard/package.json @@ -0,0 +1,13 @@ +{ + "name": "repository-sensitive-artifact-guard", + "version": "1.0.0", + "private": true, + "description": "Synthetic sensitive artifact commit guard for SCIBASE project repository version control.", + "main": "index.js", + "scripts": { + "check": "node --check index.js && node --check sample-data.js && node --check test.js && node --check demo.js", + "test": "node test.js", + "demo": "node demo.js" + }, + "license": "MIT" +} diff --git a/repository-sensitive-artifact-guard/reports/demo.mp4 b/repository-sensitive-artifact-guard/reports/demo.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..a5d3c0cedbdc8718a96f499206739daa5727634f GIT binary patch literal 59273 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/raw-participant-table.csv | +| syn-commit-b2 | experiment/model-card | merge_request | require_repository_hygiene_fix | medium | 1 | none | +| syn-commit-c3 | protocol/steward-review | tag | require_repository_hygiene_fix | medium | 1 | none | +| syn-commit-d4 | main | export_bundle | allow_merge_tag_export | low | 0 | none | + +## Guardrails + +- Uses synthetic commit, file, signal, and export metadata only. +- Does not scan real repositories, real secrets, patient data, private projects, or external services. +- Blocks release surfaces when synthetic secret, restricted-data, or patient-identifier indicators appear. +- Emits deterministic rewrite, LFS routing, remediation, and rollback packets for reviewers. diff --git a/repository-sensitive-artifact-guard/reports/summary.svg b/repository-sensitive-artifact-guard/reports/summary.svg new file mode 100644 index 00000000..703f0e5d --- /dev/null +++ b/repository-sensitive-artifact-guard/reports/summary.svg @@ -0,0 +1,20 @@ + + + Repository sensitive artifact guard summary + Synthetic commit bundle decisions for sensitive artifact gating. + + + Repository sensitive artifact guard + Commit, tag, and export checks before sensitive artifacts become durable + + 4 bundles + commit/tag/export + + 1 held + rewrite or review + + 7 findings + deterministic packets + Actions: reject_bundle_and_require_history_rewrite (1) / require_repository_hygiene_fix (2) / allow_merge_tag_export (1) + Digest: e1296212638d85509a535fc84efed4e2... + diff --git a/repository-sensitive-artifact-guard/sample-data.js b/repository-sensitive-artifact-guard/sample-data.js new file mode 100644 index 00000000..d5da11a2 --- /dev/null +++ b/repository-sensitive-artifact-guard/sample-data.js @@ -0,0 +1,126 @@ +'use strict'; + +module.exports = [ + { + repositoryId: 'repo-immunology-preprint', + branch: 'release/preprint-v2', + targetSurface: 'doi_snapshot', + commitId: 'syn-commit-a1', + artifacts: [ + { + path: 'notebooks/immune-response-analysis.ipynb', + kind: 'notebook', + visibility: 'doi_snapshot', + dataClass: 'controlled', + sizeBytes: 420000, + lfsPointer: false, + signals: ['notebook_output_leak', 'credential_marker'] + }, + { + path: 'data/raw-participant-table.csv', + kind: 'dataset', + visibility: 'doi_snapshot', + dataClass: 'restricted', + sizeBytes: 3100000, + lfsPointer: true, + signals: ['raw_patient_identifier', 'human_subjects_column'] + }, + { + path: 'metadata.json', + kind: 'metadata', + visibility: 'doi_snapshot', + dataClass: 'public', + sizeBytes: 9000, + lfsPointer: false, + signals: [] + } + ] + }, + { + repositoryId: 'repo-vision-model', + branch: 'experiment/model-card', + targetSurface: 'merge_request', + commitId: 'syn-commit-b2', + artifacts: [ + { + path: 'results/model-weights.bin', + kind: 'model_weights', + visibility: 'private', + dataClass: 'internal', + sizeBytes: 88000000, + lfsPointer: false, + signals: [] + }, + { + path: 'code/train.py', + kind: 'code', + visibility: 'private', + dataClass: 'internal', + sizeBytes: 18000, + lfsPointer: false, + signals: [] + } + ] + }, + { + repositoryId: 'repo-clinical-protocol', + branch: 'protocol/steward-review', + targetSurface: 'tag', + commitId: 'syn-commit-c3', + artifacts: [ + { + path: 'protocols/consent-appendix.md', + kind: 'protocol', + visibility: 'private', + dataClass: 'restricted', + sizeBytes: 25000, + lfsPointer: false, + signals: ['consent_form_marker'] + }, + { + path: 'data/deidentified-summary.csv', + kind: 'dataset', + visibility: 'private', + dataClass: 'controlled', + sizeBytes: 1400000, + lfsPointer: true, + signals: [] + } + ] + }, + { + repositoryId: 'repo-open-methods', + branch: 'main', + targetSurface: 'export_bundle', + commitId: 'syn-commit-d4', + artifacts: [ + { + path: 'manuscript/paper.md', + kind: 'manuscript', + visibility: 'preprint_export', + dataClass: 'public', + sizeBytes: 35000, + lfsPointer: false, + signals: [] + }, + { + path: 'code/analysis.py', + kind: 'code', + visibility: 'preprint_export', + dataClass: 'public', + sizeBytes: 22000, + lfsPointer: false, + signals: [] + }, + { + path: 'data/aggregate-results.csv', + kind: 'dataset', + visibility: 'preprint_export', + dataClass: 'public', + sizeBytes: 1800000, + lfsPointer: true, + signals: [] + } + ] + } +]; diff --git a/repository-sensitive-artifact-guard/test.js b/repository-sensitive-artifact-guard/test.js new file mode 100644 index 00000000..bc623abe --- /dev/null +++ b/repository-sensitive-artifact-guard/test.js @@ -0,0 +1,129 @@ +'use strict'; + +const assert = require('assert'); +const sampleBundles = require('./sample-data'); +const { + evaluateArtifact, + evaluateCommitBundle, + evaluateRepository, + inferComponent, + normalizePath +} = require('./index'); + +const asOfDate = '2026-05-22'; + +function testPathNormalizationAndComponentInference() { + assert.equal(normalizePath('/data/raw.csv'), 'data/raw.csv'); + assert.equal(inferComponent('notebooks/run.ipynb'), 'notebooks'); + assert.equal(inferComponent('metadata.json'), 'metadata'); +} + +function testPublicCredentialExposureRejectsArtifact() { + const packet = evaluateArtifact({ + path: 'notebooks/example.ipynb', + kind: 'notebook', + visibility: 'doi_snapshot', + dataClass: 'controlled', + sizeBytes: 12000, + lfsPointer: false, + signals: ['credential_marker'] + }); + + assert.equal(packet.action, 'reject_commit_and_quarantine_export'); + assert.equal(packet.severity, 'critical'); + assert.ok(packet.findings.some((finding) => finding.code === 'credential_marker')); +} + +function testSensitivePathNameIsHeld() { + const packet = evaluateArtifact({ + path: 'code/.env', + kind: 'code', + visibility: 'private', + dataClass: 'internal', + sizeBytes: 300, + lfsPointer: false, + signals: [] + }); + + assert.equal(packet.action, 'hold_tag_for_steward_review'); + assert.ok(packet.findings.some((finding) => finding.code === 'sensitive_path_name')); +} + +function testMissingLfsPointerRequiresHygieneFix() { + const packet = evaluateArtifact({ + path: 'results/model-weights.bin', + kind: 'model_weights', + visibility: 'private', + dataClass: 'internal', + sizeBytes: 88000000, + lfsPointer: false, + signals: [] + }); + + assert.equal(packet.action, 'require_lfs_or_redaction_before_merge'); + assert.equal(packet.severity, 'medium'); + assert.ok(packet.findings.some((finding) => finding.code === 'missing_lfs_pointer')); +} + +function testCleanArtifactAllowed() { + const packet = evaluateArtifact({ + path: 'code/analysis.py', + kind: 'code', + visibility: 'preprint_export', + dataClass: 'public', + sizeBytes: 22000, + lfsPointer: false, + signals: [] + }); + + assert.equal(packet.action, 'allow_commit_tag_export'); + assert.equal(packet.findingCount, 0); +} + +function testCriticalBundleBuildsRewritePacket() { + const packet = evaluateCommitBundle(sampleBundles[0], { asOfDate }); + + assert.equal(packet.action, 'reject_bundle_and_require_history_rewrite'); + assert.equal(packet.severity, 'critical'); + assert.equal(packet.rollbackPacket.rewriteRequired, true); + assert.ok(packet.heldPaths.includes('notebooks/immune-response-analysis.ipynb')); + assert.ok(packet.heldPaths.includes('data/raw-participant-table.csv')); + assert.equal(packet.auditDigest.length, 64); +} + +function testPrivateConsentFormHoldsReleaseWithoutRewrite() { + const packet = evaluateCommitBundle(sampleBundles[2], { asOfDate }); + + assert.equal(packet.action, 'require_repository_hygiene_fix'); + assert.equal(packet.severity, 'medium'); + assert.equal(packet.rollbackPacket.rewriteRequired, false); + assert.ok(packet.rollbackPacket.remediationActions.some((action) => action.path === 'protocols/consent-appendix.md')); +} + +function testRepositorySummary() { + const portfolio = evaluateRepository(sampleBundles, { asOfDate }); + + assert.equal(portfolio.bundleCount, 4); + assert.equal(portfolio.byAction.reject_bundle_and_require_history_rewrite, 1); + assert.equal(portfolio.byAction.require_repository_hygiene_fix, 2); + assert.equal(portfolio.byAction.allow_merge_tag_export, 1); + assert.ok(portfolio.heldCommits.includes('syn-commit-a1')); + assert.equal(portfolio.auditDigest.length, 64); +} + +const tests = [ + testPathNormalizationAndComponentInference, + testPublicCredentialExposureRejectsArtifact, + testSensitivePathNameIsHeld, + testMissingLfsPointerRequiresHygieneFix, + testCleanArtifactAllowed, + testCriticalBundleBuildsRewritePacket, + testPrivateConsentFormHoldsReleaseWithoutRewrite, + testRepositorySummary +]; + +for (const test of tests) { + test(); +} + +console.log(`${tests.length} repository sensitive artifact guard tests passed`);