Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ jobs:
- run: npm run check:known-symbols
- run: npm run check:route-guard-membership
- run: npm run check:test-discovery
- run: npm run check:tracked-artifacts
- run: npm run check:lockfile
- run: npm run check:licenses
- run: npm run check:docs-sync
- run: npm run typecheck:core
# typecheck:noimplicit:core is a forward-looking gate (noImplicitAny).
Expand Down Expand Up @@ -102,6 +105,43 @@ jobs:
path: .artifacts/quality-ratchet.md
if-no-files-found: warn

# Phase 7 extended quality gates — ADVISORY (continue-on-error). The 5 npm-based
# ratchets (dead-code/cognitive-complexity/type-coverage/circular-deps/bundle-size)
# run for real. The external scans (vuln/secrets/workflows) skip gracefully until the
# owner adds install steps for osv-scanner/gitleaks/actionlint/zizmor; CodeQL ratchet
# works via the runner's gh token. SonarQube needs SONAR_TOKEN/SONAR_HOST_URL secrets.
quality-extended:
name: Quality Gates (Extended, advisory)
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: ${{ env.CI_NODE_VERSION }}
cache: npm
- run: npm ci
- name: Dead-code (knip)
run: npm run check:dead-code
- name: Cognitive complexity (sonarjs)
run: npm run check:cognitive-complexity
- name: Type coverage
run: npm run check:type-coverage
- name: Circular deps (dpdm)
run: npm run check:circular-deps
- name: Bundle size
run: npm run check:bundle-size
- name: CodeQL alerts ratchet
run: npm run check:codeql-ratchet
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Secret scan (gitleaks; skips if absent)
run: npm run check:secrets
- name: Vulnerability ratchet (osv-scanner; skips if absent)
run: npm run check:vuln-ratchet
- name: Workflow lint (actionlint+zizmor; skips if absent)
run: npm run check:workflows

docs-sync-strict:
name: Docs Sync (Strict)
runs-on: ubuntu-latest
Expand Down Expand Up @@ -198,6 +238,11 @@ jobs:
# Anti test-masking: flag net assert removal / new assert.ok(true) in changed tests.
- name: Detect test-masking (weakened assertions)
run: npm run check:test-masking
# Evidence-in-PR-body (Hard Rule #18 mechanized): claims of "tests pass" must carry output.
- name: Require evidence in PR body
run: npm run check:pr-evidence
env:
PR_BODY: ${{ github.event.pull_request.body }}
- name: Publish PR test policy summary
if: always()
run: |
Expand Down
49 changes: 49 additions & 0 deletions .github/workflows/quality.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: Quality Gates

on:
pull_request:
branches: ["release/**"]
types: [opened, synchronize, reopened, ready_for_review]
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

env:
CI_NODE_VERSION: "24"

jobs:
fast-gates:
name: Fast Quality Gates
runs-on: ubuntu-latest
# tsx gates (known-symbols, route-guard-membership) import modules that open
# SQLite on load; provide DB env so a fresh CI DB initializes cleanly.
env:
JWT_SECRET: ci-lint-secret-with-sufficient-length-for-validation
API_KEY_SECRET: ci-lint-api-key-secret-long
DISABLE_SQLITE_AUTO_BACKUP: "true"
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: ${{ env.CI_NODE_VERSION }}
cache: npm
- run: npm ci
- run: npm run check:provider-consistency
- run: npm run check:fetch-targets
- run: npm run check:openapi-routes
- run: npm run check:docs-symbols
- run: npm run check:deps
- run: npm run check:file-size
- run: npm run check:error-helper
- run: npm run check:migration-numbering
- run: npm run check:public-creds
- run: npm run check:db-rules
- run: npm run check:known-symbols
- run: npm run check:route-guard-membership
- run: npm run check:test-discovery
- run: npm run check:any-budget:t11
68 changes: 68 additions & 0 deletions .gitleaks.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# .gitleaks.toml — Configuração do gitleaks para OmniRoute
# Task 7.18 — PLANO-QUALITY-GATES-FASE7.md
#
# Estende as regras padrão do gitleaks com allowlists específicas do projeto.
#
# INSTRUÇÕES para allowlists:
# Findings legítimos (fixtures de teste, creds OAuth públicas já cobertas pelo
# check-public-creds.mjs, valores de exemplo em docs) devem ser registrados abaixo
# em [[allowlist]] com um comentário explicativo obrigatório.
#
# NÃO adicione uma entrada de allowlist sem justificativa. Cada entrada é revisada
# a cada release (stale-enforcement). Regra: se o finding é um valor real que o
# sistema usa em produção, é um verdadeiro positivo — não allowliste, corrija.
#
# Referência: docs/security/PUBLIC_CREDS.md (credenciais OAuth públicas conhecidas)
# CLAUDE.md Hard Rule #11 (resolvePublicCred obrigatório)

