Skip to content

Harden policy-human authorization and remove /tmp import bootstrap - #15

Closed
juguelio wants to merge 1 commit into
p2pdevmedia:mainfrom
juguelio:security/harden-policy-human
Closed

Harden policy-human authorization and remove /tmp import bootstrap#15
juguelio wants to merge 1 commit into
p2pdevmedia:mainfrom
juguelio:security/harden-policy-human

Conversation

@juguelio

Copy link
Copy Markdown
Collaborator

Dos hallazgos de una revisión de seguridad del repo, más instrumentación para el camino de error que hizo difícil debuggear el sandbox de World ID.

⚠️ Este PR cambia comportamiento. Ver la nota en el punto 1 antes de mergear — si tu deployment de testnet depende del bypass implícito, necesita una variable nueva.

1. Bypass de sesión de wallet al emitir la autorización · CWE-639

En verify-policy-human-handler.ts, el chequeo de sesión SIWE se saltea cuando el deployment no es producción:

const allowsUnsignedBrowserWallet = expectedWorldIdEnvironment === "staging";
if (!walletSession && process.env.NODE_ENV === "production" && !allowsUnsignedBrowserWallet) { ... }

Con eso, un deployment de staging/testnet confía en body.walletAddress como titular de la póliza. El signal de World ID solo prueba que un humano ligó su prueba a esa dirección, no que la controle — así que un llamante puede obtener una autorización EIP-712 firmada con la clave del protocolo nombrando a un titular arbitrario.

El impacto está acotado (canjearla on-chain igual requiere controlar esa wallet, y quema el nullifier propio), pero deja de estarlo si la clave de firma de staging llegara a ser la de producción.

Ahora la sesión es obligatoria siempre. El sandbox de navegador sigue necesitando funcionar —fuera de World App no hay MiniKit Wallet Auth, así que nunca puede existir una sesión SIWE— así que mantuve la salida de emergencia pero invertida:

  • Nunca se abre implícitamente: ni por NODE_ENV, ni por el entorno de World ID. Esa apertura implícita era el bug.
  • Se abre solo a propósito, con RISKA_ALLOW_UNSIGNED_BROWSER_WALLET=true.
  • Siempre se cierra en producción: en un build productivo la variable se ignora y se loguea un error, así que una variable perdida no puede reabrir el bypass al lado de la clave real.

Nota de migración: si tu deployment de testnet/staging dependía del bypass implícito, ahora tiene que setear RISKA_ALLOW_UNSIGNED_BROWSER_WALLET=true. Lo pongo acá arriba y no enterrado justamente para que no te sorprenda en el deploy.

2. Ruta /tmp inyectada en el import de Python · CWE-426

scripts/build_whitepaper_pdf.py hacía:

sys.path.insert(0, "/tmp/codex_pdf_build")

antes de importar reportlab. /tmp es de escritura pública y esa ruta no es específica del usuario, así que en un host compartido o de CI otro usuario puede dejar ahí un reportlab/__init__.py malicioso y ejecutar código con los permisos de quien corre el build — incluyendo acceso a los secretos del .env cargados en ese proceso. Al estar en el índice 0, tiene precedencia sobre el paquete legítimo.

Eliminado; ahora usa el reportlab instalado normalmente.

3. Diagnóstico del camino de error

En la rama de "World ID proof of human is missing or is not bound to the connected wallet" ahora se loguea el identifier y el protocolo esperados, el signal hash esperado vs. los recibidos, y los identifiers que volvieron.

Ese mensaje hoy mezcla tres fallas distintas —protocolo equivocado, signal que no coincide, e identidad que ya abrió póliza— y fue lo que más caro salió al diagnosticar el sandbox. Si preferís esto en un PR aparte, lo separo sin problema.


Relacionado: #14 (fixes del sandbox del simulador). Son independientes y se pueden mergear en cualquier orden.

Two findings from a security review of the repo, plus diagnostics for the
error path that made the World ID sandbox hard to debug.

1. Wallet-session bypass when issuing a policy-human authorization.
   The SIWE session check was skipped whenever the deployment was not
   production, so a staging/testnet deployment trusted `body.walletAddress`
   as the policy holder. The World ID signal only proves a human bound a
   proof to that address, not that they control it, so a caller could obtain
   a signing-key-backed EIP-712 authorization naming an arbitrary holder.
   Bounded (redeeming on-chain still needs that wallet, and it burns the
   caller's own nullifier), but it stops being bounded if the staging signing
   key is ever the production one. CWE-639.

   The session is now required unconditionally. The browser sandbox still
   needs to work — outside World App, MiniKit Wallet Auth is unavailable so
   no SIWE session can ever exist — so the escape hatch is preserved but
   inverted: it is never opened implicitly (not by NODE_ENV, not by the World
   ID environment), only by setting RISKA_ALLOW_UNSIGNED_BROWSER_WALLET=true
   on purpose, and it is ignored outright on a production build so a stray
   env var cannot re-open the bypass next to the real signing key.

   NOTE: this changes behaviour. A testnet/staging deployment that relied on
   the implicit bypass must now set RISKA_ALLOW_UNSIGNED_BROWSER_WALLET=true,
   which is why it is called out here rather than buried.

2. scripts/build_whitepaper_pdf.py prepended /tmp/codex_pdf_build to
   sys.path before importing reportlab. /tmp is world-writable, so on a
   shared or CI host another user could plant a module there and execute code
   as whoever runs the build, with access to the .env secrets in that
   process. Removed; it now uses the normally installed reportlab. CWE-426.

Also logs, on the "proof of human is missing or is not bound" path, the
expected identifier and protocol version, the expected vs received signal
hashes, and the received identifiers. That single message currently conflates
three distinct failures — wrong protocol version, signal mismatch, and an
identity that already opened a policy — which is what made this expensive to
diagnose.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Someone is attempting to deploy a commit to the p2pdevmedia's projects Team on Vercel.

A member of the Team first needs to authorize it.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2ba57cdb7e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +116 to +118
const allowsUnsignedBrowserWallet =
process.env.RISKA_ALLOW_UNSIGNED_BROWSER_WALLET === "true" &&
process.env.NODE_ENV !== "production";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Honor the testnet unsigned-wallet escape hatch

Because this gates the escape hatch on NODE_ENV, the TEST browser-wallet flow remains unusable in any built staging/testnet deployment where NODE_ENV is production. The browser-wallet path in WalletAuth only calls connectWallet(...) and never creates the HTTP-only riska_wallet_session, so even with RISKA_ALLOW_UNSIGNED_BROWSER_WALLET=true this endpoint returns wallet_session_required instead of allowing the World ID simulator/reviewer-demo path. Please key the bypass off the requested deployment, or another dedicated app environment, rather than the Next build mode.

Useful? React with 👍 / 👎.

@p2pdevmedia

Copy link
Copy Markdown
Owner

Cerrado a pedido. La corrección quedó committeada en fbfa17e; no se mergea a main.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants