linebreak-gate

Portão de CI com falha fechada para código escrito por IA: bloqueia CVEs conhecidas e serve critérios de aceitação aprovados por humanos para Claude Code/Cursor/Codex via MCP, somente leitura para o agente.

Documentação

linebreak-gate — a compuerta de segurança LineBreak na fronteira git/CI

Veja em ação

Um pull request real, bloqueado de verdade: a compuerta é um check obrigatório, então o botão de merge fica cinza até a CVE ser corrigida ou um humano nomeado registrar uma exceção.

Real pull request blocked by the LineBreak Security Gate: required check failing, merge disabled

Veja ao vivo — um PR público que você pode abrir agora →

Uma gravação real, sem simulação: a varredura bloqueia uma CVE crítica com falha fechada, o pin é corrigido, a compuerta abre.

linebreak-gate scan blocking a critical CVE, then passing after the fix

O ciclo de especificação: um humano nomeado aprova os critérios, check bloqueia até o critério manual ter uma aprovação registrada, então tudo passa.

spec approve, check blocked until sign-off, then all criteria pass

Bloqueia merges que carregam vulnerabilidades conhecidas. Uma ferramenta, dois detectores — a varredura de dependências é gratuita; a revisão de IA é o upgrade Pro:

  • Varredura de CVE de dependências — gratuita, sem chave — osv-scanner em todos os ecossistemas (npm, PyPI, Go, Cargo, Maven, …), com um fallback npm audit para projetos npm (cobertura apenas npm e sem dados de versão instalada — a GitHub Action falha fechada se o osv-scanner não puder ser instalado em vez de degradar para ele).
  • IA SAST — Pro — uma revisão de segurança por LLM do código-fonte de primeira parte (injeção, autenticação quebrada, exposição de segredos, SSRF, desserialização insegura, uso indevido de criptografia) com verificação adversarial, habilitada por LINEBREAK_LICENSE_KEY (hospedado, usa créditos) ou ANTHROPIC_API_KEY (sua própria chave, tem precedência). Sem uma chave, a varredura de dependências ainda roda e esta etapa é pulada com um aviso.

A compuerta bloqueia e pode propor; nunca limpa automaticamente por ordem de um agente. Um humano aprova a correção ou registra uma exceção — com um motivo e um aprovador — em um arquivo de auditoria commitado no git.

Este é o mesmo núcleo de varredura que alimenta o resto da compuerta de segurança dentro do produto LineBreak (o backend desktop importa este pacote), mas é totalmente independente: uma equipe que nunca usou nada mais da LineBreak pode adicionar a compuerta ao seu repositório e obter aplicação real.

Contribuição e licença. Este repositório é o código-fonte publicado de linebreak-gate (Apache-2.0): cada release chega aqui e no PyPI a partir do nosso CI, e cada mudança passou pela nossa própria compuerta primeiro — varredura de CVE e critérios aprovados por humanos, a mesma disciplina que vendemos. Relatórios de bugs e pedidos de recursos: abra uma issue ou discussão aqui; lemos tudo. PRs diretos para este repositório não podem ser mesclados (releases fluem pelo nosso pipeline de revisão), então comece com uma issue e seguimos a partir daí.

Início 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 }}

A action roda linebreak-gate scan, sempre roda report, publica um comentário no PR (atualizado no lugar a cada push, nunca spam), envia o relatório JSON

  • artefatos de auditoria como artefato de workflow, e falha o check conforme o código de saída da varredura.

Torne isso uma fronteira real: exija o check

Um job de CI que pode ser ignorado é um painel, não uma compuerta. No seu repositório:

Settings → Branches → Branch protection rules → sua branch padrão → "Require status checks to pass before merging" → adicione o job gate (o nome do job que roda esta action). A partir daí, um PR com uma CVE crítica não pode ser mesclado pela interface do GitHub.

Início rápido — qualquer outro CI (exemplo GitLab)

O CLI é um pacote Python simples com códigos de saída estritos — 0 passa, 1 achados bloqueantes, 2 erro de ferramenta/configuração (falha fechada: uma falha do scanner falha o pipeline, nunca é uma passagem limpa). Qualquer CI que respeite códigos de saída obtém a mesma aplicação:

