Commit Graph
4 Commits
Author SHA1 Message Date
cayce 88b2ad2980 hardcap: auth middleware en /admin — token obligatorio, comparación en tiempo constante
ci / lint-test-leaks (push) Canceled after 0s
ci / lint-test-leaks (pull_request) Canceled after 0s
- adminAuth: exige Authorization: Bearer <HARDCAP_ADMIN_TOKEN> (o
  X-Admin-Token). Comparación timingSafeEqual sobre sha256 de ambos
  valores (sin leak de longitud ni timing). Fail-closed: sin token
  configurado, TODAS las rutas admin responden 503.
- 401s ahora se loguean con método, ruta e IP (primera trazabilidad de
  acceso que tiene el admin API desde que existe).
- token generado (32 bytes hex) y almacenado SOLO en
  /srv/hardcap-backend/.env — nunca impreso ni versionado.
- test-admin-auth.mjs: 6 tests (sin token, incorrecto, vacío, correcto,
  header alternativo, misma-longitud-distinto) — todos pasan en la VPS.
- desplegado en src + dist; servicio reiniciado y activo.

Nota de contexto: el admin API carecía de auth desde su creación
(index.ts: \x27TODO: add auth middleware before production\x27) y estuvo
alcanzable sin credenciales en :3001 hasta la contención de hoy.
2026-10-31 17:39:38 +00:00
cayce 87a2829b8a containment: hardcap admin API publish gate + outbox self-sufficient pool
ci / lint-test-leaks (pull_request) Canceled after 0s
- hardcap PATCH /admin/articles/:id → PUBLISHED now stages into
  outbound_drafts (channel hardcap) instead of publishing, when
  HARDCAP_PUBLISH_MODE=draft (default). Fail-closed. Verified E2E:
  PATCH on a QUEUED article returned {staged:true, draft_id:7} and the
  article stayed QUEUED.
- bitcoinhotels.org: audited — frontend only (Next.js), no publishing
  path; nothing to contain.
- outbox.mjs: self-sufficient pool (createRequire from
  /srv/cayce-core/node_modules/pg — hosts may lack pg; found by the
  promo simulation which now works end-to-end).
- runtime/hardcap/admin.ts: versioned copy of the patched route
  (hardcap-backend itself is not a git repo).
- deploy.sh: hardcap env wiring (HARDCAP_PUBLISH_MODE, IDENTITY_PATH,
  CAYCE_PGURL appended server-side).

🔴 UNRELATED CRITICAL FINDING (needs operator decision, not patched
without approval): hardcap admin API has NO authentication and binds
*:3001 — reachable from the public internet (HTTP 200 from outside,
including DELETE /admin/articles/all). Recommend: auth middleware +
bind 127.0.0.1 + firewall, plus rotation of credentials that appeared
in journald (hardcap DB role DSN leaked via a psql error line during
testing — fixed in the same patch, but journald retains it).
2026-10-31 17:11:57 +00:00
cayce 058126b80d containment 2/2: kind 10050 (inbox relays) + drafts summary in supervisor DM + grants suspended, BTCPay priority-1
ci / lint-test-leaks (pull_request) Canceled after 0s
- publish-10050.mjs: publishes Cayce kind 10050 (NIP-65 message-relays/
  inbox) listing the 6 relays where the listener actually reads DMs.
  EXECUTED once with operator authorization: event id
  188ef397824e8f29b5e1bea661ae65b1f8532ee97617747b9fbde57c6e8dfce9,
  accepted 6/6 relays.
- initiative.mjs: the cycle DM to the supervisor now includes a summary
  of outbound_drafts from the last 24h (id, type, first 15 words) so the
  operator reviews what the agent WANTED to publish.
- initiative.mjs: supervisor directive (CAYCE_DIRECTIVE_BTCPAY=1,
  reversible) injected into the cycle prompt: grants/opportunity search
  SUSPENDED, single priority = consolidate the BTCPay Server proposal
  (opportunity #106 / draft #33) ready for HUMAN sending. MANDATE text
  untouched (only the supervisor can change it).
- initiative.mjs: add_opportunity now rejects duplicates (the agent had
  re-registered OpenSats ×10, HRF ×6).
- opportunity #106 status -> priority-1.
- check-relay-silence.mjs: queries the relays for Cayce-signed events
  since a timestamp; allows only kind 10050 and supervisor DMs; exits 1
  on any violation. Verified: 0 events, 0 violations since deployment.
- deploy.sh: sudo for the daemon restart, optional files tolerated.

Gate: make check green (96 tests).
2026-10-31 16:48:54 +00:00
cayce b81f7f3625 containment: every third-party send becomes a draft (outbox), identity file versioned
ci / lint-test-leaks (pull_request) Canceled after 0s
Problem: Cayce publishes straight to relays with no approval gate —
cayce-initiative publish_note, core.mjs digest/opinion/reflection/events,
mention auto-replies, social promo (Facebook/Instagram via sfeed), and
mail — while the operator believed approvals existed (they never did;
ADR-0005 Telegram queue was never implemented).

Mechanism (outbox.mjs, deployed to all runtimes):
- CAYCE_PUBLISH_MODE=draft (default): gateOutbound() stages EVERY outbound
  artifact into outbound_drafts (entity DB) with identity_path +
  identity_hash (sha256 of prompts/cayce-identity.md) before anything
  else happens. Fail-closed: if staging fails, the event is discarded,
  never published.
- CAYCE_PUBLISH_MODE=live: original behavior, unchanged (reversible with
  one line per service .env).
- Chokepoint: the nostr daemon /publish endpoint gates ALL callers
  (initiative publish_note, core.mjs ×4, banner kind 0); the daemon own
  mention-reply path gates too, storing original_event for context;
  startup profile publish gates as well. DM-to-supervisor (/dm) is
  internal and stays live.

Identity (point 8b):
- prompts/cayce-identity.md: the 5 hardcoded voice blocks extracted
  VERBATIM (verified byte-identical by runtime test) with block markers.
- core.mjs / editorials.mjs now load their voice blocks from this file
  (loadIdentityBlock); identity hash recorded on every draft.
- runtime/ holds the versioned copies of the previously-unversioned
  runtime scripts + deploy.sh installer.

No timers enabled or disabled. No production data modified.
2026-10-31 16:44:57 +00:00