# Usar as regras padrão do gitleaks (não declaramos [rules] aqui para herdar tudo)
# Para desabilitar uma regra específica, usar:
# [[rules]]
# id = "rule-id"
# [rules.allowlist]
# description = "..."

# ---------------------------------------------------------------------------
# Allowlist global do projeto
# Entradas aqui são ignoradas em TODAS as varreduras.
# ---------------------------------------------------------------------------
[allowlist]
description = "OmniRoute project-level allowlist — fixtures, test vectors, public OAuth creds"

# Paths a ignorar completamente (node_modules, builds, etc.)
paths = [
'''node_modules''',
'''\.next''',
'''dist''',
'''\.git''',
'''coverage''',
'''\.nyc_output''',
]

# Commits específicos a ignorar (ex: commit que introduziu fixtures de teste)
# commits = []

# Regexes de stopwords — linhas que contêm estes padrões são ignoradas.
# Usar apenas para falsos positivos comprovados com justificativa abaixo.
# stopwords = []

# Regexes de targets (paths de arquivos) que podem ser allowlistados por regra.
# Ver [[allowlist]] por-regra abaixo para granularidade.

# ---------------------------------------------------------------------------
# Allowlist por-regra (adicionar conforme necessário durante o stale review)
# ---------------------------------------------------------------------------
#
# Exemplo (REMOVER / SUBSTITUIR por entradas reais quando necessário):
#
# [[rules]]
# # Allowlistar fixtures de teste que contêm tokens OAuth de exemplo/inválidos
# # Adicionado: 2026-06-13 | Revisar em: v3.9.0 | Justificativa: valores não-reais de teste
# id = "github-fine-grained-pat"
# [rules.allowlist]
# description = "Test fixture PATs — valores sintéticos, não funcionais"
# paths = [
# '''tests/fixtures/''',
# '''tests/unit/''',
# ]
#
16 changes: 10 additions & 6 deletions .husky/pre-push
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
#!/usr/bin/env sh
#if ! command -v npm >/dev/null 2>&1; then
# echo "⚠️ npm not found in PATH — skipping pre-push hooks"
# echo " Run 'npm test' manually before pushing."
# exit 0
#fi
# .husky/pre-push — fast deterministic gates (<10s total)
# Intentionally excludes test:unit (slow; covered by CI pre-push remote run).
# Activated: 2026-06-13 (6A.12 — replaced commented-out test:unit stub)

#npm run test:unit
if ! command -v npm >/dev/null 2>&1; then
echo "⚠️ npm not found in PATH — skipping pre-push hooks"
echo " Run 'npm run check:any-budget:t11 && npm run check:tracked-artifacts' manually before pushing."
exit 0
fi