# .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 o job como obrigatório (sem allow_failure) e proteja a branch.

Bitbucket Pipelines e Azure DevOps

A mesma compuerta em Bitbucket Pipelines e Azure DevOps: linebreak-gate ci roda varredura + verificação, publica o comentário do PR e o status do build pela API do provedor, e sai com 0/1/2. Esta seção está em espanhol para as equipes que estão pilotando; o runbook passo a passo está em docs/RUNBOOK_BITBUCKET_AZURE.md no 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 permisos pullrequest:write y repository: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:

  1. Crear el pipeline desde azure-pipelines.yml (Pipelines > New pipeline > Azure Repos Git > Existing YAML) y agregar las variables secretas LINEBREAK_LICENSE_KEY y ANTHROPIC_API_KEY (Edit > Variables). Una variable que no existe queda como el texto literal $(NOMBRE); la compuerta la trata como no definida.
  2. 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).
  3. 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.
  4. Opcional: en la misma página, Status checks: exigir el estado linebreak/gate que 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

  1. Imagen base de Python + pip install (las plantillas de arriba). Funciona hoy sin publicar nada; descarga osv-scanner desde GitHub en cada corrida (si el runner no tiene salida a internet, usar el camino 2).

  2. Imagen de CI de la compuerta, construida desde Dockerfile.ci en 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:1
    

    En Bitbucket: image: <registro>/linebreak-gate-ci:1 y se quitan las líneas de curl y pip install del script. En Azure: un container job (container: <registro>/linebreak-gate-ci:1 bajo pool) y se quita el paso de instalación. La imagen trae osv-scanner, git, bash y curl, corre como root y no define ENTRYPOINT: los tres son requisitos de Bitbucket Pipelines y de los container jobs de Azure.

    El Dockerfile sin sufijo es la imagen del servidor MCP (entrypoint linebreak-gate mcp, usuario sin privilegios, sin osv-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.

O ciclo de especificação — autor, aprove, sirva via MCP, aplique

A compuerta também aplica critérios de aceitação aprovados, e todo o ciclo é agnóstico de ferramenta — sem conta LineBreak, sem app desktop, sem 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 o servidor (.mcp.json, .cursor/mcp.json, ~/.codex/config.toml ou .vscode/mcp.json para GitHub Copilot no VS Code) e escreve um bloco gerenciado curto, entre marcadores LineBreak, no arquivo que o agente sempre lê: CLAUDE.md, .cursor/rules/linebreak.mdc, AGENTS.md ou .github/copilot-instructions.md. O bloco diz que os critérios são o contrato assinado, são lidos com as ferramentas MCP (não o CLI, não .linebreak/), que comandos de escrita do CLI precisam que uma pessoa peça, e que a especificação nunca é editada. Ele segue o idioma da especificação (espanhol ou inglês); rodar install novamente substitui apenas esse bloco.

linebreak-gate mcp serve o pacote aprovado (.linebreak/spec/) via MCP (stdio) para Claude Code, Cursor, Codex ou qualquer cliente MCP. Seis ferramentas: list_stories, get_story (critérios como contexto do agente ANTES de o código ser escrito), next_story, set_story_status, check_story (o mesmo motor de avaliação que o CI roda, com escopo para uma história), e spec_status (aprovação + estado de assinatura offline). Git é o transporte — sem rede, sem conta, funciona em um clone simples — e nada na ponte pode escrever, editar ou invalidar um critério aprovado: critérios mudam apenas editando o rascunho e re-aprovando, com um humano no registro.

spec approve imprime o que está prestes a aprovar (e cada declaração que mudou, antes e depois) e recusa um rascunho que não seja o do remoto; --local aprova a cópia no disco conscientemente.

Então linebreak-gate check aplica os mesmos critérios no CI: verificações de máquina rodam de verdade, critérios manual bloqueiam até uma aprovação registrada. Um critério tests que executou zero testes falha, e uma verificação pode declarar o que espera observar (expect: {output, exit}); a seção "Integrity" de docs/CRITERIA_ENFORCEMENT.md no repositório tem os detalhes. Primeira execução guiada com o porquê de cada passo: 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]
  • init configura um repositório em um único comando: escreve o arquivo de pipeline para o provedor de CI do repositório (workflow do GitHub Actions, bitbucket-pipelines.yml ou azure-pipelines.yml, detectado a partir do remote do git ou escolhido com --provider; nunca sobrescreve um existente sem --force), opcionalmente escreve .linebreak/gate.yml, oferece armazenar os segredos via GitHub CLI e exigir a verificação gate — e imprime os links exatos de configuração para qualquer coisa que não puder fazer por você.

  • ci é a execução completa para provedores de CI sem uma Action nativa (Bitbucket Pipelines, Azure DevOps): scan, verificação com escopo, diretório de evidências, comentário em PR e status de build através da API do provedor, sair com o pior código. Veja a seção Bitbucket / Azure acima.

  • scan executa ambos os detectores, escreve artefatos de auditoria nativos do git sob .linebreak/audit/, e sai com 0/1/2.

  • report renderiza o scan registrado: contagens por severidade e cada achado com ID de CVE, CVSS, link de advisory e status de override. --format json para máquinas.

  • override registra um reconhecimento aprovado por humano de um achado exato — o pacote + versão instalada + tupla de CVE. Um CVE diferente, uma versão atualizada ou um novo achado ainda bloqueia. --reason e --approver são obrigatórios; o registro vai para a trilha de aprovação do artefato. Faça commit do .linebreak/audit/*.json atualizado para que o CI o veja. --expires YYYY-MM-DD (ou --days N) limita a aceitação no tempo: após essa data, o achado bloqueia novamente como expired_risk até que a aceitação seja renovada ou o achado seja corrigido (veja Riscos aceitos e tickets).

  • check avalia os critérios de aceitação aprovados (.linebreak/spec/, entregue por spec approve) contra a árvore de trabalho: build/tests/command executam de verdade, manual exige um sign-off registrado. Saída 0 se tudo satisfeito (ou sem bundle — um no-op limpo), 1 bloqueando (falha ou precisa de sign-off), 2 erro de ferramenta/config/bundle (falha fechada). Escreve .linebreak/audit/criteria.json. Flags de escopo (veja Escopo da verificação): --story <id> (repetível) avalia apenas essas histórias, --started-only avalia apenas histórias com estado local iniciado, --manual warn relata sign-offs ausentes sem bloquear, --stage pr pula critérios marcados check.when: release (listados como somente-release, não avaliados; o padrão --stage release os avalia). O resumo e o JSON declaram o escopo.

  • signoff registra um sign-off atribuído por humano para um critério manual sob .linebreak/spec/signoffs/ (aditivo; --approver e --note obrigatórios). Ele vincula-se ao critério como aprovado — editar o critério e re-aprovar a especificação torna sign-offs anteriores obsoletos. Faça commit do registro.

  • override --criterion registra um override aprovado por humano para um critério de máquina falho em .linebreak/audit/criteria.json — mesma filosofia dos overrides de CVE: possível, sempre atribuído, obsoleto assim que o critério é editado. Outros critérios bloqueantes ainda bloqueiam.

  • spec new / spec approve — o caminho de autoria agnóstico de ferramenta (veja a seção spec-loop acima): crie um rascunho, preencha-o com qualquer ferramenta, entregue-o como o bundle aprovado com uma aprovação humana atribuída, com commit. Aprovações locais não assinadas são marcadas como identity_source: client; assinaturas criptográficas vêm do serviço de governança (chave de licença).

  • spec list imprime o bundle de critérios de aceitação aprovados: cada história, seus critérios com tipos de verificação e a atribuição do aprovador. Somente leitura. Saída 0 em um bundle válido ou quando nenhum existe; saída 2 em um bundle malformado (falha fechada na estrutura). spec next / show / check são os gêmeos de CLI das ferramentas da ponte MCP.

Publicar no painel

linebreak-gate publish --to https://governance.example --project <id> envia a execução registrada (critérios, achados, sign-offs, overrides, atestação) para um serviço de governança LineBreak para que executivos e auditores a vejam no painel. O bearer vem de LINEBREAK_GOV_TOKEN (um token emitido por esse serviço), senão LINEBREAK_GOVERNANCE_TOKEN; --to tem como padrão LINEBREAK_GOVERNANCE_BASE_URL e --project para LINEBREAK_GOV_PROJECT. Execute após scan e check, como a última etapa do job.

Publicar nunca bloqueia uma mudança: um token ausente, um serviço inacessível ou um corpo rejeitado imprime um aviso e sai com 0. O veredito já foi dado por scan/check; publish apenas o relata. Sob GitHub Actions, o run_id é derivado do ID da execução e da tentativa, então re-executar a etapa é idempotente no lado do servidor. --dry-run imprime o corpo sem enviar.

Credenciais de governança: uma única consulta

Todo comando que fala com o serviço de governança (check para os sign-offs do painel e a configuração do tracker, signoff / override para a identidade verificada, publish, report --from-governance) encontra a URL do serviço, o token e o projeto da mesma forma, o primeiro que se aplica:

  1. variáveis de ambiente (LINEBREAK_GOVERNANCE_BASE_URL, LINEBREAK_GOVERNANCE_TOKEN, LINEBREAK_GOV_TOKEN, LINEBREAK_GOV_PROJECT); uma variável definida sempre vence;
  2. ~/.config/linebreak/governance.env ($XDG_CONFIG_HOME/linebreak/ quando definido);
  3. ~/.config/linebreak/governance-local.env (uma instância local);
  4. ~/.linebreak/env, mantido para compatibilidade com o aplicativo desktop.

Arquivos são linhas KEY=value (export e aspas permitidas). Apenas o primeiro arquivo que carrega um token é usado, por inteiro: uma URL de um arquivo nunca é pareada com um token de outro. LINEBREAK_GOV_CREDENTIALS=off desativa os arquivos. Em CI não existem tais arquivos, então os pipelines se comportam como antes; em uma estação de trabalho, um token em um deles faz signoff e override registrarem a identidade de governança verificada (e recusarem se esse token falhar), exatamente como a variável exportada faz.

Ler a execução publicada: report --from-governance

O scan roda em CI; report sozinho apenas lê o scan registrado nesta máquina. linebreak-gate report --from-governance --project <id> lê a última execução que o CI publicou (GET /v1/reports/projects/{id} para a lista, GET /v1/projects/{id}/gate-runs/{run_id} para a execução): achados por severidade com suas aceitações e marcas KEV, as contagens de critérios e o veredito como essa execução o publicou. --stage pr|release pega a última execução daquele estágio, --run-id uma específica, --format json a execução inteira. Somente leitura; saída 2 quando o serviço não pode ser lido, 0 caso contrário (também quando nada foi publicado ainda).

Verificar a evidência sem rede: verify

A instância de governança assina com sua chave Ed25519 (o mesmo kid que assina a aprovação da especificação) o expediente de cada corrida, cada exceção e cada assinatura feita a partir do painel. "Baixar evidência" no painel (ou GET /v1/projects/{id}/gate-runs/{run_id}/evidence) baixa um arquivo linebreak-evidence/v1 com cada registro, seu texto assinado, sua assinatura, seu kid e as chaves públicas da instância.

linebreak-gate verify evidencia-pagos-2026-09-12-55555555.json
linebreak-gate verify evidencia.json --key=<kid>=<llave pública base64>

Chaves de confiança: approvals.public_keys de .linebreak/gate.yml (em --path, por padrão .) e as --key. A chave que o próprio arquivo traz só conta com --key-from-file, e o comando avisa que é preciso confirmá-la por outro canal (GET /v1/keys ou gate.yml). Não usa a rede.

SaídaSignifica
0tudo assinado pela instância e verifica
1uma assinatura não verifica ou seu kid não é de confiança
3o assinado verifica, mas há registros sem assinatura da instância (anteriores a esta versão)
2o arquivo não pode ser lido ou seu formato não é conhecido

Badge

Mostre aos visitantes que o repositório é protegido. linebreak-gate badge imprime um trecho de README pronto para colar (sem chamadas de rede — o badge estático do shields.io está totalmente codificado em sua URL); --format html|url para a tag ou variantes de URL simples:

[![gated by LineBreak](https://img.shields.io/badge/gated%20by-LineBreak-14120F?labelColor=FAF8F4)](https://www.linebreakapp.com/en/gate)

Escopo da verificação: por história em PRs, completo no release

Uma equipe que aprova o sprint inteiro antecipadamente (o fluxo que este gate promove: especificação aprovada antes do código) veria, de outra forma, cada PR bloqueado por critérios de histórias que ninguém começou. A correção é escopo, não um gate mais fraco:

  • Scan sempre. Os scans de dependências e código rodam em cada PR e no release, sem mudanças.
  • Verifique a história em PRs. linebreak-gate check --story <id> avalia apenas os critérios daquela história (--story repete); --story auto pega a história de um branch feat/<id> ou story/<id>, senão apenas histórias iniciadas. --started-only avalia apenas histórias cujo estado local é doing, review ou done (o estado que spec next e a ponte MCP escrevem); histórias sem estado são listadas como não iniciadas e não contam. Quando nenhuma história está iniciada (sem arquivo de estado, um ilegível ou um tracker externo sem estados locais), o escopo não seleciona nada e a verificação é saída 2, nunca uma aprovação vazia. --manual warn relata critérios manual sem sign-off como pendentes em vez de bloqueantes, para que um sign-off que pertence ao release não segure um PR.
  • Verifique tudo no release. O job de release roda o bundle completo com --manual block (o padrão): cada critério de cada história, cada critério manual com sign-off. Cada execução escreve .linebreak/audit/criteria.json carimbado com seu scope e pending_signoffs, para que uma execução com escopo ou relaxada seja evidência dessa execução e nunca possa ser lida como um veredito completo (e o CI nunca envia um obsoleto).

O resumo imprime uma linha scope: (modo, histórias avaliadas, critérios contados, política manual, estágio), uma linha release-only (not evaluated at stage pr): quando critérios foram pulados e uma linha pending sign-off: por sign-off ausente; o JSON carrega o mesmo sob scope (incluindo stage e release_only) e pending_signoffs. Um id --story desconhecido é saída 2 (um escopo que não nomeia nada é um erro, nunca uma aprovação).

Na GitHub Action, o mesmo padrão são três entradas (stage é descrito abaixo). story é all (cada história, o padrão), auto (inferir o id de um branch feat/<id> ou story/<id>, um slug final permitido como em feat/<id>-add-login, quando é uma história aprovada; senão apenas histórias iniciadas) ou um id explícito. manual é warn ou block; deixado vazio, é warn em pull_request e block em qualquer outro evento. O comentário do PR mostra o escopo resolvido, as histórias não iniciadas e os sign-offs pendentes.

Mudança de comportamento para usuários existentes de @v1 (1.11.0): o padrão manual em eventos pull_request agora é warn, então um critério manual sem sign-off não bloqueia mais um PR, a menos que o workflow defina manual: block. Adicione o job de release abaixo (ou defina manual: block no job de PR) para manter os sign-offs aplicados.

# .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

Exija a verificação gate no branch padrão e o job release-gate antes de publicar. CI genérico: as mesmas duas invocações da CLI, com os códigos de saída respeitados.

Critérios somente-release: check.when: release

Um critério verificado por um script contra um ambiente de staging compartilhado falha para cada PR no momento em que o staging regride, incluindo o PR que o corrige. Marque-o como when: release na especificação:

- id: checkout-smoke
  statement: The checkout smoke script passes against staging.
  check:
    type: command
    payload: ./scripts/smoke-staging.sh
    when: release # absent means always

Com stage: pr no trabalho de PR (check --stage pr), tais critérios não são avaliados: suas verificações nunca são executadas, o relatório os lista como [release-only] com sua própria contagem, e eles não são aprovados nem reprovados. O trabalho de release (stage: release, o padrão) os avalia como sempre, e uma falha ainda bloqueia o release. O campo faz parte do conteúdo do critério, então adicioná-lo a um critério aprovado reativa as aprovações e substituições desse critério como qualquer outra edição.

Release em duas fases: check.when: attestation e --phase

Um critério como "o release carrega uma atestação assinada" é assinado EM uma execução de release verde, então a execução que o produz não pode exigi-lo. Marque esse critério manual como when: attestation:

- id: release-attestation
  statement: The release carries a signed attestation of its green run.
  check:
    type: manual
    when: attestation

Então faça o release em duas execuções: check --phase prepare (entrada da Action phase: prepare) avalia tudo exceto esse critério, que aparece como [awaiting-attestation]; o aprovador assina contra essa execução; check (--phase verify, o padrão) o aplica como qualquer critério manual, e essa execução é o release. Outros critérios manual bloqueiam em ambas as fases.

Ambientes compartilhados: check.resource e 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

Verificações que nomeiam o mesmo resource nunca se sobrepõem em uma máquina (um bloqueio de SO), e uma falha é reexecutada uma vez sozinha: passar sozinha é [collision] (não é um defeito, não bloqueia), falhar novamente é uma falha "reproduzida quando reexecutada sozinha". Entre máquinas, serialize os trabalhos no CI ou dê a cada execução de ponta a ponta seu próprio locatário descartável. environment registra a versão implantada que a verificação mediu e avisa (nunca bloqueia) quando não é o commit avaliado: atrás, à frente, divergente, inalcançável ou alterado durante a execução da verificação.

Configuração — .linebreak/gate.yml

O rigor do portão é governança, então ele vive no repositório — mudar o limiar é em si um PR: visível, revisável, atribuível no histórico do 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]

Precedência: flag --fail-on explícita / entrada da Action → .linebreak/gate.yml → padrão embutido (critical). fail_on pode estar no nível superior ou sob security: (não ambos). Uma configuração inválida é um erro de ferramenta (saída 2): um arquivo de governança quebrado nunca cai silenciosamente em um padrão.

Prioridade por exploração

Um CVSS alto diz quanto dano uma vulnerabilidade causaria; não diz se alguém a está usando. Desde 1.13.2, o portão enriquece cada achado de dependências com duas fontes públicas e ordena e bloqueia por exploração real:

  • KEV (catálogo de vulnerabilidades exploradas conhecidas da CISA): "estão explorando hoje".
  • EPSS (FIRST): probabilidade de exploração nos próximos 30 dias, entre 0 e 1: "provável em 30 dias".

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é)

Um achado bloqueia por qualquer um de três gatilhos: severidade em ou acima do piso, presença no KEV (com block_kev) ou EPSS em ou acima do limiar. O motivo fica registrado em cada achado (block_reason) e agregado na execução (block_reasons) com exatamente dois valores, que o serviço de governança lê separadamente: kev (no catálogo) e vulnerability (piso de severidade ou limiar EPSS). Quando ambos se aplicam, vence kev. Uma substituição registrada com linebreak-gate override --finding <id> reconhece o achado exato como sempre, independentemente do motivo.

Ordem e pontuação

O report, o veredito e o JSON listam os achados nesta ordem: primeiro os que estão no KEV, depois por EPSS do maior para o menor, depois por severidade e CVSS. Um achado sem pontuação EPSS (sem CVE, ou um CVE que a FIRST ainda não pontuou) vai depois dos pontuados, ordenado por severidade. Cada um mostra seu rótulo: explotada activamente (KEV), EPSS 0.93 ou sem dados de exploração.

  [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

O risk_score sobe com a exploração e nunca desce: um achado no KEV vale 100 independentemente da severidade; o EPSS eleva a pontuação até sua probabilidade em porcentagem (um medium com EPSS 0.93 vale 93); a severidade é o piso (critical 100, high 80, medium 50, low 20). Sem dados de exploração, os números são os de sempre.

Cache e modo sem rede

As duas fontes são armazenadas em .linebreak/cache/ (a pasta é ignorada sozinha no git; o registro de auditoria é .linebreak/audit/security.json, não o cache). Com cache fresco, não há nenhuma chamada de rede. Com cache vencido, consulta-se a rede e, se falhar, usa-se o cache vencido e o relatório informa (stale). Sem rede e sem cache, os campos epss e kev ficam vazios (null) e o relatório diz sin datos de explotación: o enriquecimento nunca converte uma varredura em erro nem muda o veredito que teria dado sem dados. LINEBREAK_OFFLINE=1 pula a rede (o cache continua sendo usado), útil em pipelines sem saída para a internet; security.intel: false desliga tudo.

No artefato, kev: true é "está no catálogo", kev: false é "foi consultado o catálogo e não está" e kev: null é "não foi possível consultar" (ou o achado não tem CVE): para um auditor, são três fatos distintos. A execução registra também exploit_intel (fonte e versão do catálogo dessa execução) e verdict com seus block_reasons.

Fontes: https://api.first.org/data/v1/epss (por lotes de CVE) e https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json. Nenhuma requer credenciais.

Papéis e identidade de quem assina (.linebreak/roles.yml)

Sem este arquivo, qualquer um pode executar signoff ou override e o nome que fica no registro é o que a pessoa escreveu (identity_source: client). Com ele, o portão sabe quem pode assinar o 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
  • signoff e override registram o papel com o qual se assina: --role, ou é inferido quando a pessoa tem exatamente um papel que o permite. Se não tiver nenhum e require_roles estiver ativo, o comando falha e diz qual papel seria necessário. Aceitar um achado de segurança é limitado por severidade.
  • check, scan e report reavaliam cada assinatura e cada substituição contra os papéis vigentes. Um registro cujo papel não existe mais, cujo membro saiu, ou que não cobre esse critério, é rejeitado com motivo role_denied e uma linha legível (role denied: <id> (<historia>): ...); bloqueia mesmo que a verificação seja executada com --manual warn. O registro não é apagado: fica como evidência e uma assinatura posterior autorizada o substitui.
  • Identidade: além de client, o portão reconhece vcs (no CI, usa o ator do provedor: GITHUB_ACTOR, GITLAB_USER_EMAIL, BITBUCKET_STEP_TRIGGERER_UUID, BUILD_REQUESTEDFOREMAIL) e governance (com LINEBREAK_GOVERNANCE_BASE_URL e LINEBREAK_GOVERNANCE_TOKEN consulta GET /v1/me e usa essa identidade; um token configurado que falha interrompe o comando, nunca cai silenciosamente em um nome escrito). A identidade verificada manda; o que foi escrito em --approver é salvo como declared_approver. Na lista de membros, pode-se colocar um e-mail ou a forma proveedor:login (github:luis-qa).
  • policy.require_verified_identity: true faz com que uma assinatura com identity_source: client seja registrada como declarada, mas não conte: o comando avisa ao assinar e check a rejeita com motivo identity_unverified.
  • Sem arquivo, ou com ambas as políticas em false, nada muda: nada é rejeitado e os registros carregam role: null. Um arquivo malformado é erro de configuração (saída 2), nunca uma omissão silenciosa.

Registros de auditoria

Cada varredura e cada substituição são registradas em .linebreak/audit/security.json (dependências) e .linebreak/audit/code.json (AI SAST) — o mesmo formato de documento versionado que as ferramentas LineBreak escrevem, carregando achados (ID do CVE, CVSS, link do aviso), mecanismo de varredura, carimbo de tempo, ator e a trilha de aprovação com o motivo + aprovador de cada substituição, o papel sob o qual foi feita e a fonte de identidade (client, vcs, governance). Quem afrouxou o portão, e quando, é em si auditável.

Riscos aceitos e tickets

Uma exceção registrada com override era para sempre: a pessoa que aceitou o risco sai e o risco fica. Desde a versão 1.13.3, cada aceitação pode (ou deve) expirar, e cada exceção também vive no gerenciador de tickets da equipe, para que a rastreabilidade fique na ferramenta deles e não apenas no LineBreak.

Expiração

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-DD ou --days N definem a data em que a aceitação deixa de valer. Vale até esse dia inclusive; no dia seguinte, o achado (ou o critério) volta a bloquear com motivo expired_risk. O relatório diz qual achado é, quem o aceitou, quando expirou e as duas saídas: renovar com outro override ou corrigir.

  • Com menos de 14 dias para expirar, scan, report e check avisam sem bloquear (expiring risk / expiring exception).

  • A política vive em .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)
    

    Sem este bloco, aceitações sem data continuam permitidas (o comportamento anterior). A mesma política se aplica a achados de segurança e a exceções de critérios.

  • Renovar é registrar um override novo sobre o mesmo alvo. A aceitação anterior não é sobrescrita: fica no histórico do artefato (.linebreak/audit/security.json, code.json, criteria.json) e a mais recente é a que vale. Cada entrada guarda expires, e ticket quando há gerenciador configurado.

  • Em --format json, scan/report trazem block_reasons (vulnerability, expired_risk) e, por detector, expired e expiring; check traz block_reasons, expired_overrides e expiring_overrides.

Tickets (Jira primeiro, GitHub Issues também)

# .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)

Credenciais por ambiente, nunca no repositório: JIRA_BASE_URL, JIRA_EMAIL e JIRA_API_TOKEN para Jira (API REST v2, funciona em Cloud e em Data Center); GITHUB_TOKEN para GitHub Issues.

O que o portão faz quando há um tickets: configurado:

MomentoO que acontece no gerenciador
override aceita um achado ou dispensa um critérioCria um ticket com o achado ou critério, quem aceitou, motivo, vencimento, repositório, commit e link para o registro de evidência. A chave fica na entrada de evidência (ticket: "SEC-123"). Se já existia, comenta e reabre (renovação).
scan ou check veem uma aceitação vencidaComenta o ticket e o reabre se estava fechado. Um único comentário por data de vencimento, mesmo que o gate rode em cada push.
O achado desaparece do scan, ou o critério passa sozinho em um check completoComenta e fecha o ticket.
Um scan na rama principal encontra um achado que não estava no escaneamento anterior salvoAbre um ticket de atenção (vulnerabilidade nova sobre código já liberado).

Detalhes que vale a pena saber:

  • O gerenciador nunca bloqueia o veredicto. Sem rede, credenciais erradas ou um projeto que rejeita a incidência: o veredicto é o que dizem os artefatos, um aviso é impresso, o erro fica na evidência (ticket_error na entrada) e a operação fica pendente em .linebreak/audit/tickets.json. linebreak-gate tickets sync tenta novamente o que está pendente (exit 0 quando não sobra nada; exit 1 se algo ainda estiver pendente).
  • A idempotência vive no gerenciador. Cada ticket leva as etiquetas linebreak-target-<hash> e linebreak-artifact-<security|code|criteria>; antes de criar, a compuerta busca por etiqueta. Assim, um runner de CI que não tem o livro local nunca duplica tickets, e pode fechar os de achados corrigidos.
  • "Novo" na rama principal é medido contra o artefato anterior em .linebreak/audit/ (o que estava comprometido antes de reescrevê-lo). O primeiro scan de um repositório não abre nada: não há linha de base. Só contam achados no nível do limiar fail_on ou acima, e que não tenham já um ticket. Em GitHub Actions, a rama é lida de GITHUB_REF_NAME; um pull request (GITHUB_HEAD_REF) nunca conta como rama principal.
  • Vale a pena comprometer .linebreak/audit/tickets.json junto com o override (assim como security.json), para que o histórico do ticket viaje com a evidência.

Preços

Grátis, para sempre: o scan de CVEs de dependências e todo o loop de spec — autoria, aprovação humana, servidor MCP e aplicação em CI. Sem chave, sem conta.

Pro — US$ 99/mês por equipe (preços): aprovações assinadas criptograficamente e à prova de adulteração (Ed25519, verificáveis offline), modo de aplicação com chave obrigatória e revisão de código com IA hospedada sem chave de API para gerenciar. Compre na página de preços — sua LINEBREAK_LICENSE_KEY chega por e-mail em segundos (é a entrada license-key da Action). Prefere usar sua própria chave de modelo? ANTHROPIC_API_KEY também habilita a revisão com IA; a revisão hospedada do Pro é o caminho de configuração zero.

O gate roda aberto por padrão: funciona sem chave e imprime um aviso quando nenhuma LINEBREAK_LICENSE_KEY está definida (suprimido para usuários BYOK). Isso é freemium — o scan de dependências roda grátis. Equipes que quiserem exigir uma chave Pro válida para o gate rodar podem optar pelo LINEBREAK_ENTITLEMENTS_PROVIDER=remote, que verifica a elegibilidade antes de qualquer scan e falha fechado em caso de chave ausente/inválida/revogada, plano errado ou serviço inacessível — bloqueando todo o gate, incluindo o scan de dependências.