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
11 changes: 6 additions & 5 deletions skills/brainstorming/scripts/server.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ function wrapInFrame(content) {

function getNewestScreen() {
const files = fs.readdirSync(CONTENT_DIR)
.filter(f => f.endsWith('.html'))
.filter(f => f.endsWith('.html') && !f.startsWith('.'))
.map(f => {
const fp = path.join(CONTENT_DIR, f);
return { path: fp, mtime: fs.statSync(fp).mtime.getTime() };
Expand Down Expand Up @@ -144,8 +144,9 @@ function handleRequest(req, res) {
res.end(html);
} else if (req.method === 'GET' && req.url.startsWith('/files/')) {
const fileName = req.url.slice(7);
const filePath = path.join(CONTENT_DIR, path.basename(fileName));
if (!fs.existsSync(filePath)) {
const safeName = path.basename(fileName);
const filePath = path.join(CONTENT_DIR, safeName);
if (safeName.startsWith('.') || !fs.existsSync(filePath)) {
res.writeHead(404);
res.end('Not found');
return;
Expand Down Expand Up @@ -267,14 +268,14 @@ function startServer() {
// macOS fs.watch reports 'rename' for both new files and overwrites,
// so we can't rely on eventType alone.
const knownFiles = new Set(
fs.readdirSync(CONTENT_DIR).filter(f => f.endsWith('.html'))
fs.readdirSync(CONTENT_DIR).filter(f => f.endsWith('.html') && !f.startsWith('.'))
);

const server = http.createServer(handleRequest);
server.on('upgrade', handleUpgrade);

const watcher = fs.watch(CONTENT_DIR, (eventType, filename) => {
if (!filename || !filename.endsWith('.html')) return;
if (!filename || !filename.endsWith('.html') || filename.startsWith('.')) return;

if (debounceTimers.has(filename)) clearTimeout(debounceTimers.get(filename));
debounceTimers.set(filename, setTimeout(() => {
Expand Down
14 changes: 14 additions & 0 deletions tests/brainstorm-server/server.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,20 @@ async function runTests() {
assert(!res.body.includes('"not"'), 'Should not serve JSON');
});

await test('ignores macOS resource fork dotfiles (._*.html)', async () => {
// macOS creates ._filename resource forks containing binary metadata
// like "Mac OS X 2ATTR...com.apple.provenance" — these must not be served
fs.writeFileSync(path.join(CONTENT_DIR, '._resource-fork.html'), 'Mac OS X 2\x00ATTR\x00\x00com.apple.provenance');
await sleep(300);

const res = await fetch(`http://localhost:${TEST_PORT}/`);
assert(!res.body.includes('com.apple.provenance'), 'Should not serve resource fork content');
assert(!res.body.includes('ATTR'), 'Should not contain resource fork data');

const filesRes = await fetch(`http://localhost:${TEST_PORT}/files/._resource-fork.html`);
assert.strictEqual(filesRes.status, 404, 'Should return 404 for dotfile via /files/');
});

await test('returns 404 for non-root paths', async () => {
const res = await fetch(`http://localhost:${TEST_PORT}/other`);
assert.strictEqual(res.status, 404);
Expand Down