@originmap/no-scan
v2.0.0
Published
Opt out of Origin Map scanning — meta tag, well-known file, multi-framework support, security middleware
Downloads
405
Maintainers
Readme
Qué hace
Origin Map es un toolkit de ingeniería inversa para apps web. Si quieres que tu sitio no sea escaneado, instala este paquete.
Emite dos señales que Origin Map detecta antes de escanear:
| Señal | Dónde | Cómo |
|-------|-------|------|
| <meta name="originmap" content="no-scan"> | HTML <head> | Inyectado client-side (React, Vue, Svelte, Angular, vanilla JS) |
| /.well-known/originmap.jsonc | Ruta del servidor | Servido via middleware Express o archivo estático |
Cualquiera de las dos es suficiente. Ambas juntas dan máxima cobertura.
Además incluye middleware de seguridad: bot guard, rate limiter y shield (todo en uno).
Instalación
npm install @originmap/no-scanyarn add @originmap/no-scanpnpm add @originmap/no-scanbun add @originmap/no-scanInicio rápido
React / Next.js / Remix
import { OriginMapNoScan } from "@originmap/no-scan"
export default function App() {
return (
<>
<OriginMapNoScan domain="tusitio.com" />
{/* tu app */}
</>
)
}Funciona con React 16.8+ (hooks). El prop
domaines opcional — usawindow.location.hostnamepor default.
Vue 3
<script setup>
import { useOriginMapNoScan } from "@originmap/no-scan/vue"
useOriginMapNoScan("tusitio.com")
</script>O como componente:
<template>
<OriginMapNoScanVue domain="tusitio.com" />
</template>
<script setup>
import { OriginMapNoScanVue } from "@originmap/no-scan/vue"
</script>Svelte
<script>
import { originMapNoScan } from "@originmap/no-scan/svelte"
</script>
<div use:originMapNoScan={"tusitio.com"}></div>O en el script:
<script>
import { injectNoScan } from "@originmap/no-scan/svelte"
import { onMount } from "svelte"
onMount(() => injectNoScan("tusitio.com"))
</script>Angular
import { Component } from "@angular/core"
import { OriginMapNoScanService } from "@originmap/no-scan/angular"
@Component({ selector: "app-root", template: "..." })
export class AppComponent {
constructor(private noScan: OriginMapNoScanService) {
noScan.activate("tusitio.com")
}
}Sin inyección de dependencias:
import { activateNoScan } from "@originmap/no-scan/angular"
activateNoScan("tusitio.com")Vanilla JS
<script type="module">
import { injectMetaTag } from "@originmap/no-scan"
injectMetaTag("tusitio.com")
</script>Express / Connect / Fastify
import express from "express"
import { originMapNoScan } from "@originmap/no-scan/middleware"
const app = express()
app.use(originMapNoScan({ domain: "tusitio.com" }))El
domaines opcional — se auto-detecta desdereq.hostname.
Shield (todo en uno)
import express from "express"
import { originMapShield } from "@originmap/no-scan/middleware"
const app = express()
app.use(originMapShield({ domain: "tusitio.com" }))Un solo middleware que aplica: rate limit → bot guard → well-known.
Archivo estático (SSG / CDN)
import { generateWellKnown } from "@originmap/no-scan"
import { writeFileSync, mkdirSync } from "fs"
const content = await generateWellKnown("tusitio.com")
mkdirSync("public/.well-known", { recursive: true })
writeFileSync("public/.well-known/originmap.jsonc", content)Manual (sin paquete)
Meta tag HTML:
<meta name="originmap" content="no-scan">Archivo estático en /.well-known/originmap.jsonc:
{
"originmap": "no-scan",
"domain": "tusitio.com"
}API
Cliente — @originmap/no-scan
| Export | Tipo | Descripción |
|--------|------|-------------|
| OriginMapNoScan | Componente React | Inyecta meta tag via useEffect. Props: { domain?: string } |
| injectMetaTag(domain?) | Promise<void> | Vanilla JS — agrega meta tag al <head> |
| generateWellKnown(domain) | Promise<string> | Genera el contenido JSONC del well-known file |
| generateToken(domain) | Promise<string> | Genera token de verificación HMAC-SHA256 |
Vue — @originmap/no-scan/vue
| Export | Tipo | Descripción |
|--------|------|-------------|
| useOriginMapNoScan(domain?) | Composable | Vue 3 composable — inyecta en onMounted |
| OriginMapNoScanVue | Componente | Componente Vue 3 con prop domain |
| injectMetaTag(domain?) | Promise<void> | Re-export de la función base |
Svelte — @originmap/no-scan/svelte
| Export | Tipo | Descripción |
|--------|------|-------------|
| originMapNoScan(node, domain?) | Action | Svelte action |
| injectNoScan(domain?) | Promise<void> | Alias de injectMetaTag |
| injectMetaTag(domain?) | Promise<void> | Re-export de la función base |
Angular — @originmap/no-scan/angular
| Export | Tipo | Descripción |
|--------|------|-------------|
| OriginMapNoScanService | Servicio | Angular service con activate(domain?) — SSR-safe |
| activateNoScan(domain?) | void | Función standalone sin DI |
| injectMetaTag(domain?) | Promise<void> | Re-export de la función base |
Middleware — @originmap/no-scan/middleware
| Export | Tipo | Descripción |
|--------|------|-------------|
| originMapNoScan(opts?) | Middleware | Sirve /.well-known/originmap.jsonc |
| botGuard(opts?) | Middleware | Bloquea bots/scrapers por User-Agent |
| rateLimit(opts?) | Middleware | Rate limit por IP (50 req/s default) |
| originMapShield(opts?) | Middleware | Todo en uno: rate limit + bot guard + well-known |
Seguridad
Bot Guard
Bloquea user-agents de bots, scrapers y herramientas de automatización. Los bots legítimos de buscadores pasan automáticamente.
import { botGuard } from "@originmap/no-scan/middleware"
app.use(botGuard({
blockEmptyUA: true, // bloquea requests sin User-Agent (default: true)
customPatterns: [/my-bot/i], // patrones adicionales a bloquear
allowList: [/mi-crawler/i], // bots custom a permitir
onBlocked: (req, res) => { // respuesta personalizada (default: 403)
res.statusCode = 403
res.end("Bloqueado")
},
}))Bloqueados: curl, wget, httpie, python-requests, aiohttp, httpx, scrapy, phantomjs, puppeteer, playwright, selenium, nikto, sqlmap, nmap, dirbuster, gobuster, wpscan, burpsuite, bot, crawler, spider, scraper.
Permitidos: googlebot, bingbot, duckduckbot, facebookexternalhit, twitterbot, linkedinbot, whatsapp, telegrambot, discordbot, applebot, gptbot, slackbot, pinterestbot, yandexbot.
Rate Limiter
Rate limit por IP con fixed-window counter. Devuelve 429 Too Many Requests con header Retry-After.
import { rateLimit } from "@originmap/no-scan/middleware"
app.use(rateLimit({
maxRequests: 50, // requests por ventana (default: 50)
windowMs: 1000, // ventana en ms (default: 1s)
cleanupIntervalMs: 60000, // limpieza de entries viejas (default: 60s)
keyGenerator: (req) => getIP(req), // key personalizada
}))Headers en cada respuesta:
X-RateLimit-Limit— límite máximoX-RateLimit-Remaining— requests restantesX-RateLimit-Reset— timestamp de reset (epoch seconds)
Shield
Middleware combinado que aplica todo en orden:
- Rate limit — configurable o desactivable con
false - Bot guard — configurable o desactivable con
false - Well-known — sirve
/.well-known/originmap.jsonc
import { originMapShield } from "@originmap/no-scan/middleware"
// Configuración personalizada
app.use(originMapShield({
domain: "tusitio.com",
rateLimit: { maxRequests: 100, windowMs: 2000 },
botGuard: { blockEmptyUA: false },
}))
// Desactivar una capa
app.use(originMapShield({
domain: "tusitio.com",
rateLimit: false, // sin rate limit
}))Cómo funciona la detección
Cuando un usuario apunta Origin Map a tu sitio, el scanner ejecuta estos checks antes de analizar código:
- Busca
<meta name="originmap" content="no-scan">en el DOM - Hace
GET /.well-known/originmap.jsonca tu dominio (timeout 3s)
Si cualquier check devuelve una señal válida de opt-out, el escaneo se aborta y el usuario ve un aviso de "Sitio Protegido".
El token HMAC-SHA256 incluido en ambas señales verifica que el opt-out fue configurado intencionalmente por el dueño del sitio.
Licencia
MIT — Rurylox LLC