npm run check:any-budget:t11 && npm run check:tracked-artifacts
85 changes: 85 additions & 0 deletions .license-allowlist.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
{
"_comment": "SPDX license allowlist for OmniRoute (MIT project). Task 7.20 / PLANO-QUALITY-GATES-FASE7.md.",
"_policy": "Production deps with a license outside 'allowed' and not in 'exceptions' fail the gate. devDeps get an advisory warning only.",

"allowed": [
"MIT",
"Apache-2.0",
"BSD-2-Clause",
"BSD-3-Clause",
"ISC",
"0BSD",
"CC0-1.0",
"Unlicense",
"Python-2.0",
"BlueOak-1.0.0",
"Artistic-2.0",
"Zlib",
"X11",
"WTFPL",
"PSF-2.0",
"CC-BY-3.0"
],

"allowedExpressions": [
"MIT*",
"MIT AND ISC",
"Apache-2.0 AND MIT",
"(MIT OR Apache-2.0)",
"(MIT OR CC0-1.0)",
"(MIT OR WTFPL)",
"(BSD-2-Clause OR MIT OR Apache-2.0)",
"(MPL-2.0 OR Apache-2.0)"
],

"exceptions": {
"@img/sharp-libvips-linux-x64": {
"license": "LGPL-3.0-or-later",
"justification": "Prebuilt native binary (libvips shared library) pulled in transitively by the 'sharp' package (optional dep of next/transformers). LGPL-3.0 on dynamically-linked native code allows use in non-GPL applications as long as the user can replace the library — satisfied here because this is a pre-built shared-object (.so) binary distributed by the sharp project, not source linked into OmniRoute code. OmniRoute does not modify or statically link libvips.",
"risk": "low",
"reviewAt": "v4.0.0"
},
"@img/sharp-libvips-linuxmusl-x64": {
"license": "LGPL-3.0-or-later",
"justification": "Same rationale as @img/sharp-libvips-linux-x64 — musl variant of the same prebuilt libvips shared library. Dynamically linked, not modified, not statically bundled.",
"risk": "low",
"reviewAt": "v4.0.0"
},
"lightningcss": {
"license": "MPL-2.0",
"justification": "MPL-2.0 is a weak, file-level copyleft: only modifications to MPL-licensed files must be released under MPL. OmniRoute does not modify lightningcss source. It is pulled in transitively as a runtime dep of vite (via monaco-editor, a production dep). MPL-2.0 is explicitly compatible with Apache/MIT combinations per the Mozilla FAQ and is routinely used in Node.js production stacks. The OmniRoute codebase remains MIT.",
"risk": "low",
"reviewAt": "v4.0.0"
},
"lightningcss-linux-x64-gnu": {
"license": "MPL-2.0",
"justification": "Platform-specific native binding for lightningcss. Same MPL-2.0 rationale as lightningcss itself: file-level copyleft, not modified by OmniRoute, compatible with MIT project.",
"risk": "low",
"reviewAt": "v4.0.0"
},
"lightningcss-linux-x64-musl": {
"license": "MPL-2.0",
"justification": "Musl variant of lightningcss native binding. Same MPL-2.0 rationale.",
"risk": "low",
"reviewAt": "v4.0.0"
},
"dompurify": {
"license": "(MPL-2.0 OR Apache-2.0)",
"justification": "dompurify is dual-licensed: MPL-2.0 OR Apache-2.0. The Apache-2.0 option is permissive and compatible with MIT. OmniRoute uses it indirectly via monaco-editor and mermaid (UI rendering). The gate expression matcher already accepts (MPL-2.0 OR Apache-2.0) as an allowed expression, but this exception is kept for explicitness and audit trail.",
"risk": "none",
"reviewAt": "v4.0.0"
},
"caniuse-lite": {
"license": "CC-BY-4.0",
"justification": "CC-BY-4.0 applies to the caniuse browser-support data (a dataset, not code). The Creative Commons Attribution license requires attribution when distributing — OmniRoute does not distribute caniuse-lite data directly to end users; it is consumed by browserslist/PostCSS at build time to generate CSS compatibility info. This is a widely accepted pattern in the Node.js ecosystem (caniuse-lite is in millions of projects). Attribution is satisfied by keeping the package in node_modules with its original license file.",
"risk": "low",
"reviewAt": "v4.0.0"
},
"tls-client-node": {
"license": "Custom: LICENSE (Apache-2.0 + Commons Clause)",
"justification": "TODO: revisar — tls-client-node uses Apache-2.0 with a 'Commons Clause' addendum that restricts 'Selling' the software (i.e., offering it as a hosted/commercial service whose value derives substantially from tls-client-node). OmniRoute is an open-source proxy; however if deployed as a paid SaaS/hosting service, this restriction could apply. The package is used by grokTlsClient.ts for Grok TLS fingerprinting. RISK: medium — legal review recommended before commercial deployment. Alternatives: consider replacing with a native TLS fingerprinting approach or a truly permissive library.",
"risk": "medium",
"reviewAt": "v3.9.0"
}
}
}
22 changes: 22 additions & 0 deletions .size-limit.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[
{
"name": "CLI entry (omniroute.mjs)",
"path": "bin/omniroute.mjs",
"limit": "15 KB"
},
{
"name": "MCP server entry (mcp-server.mjs)",
"path": "bin/mcp-server.mjs",
"limit": "5 KB"
},
{
"name": "Node runtime support (nodeRuntimeSupport.mjs)",
"path": "bin/nodeRuntimeSupport.mjs",
"limit": "8 KB"
},
{
"name": "Reset password entry (reset-password.mjs)",
"path": "bin/reset-password.mjs",
"limit": "6 KB"
}
]
51 changes: 51 additions & 0 deletions .zizmor.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# .zizmor.yml — zizmor security audit configuration
# https://github.com/woodruffw/zizmor
#
# zizmor audits GitHub Actions workflows for security anti-patterns:
# • Unpinned third-party actions (supply-chain risk)
# • Script injection via ${{ github.* }} in run: steps
# • pull_request_target misuse (allows untrusted code to reach secrets)
# • Excessive permissions / overly broad GITHUB_TOKEN scopes
# • Cache poisoning via actions/cache with unguarded keys
# • … 20+ additional audits
#
# MOTIVATION (2026-03 incident): the trivy-action/LiteLLM supply-chain attack
# exploited exactly the kind of pull_request_target misconfiguration that zizmor
# detects statically. OmniRoute's npm/Docker/Electron publish workflows are high-
# value targets that warrant proactive audit.
#
# BASELINE STRATEGY: this file starts with an EMPTY ignores list. As zizmor is
# first run against the existing workflows, findings will be reviewed:
# • True positives → fix the workflow (preferred)
# • Accepted risk → add an entry below with a justification comment
# This enforces the same stale-enforcement discipline as other allowlists in
# this project: every ignore requires human sign-off and is subject to review
# at each release cycle.
#
# RATCHET: zizmorFindings metric in quality-baseline.json (direction: down)
# starts advisory; current baseline is frozen at first green run; new findings
# must be remediated or explicitly ignored here.

