Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions collab-clipboard-import-guard/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
__pycache__/
frames/
38 changes: 38 additions & 0 deletions collab-clipboard-import-guard/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Collaborative Clipboard Import Guard

This module adds a focused issue #12 slice for the real-time collaborative research editor: a trust boundary for pasted or imported content before it becomes shared manuscript state.

It evaluates synthetic import batches for:

- untrusted clipboard or file sources
- missing or unsupported import channel metadata
- malformed import payloads that do not provide a valid block list, contain malformed block entries inside an otherwise valid list, contain malformed table rows, or provide malformed existing-anchor metadata
- missing or unrecognized source trust metadata
- missing, blank, placeholder, or malformed signed source attestations from trusted and partner imports
- hidden instruction-like text that is not visible to collaborators
- spreadsheet formula cells that could execute after import
- notebook output snippets and table cells containing local or private filesystem paths, including lowercase-drive and forward-slash Windows user paths
- source-origin metadata containing local or private filesystem paths
- stale or malformed collaborator review metadata bound to old section versions or unverifiable expiry evidence
- duplicate anchors that would collide inside the import payload or with existing shared-document anchors, with every colliding block regenerated before insertion

The guard emits a deterministic packet with sanitized blocks, reviewer actions, insertion lanes, findings, and a SHA-256 audit digest.

## Usage

```powershell
npm test
npm run demo
npm run video
npm run check
```

The demo writes JSON, Markdown, SVG, and MP4 evidence to `reports/`.

Generated packets include unsafe clipboard, partner-review, trusted-attestation, placeholder-attestation, unsupported-channel, malformed-block-list, malformed-block-entry, malformed-table-row, malformed-existing-anchors, private source-origin, lowercase Windows path, forward-slash Windows path, and clean trusted import examples.

## Scope

This is intentionally separate from previous issue #12 work on broad editor foundations, operation replay, offline conflict resolution, notebook kernel leases, reference merge/formatting, authorship governance, autosave/local-cache privacy, round-trip fidelity, presence, accessibility, evidence binding, embargo release, notification visibility, data availability, LaTeX macro safety, and suggestion provenance.

No external services, credentials, private manuscripts, live users, or payment data are used.
47 changes: 47 additions & 0 deletions collab-clipboard-import-guard/acceptance-notes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Acceptance Notes

## Local Validation

Run from `collab-clipboard-import-guard/`:

```powershell
npm test
npm run demo
npm run video
npm run check
```

Expected evidence:

- `reports/unsafe-packet.json` quarantines an untrusted clipboard payload.
- `reports/partner-review-packet.json` stages a partner import missing a signed source attestation.
- `reports/trusted-attestation-packet.json` stages a trusted import missing a signed source attestation.
- `reports/placeholder-attestation-packet.json` stages a trusted import with placeholder or malformed attestation evidence.
- `reports/malformed-block-list-packet.json` stages a malformed import payload instead of throwing or allowing collaborative insertion.
- `reports/malformed-block-entry-packet.json` stages malformed block entries inside an otherwise valid block list without throwing or creating sanitized shared-manuscript blocks.
- `reports/malformed-table-row-packet.json` stages malformed table rows before collaborative insertion and normalizes them in sanitized reviewer output.
- `reports/malformed-existing-anchors-packet.json` stages malformed existing-anchor metadata before collision checks can trust it.
- `reports/source-origin-packet.json` quarantines and redacts local/private source-origin metadata.
- `reports/lowercase-windows-path-packet.json` quarantines and fully redacts lowercase-drive Windows user paths.
- `reports/forward-slash-windows-path-packet.json` quarantines and fully redacts forward-slash Windows user paths.
- Blank signed source attestation values are treated as missing and stage trusted or partner imports for curator review.
- `reports/clean-packet.json` allows a trusted, attested import.
- Missing or unrecognized source trust metadata stages otherwise clean imports for curator review.
- Unsupported import channels stage otherwise clean, trusted, attested imports for curator review.
- Malformed block-list payloads stage for curator payload review without creating sanitized shared-manuscript blocks.
- Malformed block entries stage for curator payload review without creating sanitized shared-manuscript blocks.
- Malformed table rows stage for curator payload review and normalize to empty rows instead of entering collaborative state.
- Malformed existing-anchor metadata stages for curator anchor review instead of crashing before packet generation or claiming collision safety.
- Duplicate-anchor collisions flag and regenerate every colliding block before shared insertion, including collisions with anchors that already exist in shared manuscript state.
- Table-cell local/private paths are quarantined, redacted, and still formula-escaped when needed.
- Lowercase Windows user paths are fully redacted from sanitized reviewer output after quarantine.
- Forward-slash Windows user paths are fully redacted from sanitized reviewer output after quarantine.
- Source-origin local/private paths are quarantined and redacted before reviewer packets are emitted.
- Malformed review metadata expiry evidence is dropped before imported comments can enter shared state.
- `reports/import-provenance-report.md` summarizes insertion lanes and findings.
- `reports/summary.svg` provides a visual review packet.
- `reports/demo.mp4` is a short H.264 walkthrough generated from synthetic frames.

