PolHunter shell: default-deny gates (OUTBOUND, SIGNUPS, CURTAIN), health, placeholder page

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-19 10:16:11 -05:00
commit 1a6166abc3
5 changed files with 142 additions and 0 deletions
+82
View File
@@ -0,0 +1,82 @@
// PolHunter: gamified visits across the network, paid in POL.
//
// This is the shell the hunt engine will grow inside. What it does today is refuse to do anything
// dangerous by default, because the day before it was built a test area in this same network
// emailed 213 real people. Three gates, all default-deny, all read from the environment:
//
// OUTBOUND=on nothing can send mail (or anything else outward) without it. There is no
// mailer in this app yet; when one arrives it must check outbound() first.
// SIGNUPS=open the sign-in door stays shut until this is set. Hunters will sign in with
// their InstantAdPay account; that bridge does not exist yet either.
// CURTAIN=<secret> while set, every request gets a contentless "Coming soon" page unless the
// browser has visited ?k=<secret> once. Lift it by unsetting the variable.
//
// A missing variable means silence, never delivery.
'use strict';
const http = require('http');
const fs = require('fs');
const path = require('path');
const PORT = Number(process.env.PORT || 3000);
const DATA_DIR = process.env.DATA_DIR || path.join(__dirname, 'data');
const PUBLIC_DIR = path.join(__dirname, 'public');
const CURTAIN = String(process.env.CURTAIN || '').trim();
const outbound = () => process.env.OUTBOUND === 'on';
const signupsOpen = () => process.env.SIGNUPS === 'open';
try { fs.mkdirSync(DATA_DIR, { recursive: true }); } catch (e) {}
const CURTAIN_PAGE = `<!doctype html><html lang="en"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1"><meta name="robots" content="noindex,nofollow">
<title>Coming soon</title><style>
*{margin:0;padding:0;box-sizing:border-box} html,body{height:100%}
body{display:flex;align-items:center;justify-content:center;padding:24px;background:#0d1117;color:#e6edf3;font:16px/1.6 system-ui,-apple-system,"Segoe UI",sans-serif}
.card{max-width:420px;text-align:center} h1{font-size:clamp(28px,7vw,44px);font-weight:700;letter-spacing:-.5px;margin-bottom:14px} p{color:#8b949e}
</style></head><body><div class="card"><h1>Coming soon</h1><p>This site is still being built.</p></div></body></html>`;
function curtained(req, res, u) {
if (!CURTAIN) return false;
if (u.searchParams.get('k') === CURTAIN) {
u.searchParams.delete('k');
res.writeHead(302, {
'Set-Cookie': 'ph.pass=' + encodeURIComponent(CURTAIN) + '; Path=/; Max-Age=2592000; HttpOnly; SameSite=Lax; Secure',
Location: u.pathname + (u.searchParams.toString() ? '?' + u.searchParams : ''),
'Cache-Control': 'no-store'
});
res.end();
return true;
}
const m = /(?:^|;\s*)ph\.pass=([^;]*)/.exec(req.headers.cookie || '');
if (m && decodeURIComponent(m[1]) === CURTAIN) return false;
res.writeHead(503, { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store', 'X-Robots-Tag': 'noindex, nofollow' });
res.end(req.method === 'HEAD' ? '' : CURTAIN_PAGE);
return true;
}
const TYPES = { '.html': 'text/html; charset=utf-8', '.css': 'text/css', '.js': 'application/javascript', '.png': 'image/png', '.jpg': 'image/jpeg', '.svg': 'image/svg+xml', '.ico': 'image/x-icon', '.json': 'application/json' };
function sendFile(res, file) {
fs.readFile(file, (err, buf) => {
if (err) { res.writeHead(404, { 'Content-Type': 'text/plain' }); return res.end('Not found'); }
res.writeHead(200, { 'Content-Type': TYPES[path.extname(file)] || 'application/octet-stream', 'Cache-Control': 'no-store' });
res.end(buf);
});
}
const json = (res, code, body) => { res.writeHead(code, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' }); res.end(JSON.stringify(body)); };
const server = http.createServer((req, res) => {
const u = new URL(req.url, 'http://x');
const p = u.pathname;
if (p === '/health') return json(res, 200, { ok: true, outbound: outbound(), signups: signupsOpen(), curtain: !!CURTAIN });
if (curtained(req, res, u)) return; // nothing below runs for an uninvited visitor
if (p === '/api/config') return json(res, 200, { name: 'PolHunter', signupsOpen: signupsOpen(), outbound: outbound() });
if (p.startsWith('/api/')) return json(res, 404, { error: 'Not built yet.' });
// static: the public dir, index for /
const safe = path.normalize(p).replace(/^(\.\.[/\\])+/, '');
const file = path.join(PUBLIC_DIR, safe === '/' || safe === '\\' ? 'index.html' : safe);
if (!file.startsWith(PUBLIC_DIR)) { res.writeHead(400); return res.end(); }
sendFile(res, file);
});
server.listen(PORT, () => console.log(`PolHunter on :${PORT} — outbound: ${outbound() ? 'ON' : 'OFF'} — sign-ups: ${signupsOpen() ? 'open' : 'CLOSED'} — curtain: ${CURTAIN ? 'up' : 'down'}`));