# ── Global settings ──────────────────────────────────────────────────────────
# Uncomment to pin to a specific minimum severity level.
# min-severity: low # low | medium | high | critical (default: low = all)

# ── Per-finding ignores ──────────────────────────────────────────────────────
# Format:
# ignores:
# - id: <zizmor-audit-id> # e.g. "unpinned-uses", "script-injection"
# reason: "<justification>"
# # Optional: scope to a specific workflow or step
# # workflow: .github/workflows/ci.yml
#
# Example (do not uncomment without real justification):
#
# ignores:
# - id: unpinned-uses
# reason: >
# actions/checkout@v6 is pinned at the major-version tag intentionally:
# GitHub-managed first-party action; SHA pinning buys little against a
# GitHub-side compromise and adds significant maintenance burden.
# workflow: .github/workflows/ci.yml

ignores: []
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,7 @@ For any non-trivial change, read the matching deep-dive first:
| Tunnels | [`docs/ops/TUNNELS_GUIDE.md`](docs/ops/TUNNELS_GUIDE.md) |
| Electron desktop | [`docs/guides/ELECTRON_GUIDE.md`](docs/guides/ELECTRON_GUIDE.md) |
| Release flow | [`docs/ops/RELEASE_CHECKLIST.md`](docs/ops/RELEASE_CHECKLIST.md) |
| Quality gates (35 gates, allowlist policy) | [`docs/architecture/QUALITY_GATES.md`](docs/architecture/QUALITY_GATES.md) |

---

Expand Down
Loading