linebreak-gate
Compuerta de CI con cierre ante fallos para código escrito por IA: bloquea CVEs conocidos y sirve criterios de aceptación aprobados por humanos a Claude Code/Cursor/Codex a través de MCP, de solo lectura para el agente.
Documentación
linebreak-gate — la compuerta de seguridad LineBreak en el límite git/CI
Véalo en acción
Un pull request real, bloqueado de verdad: la compuerta es un check requerido, así que el botón de merge se pone gris hasta que el CVE se corrige o un humano nombrado registra una excepción.

Véalo en vivo — un PR público que puede abrir ahora mismo →
Una grabación real, sin simulaciones: el escaneo bloquea un CVE crítico con fallo cerrado, el pin se corrige, la compuerta se abre.

El ciclo de especificación: un humano nombrado aprueba los criterios, check bloquea hasta que el criterio manual lleva una firma, y entonces todo pasa.

Bloquea merges que llevan vulnerabilidades conocidas. Una herramienta, dos detectores — el escaneo de dependencias es gratis; la revisión con IA es la mejora Pro:
- Escaneo de CVE de dependencias — gratis, sin llave — osv-scanner
en todos los ecosistemas (npm, PyPI, Go, Cargo, Maven, …), con un respaldo
npm auditpara proyectos npm (cobertura solo npm y sin datos de versión instalada — la GitHub Action falla cerrada si osv-scanner no se puede instalar en lugar de degradar a él). - IA SAST — Pro — una revisión de seguridad LLM del código fuente propio (inyección,
autenticación rota, exposición de secretos, SSRF, deserialización insegura, mal uso criptográfico)
con verificación adversarial, habilitada por
LINEBREAK_LICENSE_KEY(alojado, usa créditos) oANTHROPIC_API_KEY(su propia llave, tiene prioridad). Sin una llave, el escaneo de dependencias igual corre y esta pasada se omite con un aviso.
La compuerta bloquea y puede proponer; nunca se auto-limpia por la palabra de un agente. Un humano aprueba la corrección o registra una excepción — con una razón y un aprobador — en un archivo de auditoría confirmado en git.
Este es el mismo núcleo de escaneo que impulsa el resto de la compuerta de seguridad dentro del producto LineBreak (el backend de escritorio importa este paquete), pero es completamente independiente: un equipo que nunca ha tocado nada más de LineBreak puede agregar la compuerta a su repositorio y obtener aplicación real.
Contribución y licencia. Este repositorio es la fuente publicada de
linebreak-gate(Apache-2.0): cada versión llega aquí y a PyPI desde nuestro CI, y cada cambio pasó nuestra propia compuerta primero — escaneo de CVE y criterios aprobados por humanos, la misma disciplina que vendemos. Informes de errores y solicitudes de funciones: abra un issue o discusión aquí; leemos todo. PRs directos a este repositorio no se pueden fusionar (las versiones fluyen a través de nuestro pipeline de revisión), así que comience con un issue y nosotros lo tomamos desde ahí.
Inicio rápido — GitHub Actions
# .github/workflows/security-gate.yml
name: Security gate
on:
pull_request:
permissions:
contents: read
pull-requests: write # for the summary comment
jobs:
gate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: Baktun-Studio/linebreak-gate@v1
with:
# fail-on: high # blocking floor; default: critical
# Optional today; required once license enforcement is enabled.
license-key: ${{ secrets.LINEBREAK_LICENSE_KEY }}
# Enables the AI code review; leave unset for dependency scan only.
anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
La acción corre linebreak-gate scan, siempre corre report, publica un comentario
en el PR (actualizado en su lugar en cada push, nunca spam), sube el informe JSON
- artefactos de auditoría como artefacto de workflow, y falla el check según el código de salida del escaneo.
Conviértalo en un límite real: exija el check
Un trabajo de CI que se puede ignorar es un panel, no una compuerta. En su repositorio:
Settings → Branches → Branch protection rules → su rama predeterminada →
"Require status checks to pass before merging" → agregue el trabajo gate (el
nombre del trabajo que corre esta acción). Desde entonces, un PR que lleva un CVE
crítico no se puede fusionar a través de la interfaz de GitHub.
Inicio rápido — cualquier otro CI (ejemplo GitLab)
El CLI es un paquete Python simple con códigos de salida estrictos — 0 pasa, 1
hallazgos bloqueantes, 2 error de herramienta/configuración (fallo cerrado: un bloqueo del escáner
falla el pipeline, nunca es un pase limpio). Cualquier CI que respete los códigos de salida
obtiene la misma aplicación:
# .gitlab-ci.yml
security-gate:
image: python:3.11
script:
- pip install linebreak-gate
- curl -fsSL -o /usr/local/bin/osv-scanner
"$(curl -fsSL https://api.github.com/repos/google/osv-scanner/releases/latest
| python -c "import json,sys;print(next(a['browser_download_url'] for a in json.load(sys.stdin)['assets'] if a['name'].endswith('linux_amd64')))")"
- chmod +x /usr/local/bin/osv-scanner
- linebreak-gate scan
- linebreak-gate report
Marque el trabajo como requerido (sin allow_failure) y proteja la rama.
Bitbucket Pipelines y Azure DevOps
La misma compuerta en Bitbucket Pipelines y Azure DevOps: linebreak-gate ci
corre escaneo + check, publica el comentario del PR y el estado del build a través de la
API del proveedor, y sale con 0/1/2. Esta sección está en español para los equipos
que la pilotan; el runbook paso a paso está en docs/RUNBOOK_BITBUCKET_AZURE.md
en el monorepo.
La compuerta es la misma en cualquier CI. El comando linebreak-gate ci hace
en un solo paso lo que la Action de GitHub hace en varios: corre el escaneo de
dependencias y la revisión de código con IA (si hay llave), evalúa los
criterios de aceptación aprobados con el alcance correcto (por historia en el
pull request, todo el paquete en la liberación), deja la evidencia en
.linebreak/ci-out/ (report.txt, criteria.txt, report.json,
comment.md y los registros de auditoría) para publicarla como artefacto,
publica un comentario en el pull request (actualizado en cada corrida,
nunca repetido) y un estado de build, y termina con el código 0 (pasa), 1
(bloquea) o 2 (error de herramienta: la compuerta queda cerrada). El
comentario tiene el mismo contenido que el de GitHub.
Sin credenciales de API, el veredicto se imprime igual, el comentario y el estado se omiten con un aviso, y el código de salida sigue bloqueando el pipeline. La compuerta nunca se abre por no poder comentar.
linebreak-gate init detecta el proveedor por el remoto de git y escribe el
archivo que corresponde; --provider bitbucket|azure|all lo elige a mano.
Los dos archivos que genera son exactamente los de abajo.
Bitbucket Pipelines
# bitbucket-pipelines.yml
# Python image with git and curl; the gate pins osv-scanner itself.
# Alternative: the gate's CI image built from packages/gate/Dockerfile.ci
# (osv-scanner preinstalled), pushed to a registry your workspace can pull.
image: python:3.11
definitions:
steps:
- step: &linebreak-gate
name: LineBreak gate
script:
# osv-scanner drives the dependency scan; without it the gate fails closed.
- curl -fsSL -o /usr/local/bin/osv-scanner https://github.com/google/osv-scanner/releases/latest/download/osv-scanner_linux_amd64
- chmod +x /usr/local/bin/osv-scanner
- pip install --quiet "linebreak-gate>=1.13.4,<2"
# Scan + acceptance criteria, PR comment and build status, exit 0/1/2.
# Repository variables (Repository settings > Pipelines > Repository variables):
# LINEBREAK_LICENSE_KEY optional today; required once enforcement is enabled
# ANTHROPIC_API_KEY enables the AI code review (secured)
# BITBUCKET_ACCESS_TOKEN repository access token, scopes pullrequest:write
# and repository:write, for the PR comment and status
- linebreak-gate ci
artifacts:
- .linebreak/ci-out/**
pipelines:
pull-requests:
"**":
- step: *linebreak-gate
branches:
main:
- step: *linebreak-gate
Variables del repositorio (Repository settings > Pipelines > Repository variables, marcadas como secured):
LINEBREAK_LICENSE_KEY: opcional hoy; requerida cuando se active la exigencia de licencia.ANTHROPIC_API_KEY: habilita la revisión de código con IA; sin ella corre solo el escaneo de dependencias, con aviso.BITBUCKET_ACCESS_TOKEN: token de acceso del repositorio (Repository settings > Access tokens) con permisospullrequest:writeyrepository:write. Es lo que permite el comentario y el estado de build. Alternativa:BITBUCKET_USERNAME+BITBUCKET_APP_PASSWORD.
Protección de rama equivalente a "required check" (Repository settings >
Branch restrictions, rama main): el merge check "Check the last commit for
at least 1 successful build and no failed builds". Los merge checks son parte
de Bitbucket Cloud Premium; con el plan Standard el build rojo se ve en el
pull request y en el estado LineBreak gate, pero no impide el merge por sí
solo (se apoya en revisores obligatorios).
Azure DevOps (Azure Repos + Azure Pipelines)
# azure-pipelines.yml
trigger:
branches:
include:
- main
pr:
branches:
include:
- "*"
pool:
vmImage: ubuntu-latest
# Container job alternative (image built from packages/gate/Dockerfile.ci):
# container: <registry>/linebreak-gate-ci:1
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: "3.11"
displayName: Python 3.11
- script: |
set -euo pipefail
mkdir -p "$HOME/bin"
# osv-scanner drives the dependency scan; without it the gate fails closed.
curl -fsSL -o "$HOME/bin/osv-scanner" https://github.com/google/osv-scanner/releases/latest/download/osv-scanner_linux_amd64
chmod +x "$HOME/bin/osv-scanner"
echo "##vso[task.setvariable variable=LINEBREAK_OSV_SCANNER_BIN]$HOME/bin/osv-scanner"
pip install --quiet "linebreak-gate>=1.13.4,<2"
displayName: Install linebreak-gate
# Scan + acceptance criteria, PR comment thread and PR status, exit 0/1/2.
# Secret variables are NOT exported automatically: map them here. An
# undefined $(NAME) stays literal; the gate treats such values as unset.
- script: linebreak-gate ci
displayName: LineBreak gate
env:
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
LINEBREAK_LICENSE_KEY: $(LINEBREAK_LICENSE_KEY)
ANTHROPIC_API_KEY: $(ANTHROPIC_API_KEY)
- task: PublishBuildArtifacts@1
condition: always()
inputs:
pathToPublish: .linebreak/ci-out
artifactName: linebreak-gate-report
displayName: Publish the gate report
Cuatro pasos manuales, en este orden:
- Crear el pipeline desde
azure-pipelines.yml(Pipelines > New pipeline > Azure Repos Git > Existing YAML) y agregar las variables secretasLINEBREAK_LICENSE_KEYyANTHROPIC_API_KEY(Edit > Variables). Una variable que no existe queda como el texto literal$(NOMBRE); la compuerta la trata como no definida. - Project settings > Repos > Repositories > el repositorio > Security: dar a
la identidad
<proyecto> Build Service (<organización>)el permiso Contribute to pull requests. Sin eso, el comentario y el estado fallan con 403 (y el pipeline sigue bloqueando por código de salida). - Repos > Branches >
main> Branch policies > Build validation: agregar este pipeline como Required, disparo Automatic. Esa política es la que deja el botón Complete apagado mientras el build esté rojo. - Opcional: en la misma página, Status checks: exigir el estado
linebreak/gateque la compuerta publica en cada pull request.
En Azure Repos el disparador pr: del YAML no aplica: la política de Build
validation es la que corre el pipeline en cada pull request. pr: queda para
repositorios alojados en GitHub o Bitbucket y construidos desde Azure
Pipelines (en ese caso el comentario debe publicarse en ese proveedor; la API
de PR de Azure DevOps no aplica y la compuerta lo dice).
Imagen Docker: dos caminos
-
Imagen base de Python +
pip install(las plantillas de arriba). Funciona hoy sin publicar nada; descargaosv-scannerdesde GitHub en cada corrida (si el runner no tiene salida a internet, usar el camino 2). -
Imagen de CI de la compuerta, construida desde
Dockerfile.cien este directorio y publicada en un registro que el workspace o la organización pueda leer:docker build -f Dockerfile.ci -t <registro>/linebreak-gate-ci:1 . docker push <registro>/linebreak-gate-ci:1En Bitbucket:
image: <registro>/linebreak-gate-ci:1y se quitan las líneas decurlypip installdel script. En Azure: un container job (container: <registro>/linebreak-gate-ci:1bajopool) y se quita el paso de instalación. La imagen traeosv-scanner,git,bashycurl, corre como root y no defineENTRYPOINT: los tres son requisitos de Bitbucket Pipelines y de los container jobs de Azure.El
Dockerfilesin sufijo es la imagen del servidor MCP (entrypointlinebreak-gate mcp, usuario sin privilegios, sinosv-scanner) y no sirve para CI.
El comando
linebreak-gate ci [--path .] [--fail-on critical|high|medium|low]
[--story all|auto|<id>] [--manual auto|warn|block] [--stage auto|release|pr]
[--out-dir .linebreak/ci-out] [--no-comment] [--no-status]
--story auto toma la historia del nombre de rama feat/<id> o story/<id>
(con sufijo permitido) cuando es una historia aprobada, y si no evalúa solo
las historias iniciadas. --manual auto es warn en un pull request y
block en cualquier otra corrida; --stage auto es pr en un pull request y
release en el resto. Los proveedores detectados son GitHub Actions, GitLab
CI, Bitbucket Pipelines y Azure Pipelines; en GitHub el comentario lo sigue
publicando la Action.
El ciclo de especificación — autor, aprueba, sirve sobre MCP, aplica
La compuerta también aplica criterios de aceptación aprobados, y todo el ciclo es agnóstico a la herramienta — sin cuenta LineBreak, sin app de escritorio, sin servidor:
linebreak-gate spec new # scaffold a draft — fill it with any tool (your
# editor, Claude Code, ChatGPT), or distill it
# from the PRD you already have in Notion/Jira
linebreak-gate spec approve .linebreak/spec-draft.yml \
--approver "Ana Lopez <ana@example.com>" # a human on the record; commits
linebreak-gate mcp install --editor claude-code # or: cursor · codex · copilot
mcp install registra el servidor (.mcp.json, .cursor/mcp.json,
~/.codex/config.toml, o .vscode/mcp.json para GitHub Copilot en VS Code)
y escribe un bloque corto gestionado, entre marcadores LineBreak, en el archivo que
el agente siempre lee: CLAUDE.md, .cursor/rules/linebreak.mdc, AGENTS.md o
.github/copilot-instructions.md. El bloque dice que los criterios son el
contrato firmado, se leen con las herramientas MCP (no el CLI, no .linebreak/), que
los comandos de escritura del CLI necesitan que una persona pregunte, y que la especificación nunca se edita. Sigue
el idioma de la especificación (español o inglés); ejecutar install de nuevo
reemplaza solo ese bloque.
linebreak-gate mcp sirve el paquete aprobado (.linebreak/spec/) sobre
MCP (stdio) a Claude Code, Cursor, Codex, o cualquier cliente MCP. Seis herramientas:
list_stories, get_story (criterios como contexto del agente ANTES de que se
escriba código), next_story, set_story_status, check_story (el mismo
motor de evaluación que corre CI, con alcance a una historia), y spec_status (aprobación +
estado de firma sin conexión). Git es el transporte — sin red, sin cuenta,
funciona en un clon pelado — y nada en el puente puede escribir, editar o
invalidar un criterio aprobado: los criterios cambian solo editando el borrador
y re-aprobando, con un humano en el registro.
spec approve imprime lo que está a punto de aprobar (y cada declaración que
cambió, antes y después) y rechaza un borrador que no sea el del
remoto; --local aprueba la copia en disco a sabiendas.
Luego linebreak-gate check aplica los mismos criterios en CI: los checks de máquina
corren de verdad, los criterios manual bloquean hasta que haya una firma registrada. Un criterio tests
que ejecutó cero pruebas falla, y un check puede declarar lo que
espera observar (expect: {output, exit}); la sección "Integrity" de
docs/CRITERIA_ENFORCEMENT.md en el repositorio tiene los detalles. Primera
ejecución guiada con el porqué de cada paso:
linebreakapp.com/en/start.
CLI
linebreak-gate init [--path .] [--fail-on critical|high|medium|low] [--force] [--non-interactive] [--provider auto|github|bitbucket|azure|all]
linebreak-gate ci [--path .] [--fail-on ...] [--story all|auto|<id>] [--manual auto|warn|block] [--stage auto|release|pr] [--out-dir DIR] [--no-comment] [--no-status]
linebreak-gate scan [--path .] [--fail-on critical|high|medium|low] [--format summary|json]
linebreak-gate report [--path .] [--format summary|json]
linebreak-gate override --finding <id> --reason "…" --approver <name/email> [--expires YYYY-MM-DD|--days N] [--path .]
linebreak-gate override --criterion <id> --reason "…" --approver <name/email> [--expires YYYY-MM-DD|--days N] [--path .]
linebreak-gate check [--path .] [--format summary|json] [--story <id> ...|--started-only] [--manual block|warn] [--stage release|pr]
linebreak-gate signoff --criterion <id> --approver <name/email> --note "…" [--path .]
linebreak-gate tickets sync [--path .] # retry pending ticket-tracker operations
linebreak-gate spec new [--path .] [--out <file>] [--force]
linebreak-gate spec approve <draft> --approver <name/email> [--role architect] [--path .]
[--against <remote>/<branch>] [--local]
linebreak-gate spec list|next [--path .]
linebreak-gate spec show|check <story-id> [--path .]
linebreak-gate mcp [--path .] # serve the approved spec over stdio
linebreak-gate mcp install [--editor claude-code|cursor|codex|copilot] [--print]
linebreak-gate badge [--format markdown|html|url]
linebreak-gate publish --to <governance-url> [--project <id>] [--path .] [--stage pr|release] [--run-id <id>] [--dry-run]
-
initconfigura un repositorio con un solo comando: escribe el archivo de pipeline para el proveedor de CI del repositorio (workflow de GitHub Actions,bitbucket-pipelines.ymloazure-pipelines.yml, detectado desde el remoto de git o elegido con--provider; nunca sobrescribe uno existente sin--force), opcionalmente escribe.linebreak/gate.yml, ofrece almacenar los secretos mediante la CLI de GitHub y exigir la verificación degate— e imprime los enlaces de configuración exactos para cualquier cosa que no pueda hacer por ti. -
cies la ejecución completa para proveedores de CI sin una Action nativa (Bitbucket Pipelines, Azure DevOps): escaneo, verificación con alcance, directorio de evidencia, comentario en PR y estado de build a través de la API del proveedor, saliendo con el código peor. Consulta la sección de Bitbucket / Azure arriba. -
scanejecuta ambos detectores, escribe artefactos de auditoría nativos de git bajo.linebreak/audit/, y sale con 0/1/2. -
reportrenderiza el escaneo registrado: conteos por severidad y cada hallazgo con ID de CVE, CVSS, enlace de aviso y estado de anulación.--format jsonpara máquinas. -
overrideregistra un reconocimiento aprobado por humanos de un hallazgo exacto — el paquete + versión instalada + tupla de CVE. Un CVE diferente, una versión actualizada o un hallazgo nuevo aún bloquean.--reasony--approverson obligatorios; el registro aterriza en el rastro de aprobación del artefacto. Confirma el.linebreak/audit/*.jsonactualizado para que CI lo vea.--expires YYYY-MM-DD(o--days N) limita la aceptación en el tiempo: después de esa fecha el hallazgo vuelve a bloquear comoexpired_riskhasta que la aceptación se renueve o el hallazgo se corrija (ver Riesgos aceptados y tickets). -
checkevalúa los criterios de aceptación aprobados (.linebreak/spec/, aterrizado porspec approve) contra el árbol de trabajo:build/tests/commandse ejecutan de verdad,manualrequiere una aprobación registrada. Sale con 0 si todo está satisfecho (o sin bundle — un no-op limpio), 1 bloqueando (fallo o requiere aprobación), 2 error de herramienta/configuración/bundle (fallo cerrado). Escribe.linebreak/audit/criteria.json. Banderas de alcance (ver Alcance de verificación):--story <id>(repetible) evalúa solo esas historias,--started-onlyevalúa solo historias con un estado local iniciado,--manual warnreporta aprobaciones faltantes sin bloquear,--stage promite criterios marcadoscheck.when: release(listados como solo-release, no evaluados; el valor predeterminado--stage releaselos evalúa). El resumen y el JSON indican el alcance. -
signoffregistra una aprobación humana atribuida para un criteriomanualbajo.linebreak/spec/signoffs/(aditivo;--approvery--noteobligatorios). Se vincula al criterio como aprobado — editar el criterio y re-aprobar la especificación hace que las aprobaciones anteriores queden obsoletas. Confirma el registro. -
override --criterionregistra una anulación aprobada por humanos para un criterio de máquina fallido en.linebreak/audit/criteria.json— misma filosofía que las anulaciones de CVE: posible, siempre atribuida, obsoleta una vez que el criterio se edita. Otros criterios bloqueantes aún bloquean. -
spec new/spec approve— la ruta de autoría independiente de herramientas (ver la sección de bucle de especificación arriba): crea un borrador, llénalo con cualquier herramienta, aterrízalo como el bundle aprobado con una aprobación humana atribuida, confirmado. Las aprobaciones locales sin firmar se marcan comoidentity_source: client; las firmas criptográficas provienen del servicio de gobernanza (clave de licencia). -
spec listimprime el bundle de criterios de aceptación aprobados: cada historia, sus criterios con tipos de verificación y la atribución del aprobador. Solo lectura. Sale con 0 en un bundle válido o cuando no existe ninguno; sale con 2 en un bundle malformado (fallo cerrado en estructura).spec next/show/checkson los gemelos de CLI de las herramientas del puente MCP.
Publicar en el panel
linebreak-gate publish --to https://governance.example --project <id> envía
la ejecución registrada (criterios, hallazgos, aprobaciones, anulaciones, atestación) a un
servicio de gobernanza de LineBreak para que ejecutivos y auditores lo vean en el panel.
El bearer proviene de LINEBREAK_GOV_TOKEN (un token emitido por ese servicio),
si no de LINEBREAK_GOVERNANCE_TOKEN; --to por defecto es
LINEBREAK_GOVERNANCE_BASE_URL y --project a LINEBREAK_GOV_PROJECT. Ejecútalo
después de scan y check, como el último paso del trabajo.
Publicar nunca bloquea un cambio: un token faltante, un servicio inalcanzable
o un cuerpo rechazado imprime una advertencia y sale con 0. El veredicto ya fue dado
por scan/check; publish solo lo reporta. Bajo GitHub Actions, el run_id
se deriva del ID de ejecución y del intento, por lo que re-ejecutar el paso es idempotente
en el servidor. --dry-run imprime el cuerpo sin enviarlo.
Credenciales de gobernanza: una sola búsqueda
Cada comando que habla con el servicio de gobernanza (check para las aprobaciones del
panel y la configuración del rastreador, signoff / override para la
identidad verificada, publish, report --from-governance) encuentra la URL
del servicio, el token y el proyecto de la misma manera, el primero que aplique:
- variables de entorno (
LINEBREAK_GOVERNANCE_BASE_URL,LINEBREAK_GOVERNANCE_TOKEN,LINEBREAK_GOV_TOKEN,LINEBREAK_GOV_PROJECT); una variable establecida siempre gana; ~/.config/linebreak/governance.env($XDG_CONFIG_HOME/linebreak/cuando está establecido);~/.config/linebreak/governance-local.env(una instancia local);~/.linebreak/env, mantenido para compatibilidad con la aplicación de escritorio.
Los archivos son líneas KEY=value (export y comillas permitidas). Solo se usa el primer archivo
que lleva un token, completo: una URL de un archivo nunca se combina con un
token de otro. LINEBREAK_GOV_CREDENTIALS=off desactiva los archivos. En CI
no existen tales archivos, por lo que los pipelines se comportan como antes; en una estación de trabajo, un
token en uno de ellos hace que signoff y override registren la identidad de
gobernanza verificada (y se nieguen si ese token falla), exactamente como lo hace la
variable exportada.
Leer la ejecución publicada: report --from-governance
El escaneo se ejecuta en CI; report solo lee el escaneo registrado en esta
máquina. linebreak-gate report --from-governance --project <id> lee la
última ejecución que CI publicó en su lugar (GET /v1/reports/projects/{id} para la lista,
GET /v1/projects/{id}/gate-runs/{run_id} para la ejecución): hallazgos por severidad
con sus aceptaciones y marcas KEV, los conteos de criterios y el veredicto tal como
esa ejecución lo publicó. --stage pr|release toma la última ejecución de esa etapa,
--run-id una específica, --format json la ejecución completa. Solo lectura; sale con 2
cuando el servicio no se puede leer, 0 en caso contrario (también cuando aún no se ha publicado
nada).
Verificar la evidencia sin red: verify
La instancia de gobierno firma con su llave Ed25519 (el mismo kid que firma la
aprobación de la especificación) el expediente de cada corrida, cada excepción
y cada firma hecha desde el panel. "Descargar evidencia" en el panel (o
GET /v1/projects/{id}/gate-runs/{run_id}/evidence) baja un archivo
linebreak-evidence/v1 con cada registro, su texto firmado, su firma, su kid y
las llaves públicas de la instancia.
linebreak-gate verify evidencia-pagos-2026-09-12-55555555.json
linebreak-gate verify evidencia.json --key=<kid>=<llave pública base64>
Llaves de confianza: approvals.public_keys de .linebreak/gate.yml (en
--path, por defecto .) y las --key. La llave que trae el propio archivo
solo cuenta con --key-from-file, y el comando avisa que hay que confirmarla
por otro canal (GET /v1/keys o gate.yml). No usa la red.
| Salida | Significa |
|---|---|
| 0 | todo firmado por la instancia y verifica |
| 1 | una firma no verifica o su kid no es de confianza |
| 3 | lo firmado verifica, pero hay registros sin firma de la instancia (anteriores a esta versión) |
| 2 | el archivo no se puede leer o su formato no es conocido |
Badge
Muestra a los visitantes que el repositorio está protegido. linebreak-gate badge imprime un fragmento de README
listo para pegar (sin llamadas de red — la insignia estática de shields.io está completamente codificada
en su URL); --format html|url para las variantes de etiqueta o URL desnuda:
[](https://www.linebreakapp.com/en/gate)
Alcance de verificación: por historia en PRs, completo en release
Un equipo que aprueba todo el sprint por adelantado (el flujo que esta puerta promueve: especificación aprobada antes del código) vería de otro modo cada PR bloqueado por criterios de historias que nadie ha comenzado. La solución es el alcance, no una puerta más débil:
- Escaneo siempre. Los escaneos de dependencias y código se ejecutan en cada PR y en release, sin cambios.
- Verifica la historia en PRs.
linebreak-gate check --story <id>evalúa solo los criterios de esa historia (--storyse repite);--story autotoma la historia de una ramafeat/<id>ostory/<id>, si no solo historias iniciadas.--started-onlyevalúa solo historias cuyo estado local esdoing,reviewodone(el estado quespec nexty el puente MCP escriben); las historias sin estado se listan como no iniciadas y no cuentan. Cuando ninguna historia está iniciada en absoluto (sin archivo de estado, uno ilegible o un rastreador externo sin estados locales) el alcance no selecciona nada y la verificación es salida 2, nunca un pase vacío.--manual warnreporta criteriosmanualsin aprobación como pendientes en lugar de bloqueantes, para que una aprobación que pertenece al release no detenga un PR. - Verifica todo en release. El trabajo de release ejecuta el bundle completo con
--manual block(el predeterminado): cada criterio de cada historia, cada criteriomanualaprobado. Cada ejecución escribe.linebreak/audit/criteria.jsonsellado con suscopeypending_signoffs, por lo que una ejecución con alcance o relajada es evidencia de esa ejecución y nunca puede leerse como un veredicto completo (y CI nunca sube uno obsoleto).
El resumen imprime una línea scope: (modo, historias evaluadas, criterios contados,
política manual, etapa), una línea release-only (not evaluated at stage pr): cuando
se omitieron criterios y una línea pending sign-off: por aprobación faltante;
el JSON lleva lo mismo bajo scope (incluyendo stage y
release_only) y pending_signoffs. Un ID de --story desconocido
es salida 2 (un alcance que no nombra nada es un error, nunca un pase).
En la GitHub Action, el mismo patrón son tres entradas (stage se describe abajo). story es all (cada
historia, el predeterminado), auto (inferir el ID de una rama feat/<id> o story/<id>,
con un slug final permitido como en feat/<id>-add-login, cuando es una
historia aprobada; si no solo historias iniciadas), o un ID explícito. manual
es warn o block; dejado vacío es warn en pull_request y block en
cualquier otro evento. El comentario del PR muestra el alcance resuelto, las historias no
iniciadas y las aprobaciones pendientes.
Cambio de comportamiento para usuarios existentes de @v1 (1.11.0): el predeterminado de manual en
eventos pull_request ahora es warn, por lo que un criterio manual sin
aprobación ya no bloquea un PR a menos que el workflow establezca manual: block. Agrega
el trabajo de release abajo (o establece manual: block en el trabajo de PR) para mantener
las aprobaciones aplicadas.
# .github/workflows/security-gate.yml, PR gate + release gate
name: Security gate
on:
pull_request:
push:
tags: ["v*"] # the release job runs on release tags
permissions:
contents: read
pull-requests: write
jobs:
gate:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: Baktun-Studio/linebreak-gate@v1
with:
license-key: ${{ secrets.LINEBREAK_LICENSE_KEY }}
story: auto # this PR's story, or started stories only
manual: warn # sign-offs are listed, not blocking, on PRs
stage: pr # check.when: release criteria are listed, not evaluated
release-gate:
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: Baktun-Studio/linebreak-gate@v1
with:
license-key: ${{ secrets.LINEBREAK_LICENSE_KEY }}
story: all # every story, every criterion
manual: block # every manual criterion needs its sign-off
stage: release # the default; release-only criteria run and block
Exige la verificación de gate en la rama predeterminada y el trabajo release-gate
antes de publicar. CI genérico: las mismas dos invocaciones de la CLI, con los
códigos de salida respetados.
Criterios solo-release: check.when: release
Un criterio verificado por un script contra un entorno de staging compartido falla
para cada PR en el momento en que staging retrocede, incluido el PR que lo corrige.
Márcalo como when: release en la especificación:
- id: checkout-smoke
statement: The checkout smoke script passes against staging.
check:
type: command
payload: ./scripts/smoke-staging.sh
when: release # absent means always
Con stage: pr en el trabajo de PR (check --stage pr), dichos criterios no se
evalúan: sus verificaciones nunca se ejecutan, el informe los lista como [release-only]
con su propio conteo, y no son ni aprobados ni reprobados. El trabajo de release
(stage: release, el predeterminado) los evalúa como siempre, y uno que falle
aún bloquea el release. El campo es parte del contenido del criterio, por lo que
agregarlo a un criterio aprobado re-activa las firmas y anulaciones de ese
criterio como cualquier otra edición.
Release en dos fases: check.when: attestation y --phase
Un criterio como "el release lleva una atestación firmada" se firma EN una
ejecución de release en verde, por lo que la ejecución que lo produce no puede
exigirlo. Marca ese criterio manual como when: attestation:
- id: release-attestation
statement: The release carries a signed attestation of its green run.
check:
type: manual
when: attestation
Luego haz el release en dos ejecuciones: check --phase prepare (entrada de Action phase: prepare) evalúa todo excepto ese criterio, que se muestra como
[awaiting-attestation]; el aprobador lo firma contra esa ejecución; check
(--phase verify, el predeterminado) lo aplica como cualquier criterio manual, y
esa ejecución es el release. Otros criterios manual bloquean en ambas fases.
Entornos compartidos: check.resource y check.environment
- id: f1-e2e
statement: Flow F1 end to end against staging.
check:
type: command
payload: node scripts/qa/flows.mjs --base "$STAGING_URL" --flow f1
when: release
resource: staging-demo-merchant # runs alone; a clash is reported as such
environment:
name: staging
version_url: ${STAGING_URL}/version # answers the deployed commit
Las verificaciones que nombran el mismo resource nunca se superponen en una
misma máquina (un bloqueo de SO), y una que falle se vuelve a ejecutar una vez
en solitario: pasar en solitario es [collision] (no es un defecto, no bloquea),
volver a fallar es una falla "reproducida al re-ejecutarse en solitario". Entre
máquinas, serializa los trabajos en CI o dale a cada ejecución de extremo a
extremo su propio tenant desechable. environment registra la versión desplegada
que la verificación midió y advierte (nunca bloquea) cuando no es el commit
evaluado: atrasado, adelantado, divergido, inalcanzable o cambiado mientras la
verificación se ejecutaba.
Configuración — .linebreak/gate.yml
La rigurosidad de la compuerta es gobernanza, por lo que vive en el repositorio: cambiar el umbral es en sí mismo un PR: visible, revisable, atribuible en el historial de git.
# .linebreak/gate.yml
fail_on: critical # critical (default) | high | medium | low
exclude_paths: # optional: root-relative globs excluded from scanning
- fixtures
- "sandbox/*"
code_scan: auto # auto (run when model credentials are set) | on (required) | off
security: # exploitation policy, see "Prioridad por explotación" below
block_kev: true # default: anything in CISA's KEV catalog blocks
epss_threshold: 0.5 # default: off
criteria:
enforce: true # default: true whenever a spec bundle exists; false disables
# criteria checking only (the security scan is unaffected)
integrity: warn # warn (default) | block | off: shared tests between stories,
# statements naming identifiers gone from the code, commands that ran 0 tests
risk_acceptance: # optional: accepted risks expire (see the section below)
max_days: 90 # longest acceptance allowed
required: true # an override without --expires/--days is refused
tickets: # optional: mirror every acceptance into your tracker
provider: jira # jira | github
project: SEC # Jira project key, or "owner/repo" for GitHub Issues
labels: [linebreak]
Precedencia: bandera explícita --fail-on / entrada de Action → .linebreak/gate.yml →
predeterminado integrado (critical). fail_on puede vivir en el nivel
superior o bajo security: (no ambos). Una configuración inválida es un error
de herramienta (salida 2): un archivo de gobernanza roto nunca cae silenciosamente
a un valor predeterminado.
Prioridad por explotación
Un CVSS alto dice cuánto daño haría una vulnerabilidad; no dice si alguien la está usando. Desde 1.13.2 la compuerta enriquece cada hallazgo de dependencias con dos fuentes públicas y ordena y bloquea por explotación real:
- KEV (catálogo de vulnerabilidades explotadas conocidas de CISA): "la están explotando hoy".
- EPSS (FIRST): probabilidad de explotación en los próximos 30 días, entre 0 y 1: "probable en 30 días".
Política
# .linebreak/gate.yml
security:
fail_on: high # piso de severidad (o en la raíz del archivo, no en ambos)
block_kev: true # cualquier hallazgo en KEV bloquea, sea cual sea su severidad (por defecto)
epss_threshold: 0.5 # bloquea desde esta probabilidad a 30 días (apagado por defecto)
intel_max_age_hours: 24 # antigüedad máxima de la caché (por defecto 24 h)
intel: true # false apaga el enriquecimiento por completo (red y caché)
Un hallazgo bloquea por cualquiera de tres disparadores: severidad en o sobre
el piso, presencia en KEV (con block_kev) o EPSS en o sobre el umbral. El
motivo queda registrado en cada hallazgo (block_reason) y agregado en la
corrida (block_reasons) con exactamente dos valores, que el servicio de
gobierno lee por separado: kev (en el catálogo) y vulnerability (piso de
severidad o umbral EPSS). Cuando aplican los dos, gana kev. Un override
registrado con linebreak-gate override --finding <id> reconoce el hallazgo
exacto igual que siempre, cualquiera sea el motivo.
Orden y puntaje
El report, el veredicto y el JSON listan los hallazgos en este orden:
primero los que están en KEV, luego por EPSS de mayor a menor, luego por
severidad y CVSS. Un hallazgo sin puntaje EPSS (sin CVE, o un CVE que FIRST
aún no puntúa) va después de los puntuados, ordenado por severidad. Cada uno
muestra su etiqueta: explotada activamente (KEV), EPSS 0.93 o sin datos de explotación.
[BLOCKING: kev] CVE-2021-44228 critical cvss 10.0 log4j-core@2.14.1
explotada activamente (KEV), EPSS 1.00, CISA due 2021-12-24 risk 100
[BLOCKING: vulnerability] CVE-2024-3094 critical cvss 10.0 xz@5.6.0
EPSS 0.86 risk 100
[below floor] CVE-2019-0001 low cvss 2.0 x@1
EPSS 0.03 risk 20
El risk_score sube con la explotación y nunca baja: un hallazgo en KEV vale
100 sin importar su severidad; el EPSS eleva el puntaje hasta su probabilidad
en porcentaje (un medium con EPSS 0.93 vale 93); la severidad es el piso
(critical 100, high 80, medium 50, low 20). Sin datos de explotación los
números son los de siempre.
Caché y modo sin red
Las dos fuentes se guardan en .linebreak/cache/ (la carpeta se ignora sola
en git; el registro de auditoría es .linebreak/audit/security.json, no la
caché). Con caché fresca no hay ninguna llamada de red. Con caché vencida se
consulta la red y, si falla, se usa la caché vencida y el reporte lo dice
(stale). Sin red y sin caché los campos epss y kev quedan vacíos
(null) y el reporte dice sin datos de explotación: el enriquecimiento
nunca convierte un escaneo en error ni cambia el veredicto que habría dado
sin datos. LINEBREAK_OFFLINE=1 salta la red (la caché sigue usándose), útil
en pipelines sin salida a internet; security.intel: false apaga todo.
En el artefacto, kev: true es "está en el catálogo", kev: false es "se
consultó el catálogo y no está" y kev: null es "no se pudo consultar" (o el
hallazgo no tiene CVE): para un auditor son tres hechos distintos. La corrida
registra además exploit_intel (fuente y versión del catálogo de esa
corrida) y verdict con sus block_reasons.
Fuentes: https://api.first.org/data/v1/epss (por lotes de CVE) y
https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json.
Ninguna requiere credenciales.
Roles e identidad de quien firma (.linebreak/roles.yml)
Sin este archivo, cualquiera puede correr signoff u override y el nombre
que queda en el registro es el que la persona escribió (identity_source: client). Con él, la compuerta sabe quién puede firmar qué:
# .linebreak/roles.yml
roles:
ciso:
members: [ana@example.com]
can:
sign_criteria: ["*"] # patrones sobre el id del criterio, la historia o la épica
approve_overrides: ["*"]
accept_security_risk: [critical, high, medium, low]
qa:
members: [luis@example.com, "github:luis-qa"]
can:
sign_criteria: ["e12-*"]
approve_overrides: []
accept_security_risk: [low, medium]
policy:
require_roles: true # una firma sin rol autorizado se rechaza
require_verified_identity: false # true: una identidad solo declarada no cuenta
signoffyoverrideregistran el rol con el que se firma:--role, o se infiere cuando la persona tiene exactamente un rol que lo permite. Si no tiene ninguno yrequire_rolesestá activo, el comando falla y dice qué rol haría falta. Aceptar un hallazgo de seguridad se limita por severidad.check,scanyreportvuelven a evaluar cada firma y cada override contra los roles vigentes. Un registro cuyo rol ya no existe, cuyo miembro salió, o que no cubre ese criterio, se rechaza con motivorole_deniedy una línea legible (role denied: <id> (<historia>): ...); bloquea aunque el check corra con--manual warn. El registro no se borra: queda como evidencia y una firma posterior autorizada lo reemplaza.- Identidad: además de
client, la compuerta reconocevcs(en CI toma el actor del proveedor:GITHUB_ACTOR,GITLAB_USER_EMAIL,BITBUCKET_STEP_TRIGGERER_UUID,BUILD_REQUESTEDFOREMAIL) ygovernance(conLINEBREAK_GOVERNANCE_BASE_URLyLINEBREAK_GOVERNANCE_TOKENconsultaGET /v1/mey usa esa identidad; un token configurado que falla detiene el comando, nunca cae en silencio a un nombre escrito). La identidad verificada manda; lo que se escribió en--approverse guarda comodeclared_approver. En la lista de miembros se puede poner un correo o la formaproveedor:login(github:luis-qa). policy.require_verified_identity: truehace que una firma conidentity_source: clientse registre como declarada pero no cuente: el comando lo avisa al firmar ycheckla rechaza con motivoidentity_unverified.- Sin archivo, o con ambas políticas en
false, nada cambia: nada se rechaza y los registros llevanrole: null. Un archivo malformado es error de configuración (exit 2), nunca una omisión silenciosa.
Registros de auditoría
Cada escaneo y cada anulación se registran en .linebreak/audit/security.json
(dependencias) y .linebreak/audit/code.json (AI SAST): el mismo formato de documento
versionado que escriben las herramientas de LineBreak, que lleva hallazgos (ID
de CVE, CVSS, enlace de aviso), motor de escaneo, marca de tiempo, actor y la
trazabilidad de aprobación con el motivo y aprobador de cada anulación, el rol
bajo el que se hizo y la fuente de identidad (client, vcs,
governance). Quién relajó la compuerta, y cuándo, es en sí mismo auditable.
Riesgos aceptados y tickets
Una excepción registrada con override era para siempre: la persona que
aceptó el riesgo se va y el riesgo se queda. Desde la versión 1.13.3 cada
aceptación puede (o debe) vencer, y cada excepción vive también en el gestor de
tickets del equipo, para que la trazabilidad quede en su herramienta y no solo
en LineBreak.
Vencimiento
linebreak-gate override --finding <id> --reason "…" --approver ana@example.com --expires 2026-12-31
linebreak-gate override --criterion <id> --reason "…" --approver ana@example.com --days 30
-
--expires YYYY-MM-DDo--days Nfijan la fecha en que la aceptación deja de valer. Vale hasta ese día inclusive; al día siguiente el hallazgo (o el criterio) vuelve a bloquear con motivoexpired_risk. El informe dice qué hallazgo es, quién lo aceptó, cuándo venció y las dos salidas: renovar con otrooverrideo corregir. -
Con menos de 14 días por vencer,
scan,reportycheckavisan sin bloquear (expiring risk/expiring exception). -
La política vive en
.linebreak/gate.yml:risk_acceptance: max_days: 90 # ninguna aceptación puede ir más allá de 90 días required: true # un override sin vencimiento se rechaza (exit 2)Sin este bloque, las aceptaciones sin fecha siguen permitidas (el comportamiento anterior). La misma política aplica a hallazgos de seguridad y a excepciones de criterios.
-
Renovar es registrar un
overridenuevo sobre el mismo objetivo. La aceptación anterior no se pisa: queda en el historial del artefacto (.linebreak/audit/security.json,code.json,criteria.json) y la más reciente es la que rige. Cada entrada guardaexpires, yticketcuando hay gestor configurado. -
En
--format json,scan/reporttraenblock_reasons(vulnerability,expired_risk) y, por detector,expiredyexpiring;checktraeblock_reasons,expired_overridesyexpiring_overrides.
Tickets (Jira primero, GitHub Issues también)
# .linebreak/gate.yml
tickets:
provider: jira # jira | github
project: SEC # clave del proyecto en Jira, o "owner/repo" para GitHub Issues
labels: [linebreak] # etiquetas que llevan todos los tickets
main_branch: main # rama cuyos scans abren tickets de atención (opcional)
issue_type: Task # solo Jira: tipo de incidencia a crear (opcional)
Credenciales por entorno, nunca en el repositorio: JIRA_BASE_URL,
JIRA_EMAIL y JIRA_API_TOKEN para Jira (API REST v2, funciona en Cloud y en
Data Center); GITHUB_TOKEN para GitHub Issues.
Qué hace la compuerta cuando hay un tickets: configurado:
| Momento | Qué pasa en el gestor |
|---|---|
override acepta un hallazgo o excusa un criterio | Crea un ticket con el hallazgo o criterio, quién aceptó, motivo, vencimiento, repositorio, commit y enlace al registro de evidencia. La clave queda en la entrada de evidencia (ticket: "SEC-123"). Si ya existía, lo comenta y lo reabre (renovación). |
scan o check ven una aceptación vencida | Comenta el ticket y lo reabre si estaba cerrado. Un solo comentario por fecha de vencimiento, aunque el gate corra en cada push. |
El hallazgo desaparece del scan, o el criterio pasa por sí solo en un check completo | Comenta y cierra el ticket. |
Un scan en la rama principal encuentra un hallazgo que no estaba en el escaneo anterior guardado | Abre un ticket de atención (vulnerabilidad nueva sobre código ya liberado). |
Detalles que conviene saber:
- El gestor nunca bloquea el veredicto. Sin red, credenciales malas o un
proyecto que rechaza la incidencia: el veredicto es el que dicen los
artefactos, se imprime un aviso, el error queda en la evidencia
(
ticket_erroren la entrada) y la operación queda pendiente en.linebreak/audit/tickets.json.linebreak-gate tickets syncreintenta lo pendiente (exit 0 cuando no queda nada; exit 1 si algo sigue pendiente). - La idempotencia vive en el gestor. Cada ticket lleva las etiquetas
linebreak-target-<hash>ylinebreak-artifact-<security|code|criteria>; antes de crear, la compuerta busca por etiqueta. Así un runner de CI que no tiene el libro local nunca duplica tickets, y puede cerrar los de hallazgos corregidos. - "Nuevo" en la rama principal se mide contra el artefacto anterior en
.linebreak/audit/(el que estaba comprometido antes de reescribirlo). El primer scan de un repositorio no abre nada: no hay línea base. Solo cuentan hallazgos a la altura del umbralfail_ono por encima, y que no tengan ya un ticket. En GitHub Actions la rama se lee deGITHUB_REF_NAME; un pull request (GITHUB_HEAD_REF) nunca cuenta como rama principal. - Conviene comprometer
.linebreak/audit/tickets.jsonjunto con eloverride(igual quesecurity.json), para que el historial del ticket viaje con la evidencia.
Precios
Gratis, para siempre: el escaneo de vulnerabilidades de dependencias y todo el ciclo de especificación — autoría, aprobación humana, servicio MCP y aplicación en CI. Sin clave, sin cuenta.
Pro — $99/mes por equipo (precios):
aprobaciones firmadas criptográficamente y a prueba de manipulación (Ed25519, verificables
sin conexión), modo de aplicación con clave obligatoria y revisión de código con IA alojada sin
clave de API que gestionar. Compra en la página de precios — tu LINEBREAK_LICENSE_KEY
llega por correo en segundos (es la entrada license-key de la Action).
¿Prefieres tu propia clave de modelo? ANTHROPIC_API_KEY también habilita la revisión con IA;
la revisión alojada de Pro es la ruta sin configuración.
El gate se ejecuta abierto por defecto: funciona sin clave e imprime un aviso
cuando no se establece LINEBREAK_LICENSE_KEY (suprimido para usuarios BYOK). Eso es
freemium — el escaneo de dependencias se ejecuta gratis. Los equipos que quieran exigir una
clave Pro válida para que el gate se ejecute en absoluto pueden optar por
LINEBREAK_ENTITLEMENTS_PROVIDER=remote, que verifica el derecho antes de
cualquier escaneo y falla de forma cerrada ante una clave faltante, inválida o revocada, un plan incorrecto o un
servicio inalcanzable — bloqueando todo el gate, incluido el escaneo de dependencias.