## Safety Boundaries

All sample records are synthetic. The module does not call network services, external editors, storage systems, reviewer databases, payment systems, or credential stores.
107 changes: 107 additions & 0 deletions collab-clipboard-import-guard/demo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
const fs = require('fs');
const path = require('path');

const { assessImportBatch } = require('./index');
const {
unsafeClipboardImport,
partnerForwardImport,
trustedMissingAttestationImport,
trustedPlaceholderAttestationImport,
unsupportedChannelImport,
malformedBlockListImport,
malformedBlockEntryImport,
malformedTableRowImport,
malformedExistingAnchorsImport,
cleanTrustedImport,
privateSourceOriginImport,
lowercaseWindowsPathImport,
forwardSlashWindowsPathImport
} = require('./sample-data');

const reportsDir = path.join(__dirname, 'reports');
fs.mkdirSync(reportsDir, { recursive: true });

const packets = [
['unsafe-packet.json', assessImportBatch(unsafeClipboardImport)],
['partner-review-packet.json', assessImportBatch(partnerForwardImport)],
['trusted-attestation-packet.json', assessImportBatch(trustedMissingAttestationImport)],
['placeholder-attestation-packet.json', assessImportBatch(trustedPlaceholderAttestationImport)],
['unsupported-channel-packet.json', assessImportBatch(unsupportedChannelImport)],
['malformed-block-list-packet.json', assessImportBatch(malformedBlockListImport)],
['malformed-block-entry-packet.json', assessImportBatch(malformedBlockEntryImport)],
['malformed-table-row-packet.json', assessImportBatch(malformedTableRowImport)],
['malformed-existing-anchors-packet.json', assessImportBatch(malformedExistingAnchorsImport)],
['source-origin-packet.json', assessImportBatch(privateSourceOriginImport)],
['lowercase-windows-path-packet.json', assessImportBatch(lowercaseWindowsPathImport)],
['forward-slash-windows-path-packet.json', assessImportBatch(forwardSlashWindowsPathImport)],
['clean-packet.json', assessImportBatch(cleanTrustedImport)]
];

for (const [fileName, packet] of packets) {
fs.writeFileSync(path.join(reportsDir, fileName), `${JSON.stringify(packet, null, 2)}\n`);
}

fs.writeFileSync(path.join(reportsDir, 'import-provenance-report.md'), renderMarkdown(packets));
fs.writeFileSync(path.join(reportsDir, 'summary.svg'), renderSvg(packets));

for (const [fileName, packet] of packets) {
console.log(`${fileName}: ${packet.status}; findings=${packet.findings.length}; digest=${packet.auditDigest.slice(0, 12)}`);
}

function renderMarkdown(packetRows) {
const lines = [
'# Collaborative Clipboard Import Provenance Report',
'',
'| Packet | Status | Collaborative insert | Reviewer preview | Retention | Findings |',
'| --- | --- | --- | --- | --- | --- |'
];

for (const [fileName, packet] of packetRows) {
lines.push([
fileName,
packet.status,
packet.insertionLanes.collaborativeInsert,
packet.insertionLanes.reviewerPreview,
packet.insertionLanes.auditRetention,
packet.findings.map((finding) => finding.code).join(', ') || 'none'
].join(' | ').replace(/^/, '| ').replace(/$/, ' |'));
}

lines.push('');
lines.push('All packets use synthetic import payloads and deterministic SHA-256 audit digests.');
lines.push('The guard runs before pasted or imported blocks become visible in a shared manuscript session.');
return `${lines.join('\n')}\n`;
}

function renderSvg(packetRows) {
const height = 108 + packetRows.length * 74 + 54;
const rows = packetRows.map(([, packet], index) => {
const y = 108 + index * 74;
const color = packet.status === 'quarantine_import' ? '#b91c1c' : packet.status === 'stage_for_curator_review' ? '#b45309' : '#15803d';
return `
<g transform="translate(48 ${y})">
<rect width="1104" height="52" rx="6" fill="#f8fafc" stroke="#cbd5e1"/>
<circle cx="28" cy="26" r="11" fill="${color}"/>
<text x="58" y="22" font-size="18" font-family="Arial" fill="#111827">${escapeXml(packet.importId)}</text>
<text x="58" y="41" font-size="13" font-family="Arial" fill="#475569">${escapeXml(packet.status)} | findings ${packet.findings.length} | digest ${packet.auditDigest.slice(0, 16)}</text>
</g>`;
}).join('');

return [
`<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="${height}" viewBox="0 0 1200 ${height}">`,
` <rect width="1200" height="${height}" fill="#eef2f7"/>`,
' <text x="48" y="52" font-size="31" font-family="Arial" font-weight="700" fill="#111827">Clipboard Import Provenance Guard</text>',
' <text x="48" y="80" font-size="16" font-family="Arial" fill="#374151">Pasted and imported research-editor blocks are gated before collaborative insertion.</text>',
rows,
'</svg>',
''
].join('\n');
}

function escapeXml(value) {
return String(value)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
}
Loading