9 Commits
Author SHA1 Message Date
Lucas Rayan 659701b2d4 chore: padroniza arquitetura Ciberfauna, logo na raiz, hooks de verificação e CI multi-arch no runner ARM
CI / Auditoria de dependências (push) Successful in 12s
CI / Testes (Linux) (push) Successful in 1m34s
Release / Cria a release (push) Successful in 5s
Release / App desktop (linux-aarch64) (push) Successful in 7m32s
CI / Formatação e clippy (push) Successful in 1m9s
CI / Compila o app desktop (Linux) (push) Successful in 1m36s
Release / CLI (linux-aarch64) (push) Successful in 1m23s
Release / CLI (linux-x86_64) (push) Successful in 1m40s
Release / App desktop (linux-x86_64) (push) Failing after 9m5s
Release / App desktop (windows) (push) Failing after 6m20s
2026-10-30 17:38:58 -03:00
Lucas RayanandClaude Sonnet 5.5 30a086a808 release 1.0.3: correct version labels, Windows build fixed
Release / Cria a release (push) Successful in 8s
Release / CLI (linux-aarch64) (push) Successful in 48s
Release / CLI (linux-x86_64) (push) Successful in 1m2s
CI / Formatação e clippy (push) Successful in 20s
CI / Testes (Linux) (push) Successful in 23s
CI / Auditoria de dependências (push) Successful in 13s
CI / Compila o app desktop (Linux) (push) Successful in 1m18s
Release / App desktop (windows) (push) Failing after 3m27s
Release / App desktop (linux) (push) Successful in 5m4s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-30 14:41:00 -03:00
Lucas RayanandClaude Sonnet 5.5 833abc6308 fix: gate deauth_directional to Linux
CI / Formatação e clippy (push) Successful in 24s
CI / Testes (Linux) (push) Successful in 25s
CI / Auditoria de dependências (push) Canceled after 3s
CI / Compila o app desktop (Linux) (push) Canceled after 3s
The non-Linux stub at the bottom of the file duplicated a definition that had
no #[cfg(target_os = "linux")] of its own — it inherited none from the
unrelated #[allow] above it. Every non-Linux build (the desktop app's Windows
CI job included) failed with a duplicate-definition error and unresolved
Linux-only imports inside the always-compiled body.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-30 14:40:29 -03:00
Lucas RayanandClaude Sonnet 5.5 3fc3febff3 release 1.0.2: fix the desktop app build that v1.0.1 shipped broken
Release / App desktop (linux) (push) Successful in 4m48s
Release / CLI (linux-aarch64) (push) Successful in 1m4s
Release / CLI (linux-x86_64) (push) Successful in 1m6s
Release / App desktop (windows) (push) Failing after 3m7s
Release / Cria a release (push) Successful in 4s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-30 14:31:47 -03:00
Lucas RayanandClaude Sonnet 5.5 827ef0c2c0 ci: find the desktop bundles in CARGO_TARGET_DIR
CI / Formatação e clippy (push) Successful in 25s
CI / Testes (Linux) (push) Successful in 25s
CI / Auditoria de dependências (push) Successful in 10s
CI / Compila o app desktop (Linux) (push) Successful in 2m3s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-30 14:02:19 -03:00
Lucas RayanandClaude Sonnet 5.5 c7638a2f4e ci: build the desktop app (Linux and Windows) automatically on release
Release / Cria a release (push) Successful in 4s
Release / App desktop (linux) (push) Failing after 8m1s
Release / CLI (linux-aarch64) (push) Successful in 51s
Release / CLI (linux-x86_64) (push) Successful in 1m13s
Release / App desktop (windows) (push) Failing after 7m15s
Mirrors Guará's release workflow. No signing key needed here: Sabiá's updater
is the custom module in sabia-core, not the Tauri plugin.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-30 13:42:35 -03:00
Lucas RayanandClaude Sonnet 5.5 d885d92643 release 1.0.1: update from Gitea, Linux-only CLI, desktop app for Linux and Windows
CI / Formatação e clippy (push) Successful in 49s
CI / Testes (Linux) (push) Successful in 49s
CI / Auditoria de dependências (push) Successful in 12s
CI / Compila o app desktop (Linux) (push) Successful in 1m12s
The updater reads the latest release from Gitea. update::apply refuses to
overwrite anything but the CLI. Release workflow builds the Linux CLI;
scripts/build-gui.sh builds the Linux and Windows desktop app. Docs say Linux
and Windows only.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-30 13:17:32 -03:00
Lucas RayanandClaude Sonnet 5.5 0e31d28342 ci: create a Gitea release when a v* tag is pushed
CI / Formatação e clippy (push) Successful in 26s
CI / Testes (Linux) (push) Successful in 22s
CI / Compila o app desktop (Linux) (push) Successful in 4m23s
CI / Auditoria de dependências (push) Successful in 5m31s
Notes come from the matching CHANGELOG.md section. The macOS and Windows
binaries the old release workflow built need runners this Gitea does not have.
The CI also ignores .wolf, .claude and the release files.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-30 12:37:01 -03:00
Lucas RayanandClaude Sonnet 5.5 9bcdd6c139 ci: move the CI to Gitea Actions and cache what is rebuilt
CI / Testes (Linux) (push) Successful in 1m1s
CI / Formatação e clippy (push) Successful in 1m43s
CI / Auditoria de dependências (push) Successful in 5m19s
CI / Compila o app desktop (Linux) (push) Successful in 4m59s
Linux-only runner, so the Windows and macOS legs and GitHub's audit action are
gone. The Rust toolchain, cargo registry and target directories live on the
runner's persistent toolcache volume. release.yml stays under .github and is
not run by Gitea.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-30 10:45:46 -03:00
103 changed files with 3305 additions and 324 deletions

No files matched your search

File renamed without changes.
File renamed without changes.
+88
View File
@@ -0,0 +1,88 @@
name: CI
on:
# Mudança só de documentação ou estado de ferramentas não precisa dos jobs de CI.
push:
branches: [main]
paths-ignore: ['**.md', 'docs/**', 'LICENSE*', '.gitignore', '.githooks/**', '.wolf/**', '.claude/**', '.opencode/**', '.codex/**', '.cursor/**', '.superpowers/**', '.superset/**', '.gitea/ISSUE_TEMPLATE/**', 'scripts/release-notes.sh', 'scripts/release-upload.sh', '.gitea/workflows/release.yml']
pull_request:
paths-ignore: ['**.md', 'docs/**', 'LICENSE*', '.gitignore', '.githooks/**', '.wolf/**', '.claude/**', '.opencode/**', '.codex/**', '.cursor/**', '.superpowers/**', '.superset/**', '.gitea/ISSUE_TEMPLATE/**', 'scripts/release-notes.sh', 'scripts/release-upload.sh', '.gitea/workflows/release.yml']
concurrency:
group: ci-${{ gitea.ref }}
cancel-in-progress: true
env:
CARGO_TERM_COLOR: always
RUSTFLAGS: -D warnings
# O runner mantém /opt/hostedtoolcache num volume que sobrevive entre jobs:
# a toolchain do rustup, o registry do cargo, caches de npm/apt e o diretório
# target ficam ali, então nada disso é baixado ou recompilado do zero. Cada job
# usa o seu próprio target (flags diferentes invalidariam o de outro job).
CARGO_HOME: /opt/hostedtoolcache/cargo
RUSTUP_HOME: /opt/hostedtoolcache/rustup
NPM_CONFIG_CACHE: /opt/hostedtoolcache/npm-cache
jobs:
lint:
name: Formatação e clippy
runs-on: ubuntu-latest
env:
CARGO_TARGET_DIR: /opt/hostedtoolcache/cargo-target/sabia-lint
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- uses: actions/setup-node@v7
with:
node-version: 22
- name: Cobertura da tradução da interface
run: node scripts/check-ui-i18n.mjs
- run: cargo fmt --all -- --check
- run: cargo clippy --workspace --all-targets
# Windows e macOS saem: o Gitea aqui tem um único runner, Linux arm64.
test:
name: Testes (Linux)
runs-on: ubuntu-latest
env:
CARGO_TARGET_DIR: /opt/hostedtoolcache/cargo-target/sabia-test
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
- run: cargo test --workspace
audit:
name: Auditoria de dependências
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
# rustsec/audit-check depends on GitHub's checks API. cargo-audit itself
# needs nothing but the advisory database, and installing it once into the
# persistent CARGO_HOME makes every later run skip the compile.
- name: cargo audit
run: |
command -v cargo-audit >/dev/null || cargo install cargo-audit --locked
cargo audit
gui:
name: Compila o app desktop (Linux)
runs-on: ubuntu-latest
env:
CARGO_TARGET_DIR: /opt/hostedtoolcache/cargo-target/sabia-gui
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
- uses: actions/setup-node@v7
with:
node-version: 22
- name: Dependências do Tauri
timeout-minutes: 25
run: scripts/apt-install.sh /opt/hostedtoolcache/apt-cache/tauri-native libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
- name: Gera os ícones
run: npx --yes @tauri-apps/cli@2 icon apps/gui/src-tauri/icons/source.png -o apps/gui/src-tauri/icons
- run: cargo check --manifest-path apps/gui/src-tauri/Cargo.toml
env:
RUSTFLAGS: ""
+131
View File
@@ -0,0 +1,131 @@
name: Release
# Quando uma tag v* é enviada: cria a entrada em "Releases" com as notas do
# CHANGELOG.md e anexa o CLI compilado (scripts/package-cli.sh), com um .sha256 ao
# lado de cada pacote. É o que o atualizador (`sabia update`) baixa.
#
# O CLI é só para Linux (x86_64 e aarch64), compilado no runner Linux; o x86_64
# por compilação cruzada. Windows e macOS têm só o app desktop.
#
# Assinatura do atualizador (opcional): SABIA_TAURI_SIGNING_PRIVATE_KEY e
# SABIA_TAURI_SIGNING_PRIVATE_KEY_PASSWORD, com os nomes antigos sem prefixo como
# fallback. Sem chave, o build continua e apenas não gera .sig.
on:
push:
tags: ['v*']
env:
CARGO_TERM_COLOR: always
# Toolchain, caches e registry do cargo ficam no volume que sobrevive entre jobs.
CARGO_HOME: /opt/hostedtoolcache/cargo
RUSTUP_HOME: /opt/hostedtoolcache/rustup
NPM_CONFIG_CACHE: /opt/hostedtoolcache/npm-cache
XWIN_CACHE_DIR: /opt/hostedtoolcache/xwin-cache
jobs:
release:
name: Cria a release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Release
env:
TAG: ${{ gitea.ref_name }}
TOKEN: ${{ gitea.token }}
API: ${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}
run: |
# Uma execução repetida não recria a release.
if curl -fsS -H "Authorization: token $TOKEN" "$API/releases/tags/$TAG" >/dev/null 2>&1; then
echo "a release $TAG já existe"; exit 0
fi
scripts/release-notes.sh "$TAG" > notes.md
pre=false; case "$TAG" in *-*) pre=true ;; esac
jq -n --arg tag "$TAG" --arg name "Sabiá ${TAG#v}" --rawfile body notes.md --argjson pre "$pre" \
'{tag_name:$tag, name:$name, body:$body, prerelease:$pre}' > release.json
curl -fsS -X POST "$API/releases" -H "Authorization: token $TOKEN" \
-H 'Content-Type: application/json' -d @release.json >/dev/null
cli:
name: CLI (${{ matrix.platform }})
needs: release
runs-on: ubuntu-latest
env:
CARGO_TARGET_DIR: /opt/hostedtoolcache/cargo-target/sabia-release-cli-${{ matrix.platform }}
strategy:
fail-fast: false
matrix:
include:
- { platform: linux-aarch64, target: aarch64-unknown-linux-gnu, apt: "" }
- { platform: linux-x86_64, target: x86_64-unknown-linux-gnu, apt: "gcc-x86-64-linux-gnu" }
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Linker e ferramentas desta plataforma
if: ${{ matrix.apt != '' }}
run: scripts/apt-install.sh /opt/hostedtoolcache/apt-cache/cli-x86_64 ${{ matrix.apt }}
- name: Compila e empacota
run: scripts/package-cli.sh "${{ gitea.ref_name }}" "${{ matrix.platform }}" "${{ matrix.target }}" | tee packages.txt
- name: Anexa à release
env:
TOKEN: ${{ gitea.token }}
API: ${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}
run: scripts/release-upload.sh "${{ gitea.ref_name }}" $(cat packages.txt)
gui:
name: App desktop (${{ matrix.platform }})
needs: release
runs-on: ubuntu-latest
env:
CARGO_TARGET_DIR: /opt/hostedtoolcache/cargo-target/sabia-release-gui-${{ matrix.platform }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.SABIA_TAURI_SIGNING_PRIVATE_KEY || secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.SABIA_TAURI_SIGNING_PRIVATE_KEY_PASSWORD || secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
strategy:
fail-fast: false
matrix:
platform: [linux-aarch64, linux-x86_64, windows]
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
- uses: actions/setup-node@v7
with:
node-version: 22
- name: Dependências do Tauri (Linux aarch64)
if: matrix.platform == 'linux-aarch64'
timeout-minutes: 25
run: scripts/apt-install.sh /opt/hostedtoolcache/apt-cache/tauri-native libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
- name: Dependências do Tauri (Linux x86_64)
if: matrix.platform == 'linux-x86_64'
timeout-minutes: 25
run: |
if [ "$(uname -m)" = aarch64 ]; then
dpkg --add-architecture amd64
. /etc/os-release
if [ "${ID:-}" = ubuntu ]; then
if [ -f /etc/apt/sources.list.d/ubuntu.sources ]; then
grep -q '^Architectures:' /etc/apt/sources.list.d/ubuntu.sources || sed -i '/^Types: deb/a Architectures: arm64' /etc/apt/sources.list.d/ubuntu.sources
printf 'Types: deb\nURIs: http://archive.ubuntu.com/ubuntu/\nSuites: %s %s-updates %s-security\nComponents: main universe\nArchitectures: amd64\nSigned-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg\n' "$VERSION_CODENAME" "$VERSION_CODENAME" "$VERSION_CODENAME" >/etc/apt/sources.list.d/amd64.sources
elif [ -f /etc/apt/sources.list ]; then
sed -i 's/^deb http/deb [arch=arm64] http/' /etc/apt/sources.list
printf 'deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ %s main universe\ndeb [arch=amd64] http://archive.ubuntu.com/ubuntu/ %s-updates main universe\ndeb [arch=amd64] http://archive.ubuntu.com/ubuntu/ %s-security main universe\n' "$VERSION_CODENAME" "$VERSION_CODENAME" "$VERSION_CODENAME" >/etc/apt/sources.list.d/amd64.list
fi
fi
scripts/apt-install.sh /opt/hostedtoolcache/apt-cache/tauri-amd64 gcc-x86-64-linux-gnu g++-x86-64-linux-gnu pkg-config patchelf file squashfs-tools qemu-user libglib2.0-bin libwebkit2gtk-4.1-dev:amd64 libayatana-appindicator3-dev:amd64 librsvg2-dev:amd64
else
scripts/apt-install.sh /opt/hostedtoolcache/apt-cache/tauri-native libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
fi
- name: Ferramentas para compilação cruzada (Windows)
if: matrix.platform == 'windows'
run: |
scripts/apt-install.sh /opt/hostedtoolcache/apt-cache/tauri-win nsis lld llvm clang
command -v cargo-xwin >/dev/null || cargo install cargo-xwin --locked
- name: Compila e empacota
run: scripts/build-gui.sh "${{ matrix.platform }}"
- name: Anexa à release
env:
TOKEN: ${{ gitea.token }}
API: ${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}
run: scripts/release-upload.sh "${{ gitea.ref_name }}" dist-gui/*
+3
View File
@@ -0,0 +1,3 @@
#!/usr/bin/env bash
# Antes de cada push: a verificação rápida. Ignore com `git push --no-verify` (use com parcimônia).
exec "$(git rev-parse --show-toplevel)/scripts/verificar.sh"
-83
View File
@@ -1,83 +0,0 @@
name: CI
on:
# Mudança só de documentação não precisa de 6 jobs em 3 sistemas.
push:
branches: [main]
paths-ignore: ['**.md', 'docs/**', 'LICENSE*', '.gitignore', '.githooks/**']
pull_request:
paths-ignore: ['**.md', 'docs/**', 'LICENSE*', '.gitignore', '.githooks/**']
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
env:
CARGO_TERM_COLOR: always
RUSTFLAGS: -D warnings
jobs:
lint:
name: Formatação e clippy
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- uses: Swatinem/rust-cache@v2
- uses: actions/setup-node@v7
with:
node-version: 22
- name: Cobertura da tradução da interface
run: node scripts/check-ui-i18n.mjs
- run: cargo fmt --all -- --check
- run: cargo clippy --workspace --all-targets
test:
name: Testes (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-24.04, windows-latest, macos-latest]
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- run: cargo test --workspace
audit:
name: Auditoria de dependências
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v7
- uses: rustsec/audit-check@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
gui:
name: Compila o app desktop (Linux)
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
with:
workspaces: apps/gui/src-tauri
- uses: actions/setup-node@v7
with:
node-version: 22
- name: Cache dos pacotes do Tauri (.deb)
uses: actions/cache@v6
with:
path: ~/apt-cache
key: apt-tauri-${{ runner.os }}-${{ runner.arch }}-${{ env.ImageVersion }}-${{ hashFiles('scripts/apt-install.sh') }}
- name: Dependências do Tauri
timeout-minutes: 25
run: scripts/apt-install.sh ~/apt-cache libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
- name: Gera os ícones
run: npx --yes @tauri-apps/cli@2 icon apps/gui/src-tauri/icons/source.png -o apps/gui/src-tauri/icons
- run: cargo check --manifest-path apps/gui/src-tauri/Cargo.toml
env:
RUSTFLAGS: ""
-151
View File
@@ -1,151 +0,0 @@
name: Release
# Publica uma release pública ao empurrar uma tag vX.Y.Z (ex.: git tag v0.1.0 && git push --tags)
on:
push:
tags: ["v*.*.*"]
permissions:
contents: write
env:
CARGO_TERM_COLOR: always
jobs:
create-release:
name: Cria a release
runs-on: ubuntu-24.04
outputs:
release_id: ${{ steps.rel.outputs.result }}
steps:
- uses: actions/checkout@v7
- name: Notas da versão (do CHANGELOG)
id: notes
run: |
v="${GITHUB_REF_NAME#v}"
v="${v%%-*}"
awk -v v="$v" '$0 ~ "^## \\[" v "\\]" {f=1; next} /^## \[/ {f=0} f' CHANGELOG.md > notes.md
[ -s notes.md ] || echo "Veja o CHANGELOG." > notes.md
- uses: actions/github-script@v9
id: rel
with:
script: |
const fs = require('fs');
const tag = context.ref.replace('refs/tags/', '');
const r = await github.rest.repos.createRelease({
owner: context.repo.owner, repo: context.repo.repo, tag_name: tag,
name: `Sabiá ${tag}`, body: fs.readFileSync('notes.md', 'utf8'),
draft: true, prerelease: tag.includes('-'),
});
return r.data.id;
cli:
name: CLI (${{ matrix.name }})
needs: create-release
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- { name: linux-x86_64, os: ubuntu-24.04, target: x86_64-unknown-linux-gnu }
- { name: linux-aarch64, os: ubuntu-24.04-arm, target: aarch64-unknown-linux-gnu }
- { name: macos-aarch64, os: macos-latest, target: aarch64-apple-darwin }
- { name: macos-x86_64, os: macos-latest, target: x86_64-apple-darwin }
- { name: windows-x86_64, os: windows-latest, target: x86_64-pc-windows-msvc }
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- uses: Swatinem/rust-cache@v2
- run: cargo build --release --locked -p sabia-cli --target ${{ matrix.target }}
- name: Empacota
shell: bash
run: |
name="sabia-${GITHUB_REF_NAME}-${{ matrix.name }}"
mkdir "$name"
if [ "$RUNNER_OS" = "Windows" ]; then
cp target/${{ matrix.target }}/release/sabia.exe "$name/"
else
cp target/${{ matrix.target }}/release/sabia "$name/"
fi
cp README.md LICENSE-MIT LICENSE-APACHE "$name/"
if [ "$RUNNER_OS" = "Windows" ]; then
7z a "$name.zip" "$name" >/dev/null; asset="$name.zip"
else
tar czf "$name.tar.gz" "$name"; asset="$name.tar.gz"
fi
if command -v sha256sum >/dev/null; then sha256sum "$asset" > "$asset.sha256"; else shasum -a 256 "$asset" > "$asset.sha256"; fi
echo "ASSET=$asset" >> "$GITHUB_ENV"
- name: Envia para a release
shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: gh release upload "$GITHUB_REF_NAME" "$ASSET" "$ASSET.sha256" --clobber
gui:
name: App desktop (${{ matrix.name }})
needs: create-release
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- { name: linux-x86_64, os: ubuntu-24.04, args: "" }
- { name: macos-aarch64, os: macos-latest, args: "--target aarch64-apple-darwin" }
- { name: macos-x86_64, os: macos-latest, args: "--target x86_64-apple-darwin" }
- { name: windows-x86_64, os: windows-latest, args: "" }
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ contains(matrix.args, 'apple') && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
- uses: Swatinem/rust-cache@v2
with:
workspaces: apps/gui/src-tauri
- uses: actions/setup-node@v7
with:
node-version: 22
- name: Cache dos pacotes do Tauri (.deb)
if: runner.os == 'Linux'
uses: actions/cache@v6
with:
path: ~/apt-cache
key: apt-tauri-${{ runner.os }}-${{ runner.arch }}-${{ env.ImageVersion }}-${{ hashFiles('scripts/apt-install.sh') }}
- name: Dependências do Tauri (Linux)
if: runner.os == 'Linux'
timeout-minutes: 25
run: scripts/apt-install.sh ~/apt-cache libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
- name: Gera os ícones
run: npx --yes @tauri-apps/cli@2 icon apps/gui/src-tauri/icons/source.png -o apps/gui/src-tauri/icons
- uses: tauri-apps/tauri-action@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
projectPath: apps/gui/src-tauri
releaseId: ${{ needs.create-release.outputs.release_id }}
args: ${{ matrix.args }}
publish:
name: Publica a release
needs: [create-release, cli, gui]
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v7
- name: Notas da versão (do CHANGELOG)
run: |
v="${GITHUB_REF_NAME#v}"
v="${v%%-*}"
awk -v v="$v" '$0 ~ "^## \\[" v "\\]" {f=1; next} /^## \[/ {f=0} f' CHANGELOG.md > notes.md
[ -s notes.md ] || echo "Veja o CHANGELOG." > notes.md
- uses: actions/github-script@v9
with:
script: |
const fs = require('fs');
await github.rest.repos.updateRelease({
owner: context.repo.owner, repo: context.repo.repo,
release_id: ${{ needs.create-release.outputs.release_id }},
name: `Sabiá ${context.ref.replace('refs/tags/', '')}`,
body: fs.readFileSync('notes.md', 'utf8'),
draft: false,
});
+27 -5
View File
@@ -1,13 +1,21 @@
/target
# Build
target/
apps/gui/src-tauri/target/
node_modules/
*.img
*.guara-session.json
.DS_Store
**/*.rs.bk
# Capturas e estado local do Sabiá
*.pcap
*.cap
.sabia-audit.log
node_modules/
apps/gui/src-tauri/target
# Configuração e memória local de agentes de IA — não faz parte do
# produto, fica só na máquina de desenvolvimento.
# Logo copiada para a interface pelo build.rs
/apps/gui/ui/logo.png
# Configuração e memória local de agentes de IA — não faz parte do produto
.claude/
.wolf/
.opencode/
@@ -18,3 +26,17 @@ apps/gui/src-tauri/target
CLAUDE.md
AGENTS.md
GEMINI.md
# Variáveis de ambiente locais (o setup do Superset copia da raiz)
.env
.env.*
# Lixo local
*.eml
__pycache__/
*.pyc
# Pacotes gerados por scripts/package-cli.sh
/guara-v*
/sabia-v*
/dist-gui
+38
View File
@@ -4,6 +4,44 @@
Format based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow SemVer.
## [1.0.3] - 2026-10-30
### Fixed
- `deauth_directional` had no `#[cfg(target_os = "linux")]` of its own; every
non-Linux build failed with a duplicate-definition error and unresolved
Linux-only imports. `v1.0.1` and `v1.0.2` never got a working Windows
desktop app for this reason, and `v1.0.2`'s Linux package was mislabeled
`1.0.1` (the version in `Cargo.toml` was not bumped). Use `v1.0.3`.
## [1.0.2] - 2026-10-30
### Fixed
- The desktop app build on CI looked for its bundle under `target/`, but the
runner's Cargo target directory is elsewhere and persists between runs; the
Linux and Windows jobs failed before publishing anything. `v1.0.1`'s desktop
app was never published for this reason — use `v1.0.2`.
## [1.0.1] - 2026-10-30
The project moved from GitHub to Gitea, and the update check followed it.
### Changed
- **Updates now come from Gitea.** `sabia update` and the desktop app's "Update now"
read the latest release from `git.lucasrguerra.dev.br` instead of the GitHub API.
Versions up to 1.0.0 still look at GitHub, which is no longer reachable, so they
cannot find this release: download 1.0.1 once, and later updates are automatic.
- Release packages: the CLI for Linux x86_64 and aarch64 (built by the Gitea CI when
a `v*` tag is pushed, with a `.sha256` next to each package), and the desktop app
for Linux (`.AppImage`, `.deb`) and Windows (NSIS `.exe` installer, cross-compiled
from Linux by `scripts/build-gui.sh`). **There is no Windows CLI and no macOS build
any more**: the CLI is Linux-only, and macOS needs Apple's SDK and code signing,
which this project's CI cannot provide.
### Fixed
- `sabia update` refuses to run from the desktop app's executable (`sabia-gui`).
The release package contains the CLI, and replacing the app with it would have
broken the app; the app now points to the downloads page instead.
## [1.0.0] - 2026-10-24
First stable release: real-hardware validation across the whole attack surface, a much more complete cracking pipeline, two new attacks (WPS, Evil Twin), a redesigned report, and several reliability fixes found only by testing against live Wi-Fi networks.
+38
View File
@@ -4,6 +4,44 @@
Formato baseado em [Keep a Changelog](https://keepachangelog.com/pt-BR/1.1.0/); versões seguem SemVer.
## [1.0.3] - 2026-10-30
### Corrigido
- O `deauth_directional` não tinha o próprio `#[cfg(target_os = "linux")]`;
todo build fora do Linux falhava com uma definição duplicada e imports só de
Linux fora de escopo. Por isso a `v1.0.1` e a `v1.0.2` nunca tiveram um app
desktop funcional para Windows, e o pacote Linux da `v1.0.2` saiu rotulado
`1.0.1` (a versão no `Cargo.toml` não tinha sido atualizada). Use a `v1.0.3`.
## [1.0.2] - 2026-10-30
### Corrigido
- O build do app desktop no CI procurava o pacote em `target/`, mas o
diretório do Cargo no runner é outro e persiste entre execuções; os jobs de
Linux e Windows falhavam antes de publicar qualquer coisa. Por isso o app
desktop da `v1.0.1` nunca foi publicado — use a `v1.0.2`.
## [1.0.1] - 2026-10-30
O projeto saiu do GitHub para o Gitea, e a verificação de atualização foi junto.
### Alterado
- **As atualizações agora vêm do Gitea.** O `sabia update` e o "Atualizar agora" do app
desktop leem a última release em `git.lucasrguerra.dev.br`, e não mais a API do
GitHub. As versões até a 1.0.0 ainda olham o GitHub, que não responde mais, e por isso
não enxergam esta release: baixe a 1.0.1 uma vez, e as próximas atualizações são automáticas.
- Pacotes da release: o CLI para Linux x86_64 e aarch64 (compilado pelo CI do Gitea
quando uma tag `v*` é enviada, com um `.sha256` ao lado de cada pacote) e o app desktop
para Linux (`.AppImage`, `.deb`) e Windows (instalador NSIS `.exe`, compilado a partir
do Linux pelo `scripts/build-gui.sh`). **Não há mais CLI para Windows nem build para
macOS**: o CLI é só para Linux, e o macOS exige o SDK e a assinatura da Apple, que o CI
deste projeto não tem.
### Corrigido
- O `sabia update` se recusa a rodar a partir do executável do app desktop (`sabia-gui`).
O pacote da release contém o CLI, e trocar o app por ele o quebraria; o app passa a
apontar para a página de downloads.
## [1.0.0] - 2026-10-24
Primeira versão estável: validação em hardware real por toda a superfície de ataque, um pipeline de quebra bem mais completo, dois ataques novos (WPS, Evil Twin), relatório redesenhado e várias correções de confiabilidade que só apareceram testando contra redes Wi-Fi de verdade.
+11 -6
View File
@@ -11,15 +11,20 @@ Thanks for helping! Golden rules:
## Workflow
```bash
cargo fmt --all
cargo clippy --workspace --all-targets # as in CI (RUSTFLAGS=-D warnings)
cargo test --workspace
node scripts/check-ui-i18n.mjs # UI translation coverage
scripts/instalar-hooks.sh # once: runs the quick check before every push
scripts/verificar.sh # fast (seconds): fmt, clippy as in CI, i18n, scripts, workflows
scripts/verificar.sh --completo # adds cargo test --workspace and cargo audit
```
CI runs on Linux, Windows and macOS; run these before pushing — almost everything that fails CI shows up here in seconds. Documentation-only changes (`*.md`, `docs/`) are light on CI.
CI runs on Linux; run the check before pushing: almost everything that fails CI shows up here in seconds. Documentation-only changes (`*.md`, `docs/`) skip CI.
Live capture exists only on Linux (`AF_PACKET` + netlink); on Windows and macOS those paths compile but aren't exercised. Parsing and cracking tests run on all three, because they're offline.
Live capture exists only on Linux (`AF_PACKET` + netlink); the code behind Windows and macOS paths still compiles and is covered by `cargo check`.
## Releases
Pushing a `v*` tag makes the Gitea CI create the release (notes come from `CHANGELOG.md`, so update it first) and attach the Linux CLI (x86_64 and aarch64) and the desktop app for Linux (x86_64 and aarch64) and Windows.
There is no macOS build: it needs Apple's SDK and code signing, which a Linux runner cannot provide.
## Where to work
+11 -6
View File
@@ -11,15 +11,20 @@ Obrigado por ajudar! Regras de ouro:
## Fluxo
```bash
cargo fmt --all
cargo clippy --workspace --all-targets # como no CI (RUSTFLAGS=-D warnings)
cargo test --workspace
node scripts/check-ui-i18n.mjs # cobertura da tradução da interface
scripts/instalar-hooks.sh # uma vez: roda a verificação rápida antes de cada push
scripts/verificar.sh # rápido (segundos): fmt, clippy como no CI, tradução, scripts, workflows
scripts/verificar.sh --completo # inclui cargo test --workspace e cargo audit
```
O CI roda em Linux, Windows e macOS; rode esses comandos antes do push — quase tudo que faz o CI falhar aparece aqui em segundos. Mudanças só de documentação (`*.md`, `docs/`) são leves no CI.
O CI roda em Linux; rode a verificação antes do push: quase tudo que faz o CI falhar aparece aqui em segundos. Mudanças só de documentação (`*.md`, `docs/`) pulam o CI.
A captura ao vivo só existe no Linux (`AF_PACKET` + netlink); em Windows e macOS esses caminhos são compilados mas não exercitados. Testes de parsing e de quebra rodam nas três plataformas, porque são offline.
A captura ao vivo só existe no Linux (`AF_PACKET` + netlink); o código dos caminhos de Windows e macOS continua compilando e é coberto pelo `cargo check`.
## Releases
Enviar uma tag `v*` faz o CI do Gitea criar a release (as notas vêm do `CHANGELOG.md`, então atualize-o antes) e anexar o CLI para Linux (x86_64 e aarch64) e o app desktop para Linux (x86_64 e aarch64) e Windows.
Não há build para macOS: ele exige o SDK e a assinatura da Apple, que um runner Linux não tem.
## Onde mexer
Generated
+2 -2
View File
@@ -440,7 +440,7 @@ dependencies = [
[[package]]
name = "sabia-cli"
version = "1.0.0"
version = "1.0.3"
dependencies = [
"clap",
"ctrlc",
@@ -451,7 +451,7 @@ dependencies = [
[[package]]
name = "sabia-core"
version = "1.0.0"
version = "1.0.3"
dependencies = [
"aes",
"flate2",
+6 -2
View File
@@ -3,10 +3,10 @@ resolver = "2"
members = ["crates/sabia-core", "crates/sabia-cli"]
[workspace.package]
version = "1.0.0"
version = "1.0.3"
edition = "2021"
license = "MIT OR Apache-2.0"
repository = "https://github.com/lucasrguerra/sabia"
repository = "https://git.lucasrguerra.dev.br/lucasrguerra/sabia"
authors = ["Sabiá contributors"]
rust-version = "1.80"
@@ -15,3 +15,7 @@ lto = "thin"
codegen-units = 1
opt-level = 3
strip = true
panic = "abort"
[profile.dev.package."*"]
opt-level = 3
+13 -11
View File
@@ -1,8 +1,8 @@
<p align="center"><img src="apps/gui/ui/sabia.png" width="300" alt="Sabiá"></p>
<p align="center"><img src="logo.png" width="300" alt="Sabiá"></p>
<h1 align="center">Sabiá</h1>
<p align="center">Free and open Wi-Fi auditing.<br>Terminal and desktop app · live capture on Linux, offline analysis on Windows, Linux and macOS · authorized use.</p>
<p align="center">Free and open Wi-Fi auditing.<br>Terminal and desktop app · live capture on Linux, offline analysis on Windows too · authorized use.</p>
<p align="center"><a href="docs/cli.md">Terminal</a> · <a href="docs/interface.md">Interface</a> · <a href="docs/architecture.md">Architecture</a> · <a href="ROADMAP.md">Roadmap</a> · <a href="CHANGELOG.md">Changelog</a></p>
<p align="center"><a href="docs/cli.md">Terminal</a> · <a href="docs/interface.md">Interface</a> · <a href="docs/architecture.md">Architecture</a> · <a href="docs/support.md">Support</a> · <a href="ROADMAP.md">Roadmap</a> · <a href="CHANGELOG.md">Changelog</a></p>
<p align="center"><b>English</b> · <a href="README.pt-BR.md">Português</a></p>
@@ -37,17 +37,18 @@ Download the package for your system from **[Releases](../../releases)**:
| | CLI | Desktop app |
|---|---|---|
| Windows | `sabia-*-windows-x86_64.zip` | `.msi` / `.exe` installer |
| macOS | `sabia-*-macos-{aarch64,x86_64}.tar.gz` | `.dmg` |
| Windows | — | `.exe` installer |
| Linux | `sabia-*-linux-{x86_64,aarch64}.tar.gz` | `.AppImage` / `.deb` |
Each file has a `.sha256` next to it. Build from source: `cargo build --release -p sabia-cli` (Rust 1.80+).
The CLI is Linux-only; the desktop app ships for Linux and Windows. Each CLI
package has a `.sha256` next to it. Build from source: `cargo build --release
-p sabia-cli` (Rust 1.80+, Linux).
**Updates:** the desktop app checks GitHub on startup; when a new version exists, a button offers **Update now**: it downloads the right binary for your platform, verifies the **SHA-256** and swaps the running executable — no manual download. The check is a single request to the releases API and sends nothing of yours.
**Updates:** the desktop app checks Gitea on startup; when a new version exists, a button offers **Update now**. The CLI updates the same way (`sabia update`); it downloads the right binary for your platform, verifies the **SHA-256** and swaps the running executable — no manual download. The check is a single request to the releases API and sends nothing of yours.
> The binaries are **not signed yet**: Windows SmartScreen and macOS Gatekeeper will warn. Check the SHA-256.
> The binaries are **not signed yet**: Windows SmartScreen will warn. Check the SHA-256.
**Capture and monitor mode require Linux and root** (`CAP_NET_ADMIN`/`CAP_NET_RAW`): raw capture uses `AF_PACKET` and interface control uses netlink. **Offline `.pcap` analysis and cracking** also run on Windows and macOS. The desktop app elevates to administrator on its own at startup.
**Capture and monitor mode require Linux and root** — the only platform the CLI ships for (`CAP_NET_ADMIN`/`CAP_NET_RAW`): raw capture uses `AF_PACKET` and interface control uses netlink. **Offline `.pcap` analysis and cracking** also run on Windows and macOS. The desktop app elevates to administrator on its own at startup.
## Getting started
@@ -74,11 +75,11 @@ Desktop app (Tauri) with an interface sidebar, a capture panel with an animated
## Known limits
- **Capture is Linux-only**, with a card that supports **monitor mode** (and injection, for `deauth`). On Windows and macOS Sabiá only does `.pcap` analysis and offline cracking.
- **Capture is Linux-only**, with a card that supports **monitor mode** (and injection, for `deauth`); the CLI is not built for other platforms. On Windows Sabiá's desktop app only does `.pcap` analysis and offline cracking.
- **WPA3/SAE** does not fall to handshake dictionaries — cracking covers **WPA/WPA2-PSK** (4-way and PMKID).
- **5 and 6 GHz** depend on the card and the regulatory domain; some channels stay passive (no probing).
- **Evil Twin's self-signed certificate:** the Enterprise variant's TLS certificate isn't trusted by clients that validate the chain (common on updated corporate/MDM devices) — that's the correct defense working, not a bug.
- **Windows and macOS:** the code compiles and is tested in CI, but live capture does not run on those platforms.
- **Windows:** the code compiles and is tested in CI, but live capture does not run there; the desktop app does offline analysis only. There is no macOS build: Tauri needs the macOS SDK and code signing, both tied to Apple hardware and an Apple Developer account, which this project's CI (a single Linux runner) does not have.
## Where it's going
@@ -88,6 +89,7 @@ Summary of the [ROADMAP](ROADMAP.md):
|---|---|
| **Done** | Monitor mode via netlink (with NetworkManager recovery), 2.4/5/6 GHz scanning, 4-way and PMKID capture, CCMP decryption, dictionary/rules/mask cracking (WPA/WPA2-PSK), `.hc22000` export, `.pcap` read/write, authorized `deauth`/PMF/PMKID, WPS attack, Evil Twin (PSK + Enterprise), channel scoring and findings, paginated audit report, hash-chained audit trail, `doctor`, CLI + desktop app, one-click update |
| **Next** | GPU acceleration for cracking, capture on Windows (Npcap) |
| **Not planned** | A macOS build — see [Known limits](#known-limits) |
| **Later** | WPA3/SAE downgrade, validation on varied hardware, signed release binaries |
## Development
+13 -11
View File
@@ -1,8 +1,8 @@
<p align="center"><img src="apps/gui/ui/sabia.png" width="300" alt="Sabiá"></p>
<p align="center"><img src="logo.png" width="300" alt="Sabiá"></p>
<h1 align="center">Sabiá</h1>
<p align="center">Auditoria de Wi-Fi livre e gratuita.<br>Terminal e interface gráfica · captura no Linux, análise offline em Windows, Linux e macOS · uso autorizado.</p>
<p align="center">Auditoria de Wi-Fi livre e gratuita.<br>Terminal e interface gráfica · captura no Linux, análise offline também em Windows · uso autorizado.</p>
<p align="center"><a href="docs/pt-BR/cli.md">Terminal</a> · <a href="docs/pt-BR/interface.md">Interface</a> · <a href="docs/pt-BR/arquitetura.md">Arquitetura</a> · <a href="ROADMAP.pt-BR.md">Roadmap</a> · <a href="CHANGELOG.pt-BR.md">Changelog</a></p>
<p align="center"><a href="docs/pt-BR/cli.md">Terminal</a> · <a href="docs/pt-BR/interface.md">Interface</a> · <a href="docs/pt-BR/arquitetura.md">Arquitetura</a> · <a href="docs/pt-BR/suporte.md">Suporte</a> · <a href="ROADMAP.pt-BR.md">Roadmap</a> · <a href="CHANGELOG.pt-BR.md">Changelog</a></p>
<p align="center"><a href="README.md">English</a> · <b>Português</b></p>
@@ -37,17 +37,18 @@ Baixe o pacote do seu sistema em **[Releases](../../releases)**:
| | CLI | App gráfico |
|---|---|---|
| Windows | `sabia-*-windows-x86_64.zip` | instalador `.msi` / `.exe` |
| macOS | `sabia-*-macos-{aarch64,x86_64}.tar.gz` | `.dmg` |
| Windows | — | instalador `.exe` |
| Linux | `sabia-*-linux-{x86_64,aarch64}.tar.gz` | `.AppImage` / `.deb` |
Cada arquivo tem um `.sha256` ao lado. Compilar do código: `cargo build --release -p sabia-cli` (Rust 1.80+).
O CLI é só para Linux; o app desktop sai para Linux e Windows. Cada pacote do
CLI tem um `.sha256` ao lado. Compilar do código: `cargo build --release -p
sabia-cli` (Rust 1.80+, Linux).
**Atualizações:** o app desktop consulta o GitHub ao abrir; havendo versão nova, um botão oferece **Atualizar agora**: ele baixa o binário certo para a plataforma, confere o **SHA-256** e troca o executável em uso — você não precisa baixar nada na mão. A consulta é uma única requisição à API de releases e nada seu é enviado.
**Atualizações:** o app desktop consulta o Gitea ao abrir; havendo versão nova, um botão oferece **Atualizar agora**. O CLI atualiza do mesmo jeito (`sabia update`); ele baixa o binário certo para a plataforma, confere o **SHA-256** e troca o executável em uso — você não precisa baixar nada na mão. A consulta é uma única requisição à API de releases e nada seu é enviado.
> Os binários ainda **não são assinados**: o SmartScreen (Windows) e o Gatekeeper (macOS) vão avisar. Confira o SHA-256.
> Os binários ainda **não são assinados**: o SmartScreen do Windows vai avisar. Confira o SHA-256.
**Captura e modo monitor exigem Linux e privilégio de root** (`CAP_NET_ADMIN`/`CAP_NET_RAW`): a captura crua usa `AF_PACKET` e o controle de interface usa netlink. A **análise de `.pcap` e a quebra offline** rodam também em Windows e macOS. O app desktop pede elevação de administrador sozinho ao abrir.
**Captura e modo monitor exigem Linux e privilégio de root** — a única plataforma para a qual o CLI é compilado (`CAP_NET_ADMIN`/`CAP_NET_RAW`): a captura crua usa `AF_PACKET` e o controle de interface usa netlink. A **análise de `.pcap` e a quebra offline** rodam também em Windows e macOS. O app desktop pede elevação de administrador sozinho ao abrir.
## Começando
@@ -74,11 +75,11 @@ App desktop (Tauri) com sidebar de interfaces, painel de captura com linha de si
## Limites conhecidos
- **Captura só no Linux**, com placa que suporte **modo monitor** (e injeção, para `deauth`). Em Windows e macOS o Sabiá faz só análise de `.pcap` e quebra offline.
- **Captura só no Linux**, com placa que suporte **modo monitor** (e injeção, para `deauth`); o CLI não é compilado para outras plataformas. Em Windows o app desktop do Sabiá faz só análise de `.pcap` e quebra offline.
- **WPA3/SAE** não cai por dicionário de handshake — a quebra cobre **WPA/WPA2-PSK** (4-way e PMKID).
- **5 e 6 GHz** dependem da placa e do _regulatory domain_; alguns canais ficam passivos (sem _probe_).
- **Certificado autoassinado do Evil Twin Enterprise:** clientes que validam a cadeia de certificado da rede (comum em perfil corporativo/MDM atualizado) não confiam nele — é a defesa correta funcionando, não um bug.
- **Windows e macOS:** o código compila e é testado em CI, mas a captura ao vivo não roda nessas plataformas.
- **Windows:** o código compila e é testado em CI, mas a captura ao vivo não roda lá; o app desktop faz só análise offline. Não há build para macOS: o Tauri precisa do SDK do macOS e de assinatura, os dois presos a hardware Apple e a uma conta Apple Developer, que o CI deste projeto (um único runner Linux) não tem.
## Para onde vai
@@ -88,6 +89,7 @@ Resumo do [ROADMAP](ROADMAP.pt-BR.md):
|---|---|
| **Feito** | Modo monitor por netlink (com recuperação do NetworkManager), varredura 2.4/5/6 GHz, captura de 4-way e PMKID, decriptação CCMP, quebra por dicionário/regras/máscara (WPA/WPA2-PSK), exportação `.hc22000`, escrita e leitura de `.pcap`, `deauth`/PMF/PMKID autorizados, ataque WPS, Evil Twin (PSK + Enterprise), pontuação de canais e achados, relatório de auditoria paginado, trilha de auditoria por hash, `doctor`, CLI + app desktop, atualização em um clique |
| **Próximo** | Aceleração por GPU na quebra, captura em Windows (Npcap) |
| **Fora de plano** | Um build para macOS — veja [Limites conhecidos](#limites-conhecidos) |
| **Depois** | WPA3/SAE _downgrade_, validação em hardware variado, binários de release assinados |
## Desenvolvimento
+1 -1
View File
@@ -2,6 +2,6 @@
**English** · [Português](SECURITY.pt-BR.md)
Sabiá processes untrusted data from the air and from `.pcap` files (802.11 frames and handshakes, possibly forged) and controls network interfaces with root privilege. Vulnerabilities — memory corruption in the radiotap/802.11/EAPOL parsers, path escape when writing `.pcap` or exporting, privilege misuse — should be reported privately through the repository's **Security → Report a vulnerability** tab on GitHub. Do not open a public issue before a fix.
Sabiá processes untrusted data from the air and from `.pcap` files (802.11 frames and handshakes, possibly forged) and controls network interfaces with root privilege. Vulnerabilities — memory corruption in the radiotap/802.11/EAPOL parsers, path escape when writing `.pcap` or exporting, privilege misuse — should be reported privately through the private vulnerability reporting feature of the repository on Gitea. Do not open a public issue before a fix.
Sabiá is a tool for **authorized auditing**. Reports about using it to attack third-party networks are out of the project's security scope — see the **Authorized use** section of the [README](README.md).
+1 -1
View File
@@ -2,6 +2,6 @@
[English](SECURITY.md) · **Português**
O Sabiá processa dados não confiáveis vindos do ar e de arquivos `.pcap` (quadros 802.11 e handshakes, possivelmente forjados) e controla interfaces de rede com privilégio de root. Vulnerabilidades — corrupção de memória nos parsers de radiotap/802.11/EAPOL, escape de caminho na escrita de `.pcap` ou na exportação, uso indevido de privilégio — devem ser reportadas em privado pela aba **Security → Report a vulnerability** do repositório no GitHub. Não abra issue pública antes da correção.
O Sabiá processa dados não confiáveis vindos do ar e de arquivos `.pcap` (quadros 802.11 e handshakes, possivelmente forjados) e controla interfaces de rede com privilégio de root. Vulnerabilidades — corrupção de memória nos parsers de radiotap/802.11/EAPOL, escape de caminho na escrita de `.pcap` ou na exportação, uso indevido de privilégio — devem ser reportadas em privado pelo recurso de relato privado de vulnerabilidade do repositório no Gitea. Não abra issue pública antes da correção.
O Sabiá é uma ferramenta de **auditoria autorizada**. Relatos sobre uso para atacar redes de terceiros não são escopo de segurança do projeto — veja a seção **Uso autorizado** do [README](README.pt-BR.md).
+2 -2
View File
@@ -2538,7 +2538,7 @@ checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
[[package]]
name = "sabia-core"
version = "1.0.0"
version = "1.0.3"
dependencies = [
"aes",
"flate2",
@@ -2555,7 +2555,7 @@ dependencies = [
[[package]]
name = "sabia-gui"
version = "1.0.0"
version = "1.0.3"
dependencies = [
"libc",
"sabia-core",
+3 -2
View File
@@ -3,10 +3,11 @@
[package]
name = "sabia-gui"
description = "Sabiá: interface gráfica de auditoria de Wi-Fi"
version = "1.0.0"
version = "1.0.3"
edition = "2021"
license = "MIT OR Apache-2.0"
repository = "https://github.com/lucasrguerra/sabia"
authors = ["Sabiá contributors"]
repository = "https://git.lucasrguerra.dev.br/lucasrguerra/sabia"
rust-version = "1.80"
[workspace]
+2
View File
@@ -1,4 +1,6 @@
fn main() {
println!("cargo:rerun-if-changed=../../../logo.png");
let _ = std::fs::copy("../../../logo.png", "../ui/logo.png");
#[cfg(windows)]
{
// Modo monitor e captura crua exigem administrador no Windows: o
@@ -0,0 +1,7 @@
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Permissões da janela principal",
"windows": ["main"],
"permissions": ["core:default", "core:window:allow-destroy", "core:window:allow-close"]
}
@@ -1 +1 @@
{}
{"default":{"identifier":"default","description":"Permissões da janela principal","local":true,"windows":["main"],"permissions":["core:default","core:window:allow-destroy","core:window:allow-close"]}}
File diff suppressed because it is too large. Load diff
Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<background android:drawable="@color/ic_launcher_background"/>
</adaptive-icon>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFFFF</color>
</resources>
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 979 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 311 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 336 KiB

After

Width:  |  Height:  |  Size: 350 KiB

+2 -2
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "Sabia",
"version": "1.0.0",
"productName": "Sabiá",
"version": "1.0.3",
"identifier": "dev.sabia.app",
"build": {
"frontendDist": "../ui"
+2 -2
View File
@@ -4,14 +4,14 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Sabiá</title>
<link rel="icon" href="/sabia.png">
<link rel="icon" href="/logo.png">
<link rel="stylesheet" href="/app.css">
</head>
<body>
<div class="app">
<aside class="sidebar">
<div class="brand">
<img class="logo" src="/sabia.png" alt="">
<img class="logo" src="/logo.png" alt="">
<div>
<strong>Sabiá</strong>
<small>auditoria de Wi-Fi</small>
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017-2024 Paged.js contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+10
View File
@@ -0,0 +1,10 @@
# Paged.js
`paged.polyfill.min.js` é o polyfill do [Paged.js](https://pagedjs.org/), versão
0.4.3, usado para paginar os relatórios em A4 no navegador.
- Projeto: https://gitlab.coko.foundation/pagedjs/pagedjs
- Licença: MIT (texto em `PAGEDJS-LICENSE`)
- Arquivo: `paged.polyfill.min.js` (build de distribuição, não modificado)
O arquivo é vendorizado para que o relatório funcione offline, sem CDN.
File renamed without changes.
+4 -4
View File
@@ -17,8 +17,8 @@ const APP_CSS: &str = include_str!("../../../../apps/gui/ui/app.css");
const APP_JS: &str = include_str!("../../../../apps/gui/ui/app.js");
const I18N_JS: &str = include_str!("../../../../apps/gui/ui/i18n.js");
const I18N_EN_JS: &str = include_str!("../../../../apps/gui/ui/i18n-en.js");
const LOGO_PNG: &[u8] = include_bytes!("../../../../apps/gui/ui/sabia.png");
const PAGED_JS: &str = include_str!("../../../../apps/gui/ui/paged.polyfill.min.js");
const LOGO_PNG: &[u8] = include_bytes!("../../../../logo.png");
const PAGED_JS: &str = include_str!("../../../../apps/gui/ui/vendor/paged.polyfill.min.js");
pub fn serve(port: u16, audit_path: std::path::PathBuf, open: bool) -> sabia_core::Result<()> {
let token = gen_token();
@@ -275,7 +275,7 @@ fn handle(
}
match req.path.as_str() {
"/paged.polyfill.min.js" => {
"/vendor/paged.polyfill.min.js" | "/paged.polyfill.min.js" => {
return respond(
&mut stream,
200,
@@ -308,7 +308,7 @@ fn handle(
I18N_EN_JS.as_bytes(),
)
}
"/sabia.png" => return respond(&mut stream, 200, "image/png", LOGO_PNG),
"/logo.png" => return respond(&mut stream, 200, "image/png", LOGO_PNG),
_ => {}
}
File renamed without changes.
File renamed without changes.
+1
View File
@@ -1198,6 +1198,7 @@ impl Engine {
/// `direction`: `ap` (AP→STA), `sta` (STA→AP, exige estação) ou `both`.
/// `frame_kind`: `deauth` ou `disassoc`.
#[cfg(target_os = "linux")]
#[allow(clippy::too_many_arguments)]
pub fn deauth_directional(
&self,
+4 -4
View File
@@ -34,12 +34,12 @@ pub struct ReportOptions {
}
const PAGED_MARKER: &str = "<!--SABIA_PAGEDJS-->";
const LOGO_PNG: &[u8] = include_bytes!("../../../apps/gui/ui/sabia.png");
const LOGO_PNG: &[u8] = include_bytes!("../../../logo.png");
const B64: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
/// Embute o logo como `data:` URI — sem isso o relatório standalone (modo
/// offline, pensado pra abrir sem servidor nenhum) teria um `<img
/// src="/sabia.png">` que nunca resolve fora do `sabia ui`. Pequeno o
/// src="/logo.png">` que nunca resolve fora do `sabia ui`. Pequeno o
/// bastante (poucos KB) pra não valer a pena puxar uma crate de base64 só
/// por isso.
fn base64(bytes: &[u8]) -> String {
@@ -502,7 +502,7 @@ pub fn render(model: &ReportModel, options: ReportOptions) -> String {
let paged = if options.inline_pagedjs {
PAGED_MARKER.to_string()
} else {
"<script src=\"/paged.polyfill.min.js\"></script>".to_string()
"<script src=\"/vendor/paged.polyfill.min.js\"></script>".to_string()
};
let stats = &model.stats;
let crackable = model.handshakes.iter().filter(|h| h.crackable).count();
@@ -765,7 +765,7 @@ mod tests {
m.title = "Rede <x>".into();
let html = render(&m, ReportOptions::default());
assert!(html.contains("Rede &lt;x&gt;"));
assert!(html.contains("paged.polyfill.min.js"));
assert!(html.contains("/vendor/paged.polyfill.min.js"));
assert!(html.contains("@page"));
}
+40 -6
View File
@@ -1,4 +1,4 @@
//! Auto-atualização simples: verifica a última release no GitHub, baixa o
//! Auto-atualização simples: verifica a última release no Gitea, baixa o
//! binário certo para a plataforma atual, confere o checksum e troca o
//! executável em uso — sem o usuário precisar baixar nada manualmente.
//!
@@ -12,7 +12,13 @@ use std::fs;
use std::path::PathBuf;
use std::process::Command;
const REPO: &str = "lucasrguerra/sabia";
/// A API do Gitea devolve o mesmo formato de `releases/latest` que o GitHub devolvia
/// (`tag_name`, `assets[].name`, `assets[].browser_download_url`).
const LATEST_RELEASE_URL: &str =
"https://git.lucasrguerra.dev.br/api/v1/repos/lucasrguerra/sabia/releases/latest";
/// Página de downloads, para quem usa o app desktop.
const RELEASES_PAGE_URL: &str = "https://git.lucasrguerra.dev.br/lucasrguerra/sabia/releases";
#[derive(Debug, Deserialize)]
struct Release {
@@ -92,8 +98,8 @@ fn asset_suffix_for_platform() -> String {
/// Consulta a última release publicada e compara com a versão atual
/// (`CARGO_PKG_VERSION` do binário chamador).
pub fn check(current_version: &str) -> Result<UpdateStatus> {
let url = format!("https://api.github.com/repos/{REPO}/releases/latest");
let body = match curl_text(&url) {
let url = LATEST_RELEASE_URL;
let body = match curl_text(url) {
Ok(b) => b,
Err(_) => {
// Sem rede, ou repositório ainda sem releases publicadas: não é
@@ -118,8 +124,22 @@ pub fn check(current_version: &str) -> Result<UpdateStatus> {
/// Baixa e aplica a atualização mais recente, substituindo o binário atual.
/// Retorna a versão instalada.
pub fn apply(current_exe: &PathBuf) -> Result<String> {
let url = format!("https://api.github.com/repos/{REPO}/releases/latest");
let body = curl_text(&url)?;
// O pacote da release contém o CLI (`sabia`). Trocar por ele um executável que
// não seja o CLI, como o `sabia-gui`, o deixaria como um CLI e quebraria o app
// na próxima abertura. O app desktop se atualiza baixando o instalador.
let is_cli = current_exe
.file_stem()
.map(|stem| stem == "sabia")
.unwrap_or(false);
if !is_cli {
return Err(SabiaError::CommandFailed {
cmd: "update".into(),
detail: format!(
"a atualização automática vale só para o CLI `sabia`; para o app desktop, baixe o instalador em {RELEASES_PAGE_URL}"
),
});
}
let body = curl_text(LATEST_RELEASE_URL)?;
let release: Release = serde_json::from_str(&body)?;
let suffix = asset_suffix_for_platform();
@@ -225,3 +245,17 @@ fn find_binary(dir: &PathBuf) -> Result<PathBuf> {
detail: "binário não encontrado no pacote baixado".into(),
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn apply_refuses_to_overwrite_the_desktop_app() {
// Falha antes de qualquer acesso à rede: o nome do executável já não é o do CLI.
let err = apply(&PathBuf::from("/opt/sabia/sabia-gui")).unwrap_err();
assert!(err.to_string().contains("só para o CLI"), "{err}");
let err = apply(&PathBuf::from("C:/Program Files/Sabia/sabia-gui.exe")).unwrap_err();
assert!(err.to_string().contains(RELEASES_PAGE_URL), "{err}");
}
}
+2 -2
View File
@@ -1,7 +1,7 @@
//! Ataque de PIN WPS com o `reaver` embutido.
//!
//! O protocolo EAP-WSC é grande demais para reimplementar aqui; o Sabiá
//! carrega uma cópia do reaver (GPL-2, veja `assets/tools/LICENSE.reaver`),
//! carrega uma cópia do reaver (GPL-2, veja `crates/sabia-core/assets/tools/LICENSE.reaver`),
//! extrai em tempo de execução e mostra a saída ao vivo. Assim o WPS já
//! funciona sem instalar nada. Se o usuário tiver um `bully` no PATH, ele
//! também aparece como alternativa.
@@ -17,7 +17,7 @@ pub struct WpsTool {
}
#[cfg(target_os = "linux")]
const BUNDLED_REAVER: &[u8] = include_bytes!("../../../assets/tools/reaver");
const BUNDLED_REAVER: &[u8] = include_bytes!("../assets/tools/reaver");
/// Procura um binário no `PATH` — usado pelo WPS (reaver/bully) e, fora
/// deste módulo, pelo Evil Twin (hostapd/dnsmasq), que precisa da mesma
+3 -3
View File
@@ -28,13 +28,13 @@ The CLI and the desktop app are thin clients: both call the core's `Engine`. The
| `pcapreader.rs`, `pcapwriter.rs` | `.pcap` read and write. |
| `audit.rs` | Local hash-chained audit trail. |
| `doctor.rs` | Diagnostics of privileges, tools and interfaces. |
| `update.rs` | Self-update from GitHub releases (curl + SHA-256). |
| `update.rs` | Self-update from Gitea releases (curl + SHA-256). |
| `error.rs`, `testutil.rs` | Shared error type; test helpers. |
## Platform
Capture and monitor mode are **Linux** (`AF_PACKET` + netlink). Parsing (`radiotap`, `dot11`, `eapol`, `handshake`), cracking (`crack`) and `.pcap` are cross-platform, being offline — which is why the tests run on Linux, Windows and macOS in CI.
Capture and monitor mode are **Linux** (`AF_PACKET` + netlink). Parsing (`radiotap`, `dot11`, `eapol`, `handshake`), cracking (`crack`) and `.pcap` are cross-platform, being offline — which is why the tests run on Linux in CI.
## UI transport
`sabia ui` starts a server on `127.0.0.1` protected by a token generated on the spot; it serves `index.html`/`app.css`/`app.js`/`sabia.png` embedded in the binary plus a JSON API. The Tauri app uses the same files, calling the `Engine` directly via Tauri commands instead of HTTP.
`sabia ui` starts a server on `127.0.0.1` protected by a token generated on the spot; it serves `index.html`/`app.css`/`app.js`/`logo.png` embedded in the binary plus a JSON API. The Tauri app uses the same files, calling the `Engine` directly via Tauri commands instead of HTTP.
+3 -3
View File
@@ -28,13 +28,13 @@ A CLI e o app desktop são clientes finos: ambos chamam o `Engine` do núcleo. A
| `pcapreader.rs`, `pcapwriter.rs` | Leitura e escrita de `.pcap`. |
| `audit.rs` | Trilha de auditoria local encadeada por hash. |
| `doctor.rs` | Diagnóstico de privilégios, ferramentas e interfaces. |
| `update.rs` | Auto-atualização por releases do GitHub (curl + SHA-256). |
| `update.rs` | Auto-atualização por releases do Gitea (curl + SHA-256). |
| `error.rs`, `testutil.rs` | Tipo de erro compartilhado; ajudantes de teste. |
## Plataforma
Captura e modo monitor são **Linux** (`AF_PACKET` + netlink). Parsing (`radiotap`, `dot11`, `eapol`, `handshake`), quebra (`crack`) e `.pcap` são multiplataforma, por serem offline — por isso os testes rodam em Linux, Windows e macOS no CI.
Captura e modo monitor são **Linux** (`AF_PACKET` + netlink). Parsing (`radiotap`, `dot11`, `eapol`, `handshake`), quebra (`crack`) e `.pcap` são multiplataforma, por serem offline — por isso os testes rodam em Linux no CI.
## Transporte da UI
`sabia ui` sobe um servidor em `127.0.0.1` protegido por um token gerado na hora; serve `index.html`/`app.css`/`app.js`/`sabia.png` embutidos no binário e uma API JSON. O app Tauri usa os mesmos arquivos, chamando o `Engine` direto por comandos Tauri em vez de HTTP.
`sabia ui` sobe um servidor em `127.0.0.1` protegido por um token gerado na hora; serve `index.html`/`app.css`/`app.js`/`logo.png` embutidos no binário e uma API JSON. O app Tauri usa os mesmos arquivos, chamando o `Engine` direto por comandos Tauri em vez de HTTP.
+67
View File
@@ -0,0 +1,67 @@
# O que o Sabiá suporta
[English](../support.md) · **Português**
Matriz do que existe hoje (versão 1.0.3). Para o que vem a seguir, veja o [ROADMAP](../../ROADMAP.pt-BR.md).
## Plataformas
| | Descoberta e captura | Análise e quebra offline | App desktop |
|---|---|---|---|
| Linux | ✅ root, adaptador em modo monitor | ✅ | ✅ AppImage e deb |
| Windows | ❌ | ✅ `.pcap`, dicionário/regras/máscara | ✅ instalador |
| macOS | ❌ | ✅ `.pcap`, dicionário/regras/máscara | ❌ sem build |
A captura ao vivo usa `AF_PACKET` e netlink; exige Linux, root (ou `CAP_NET_ADMIN`/`CAP_NET_RAW`) e uma placa com modo monitor. Injeção (`deauth`, Evil Twin) exige uma placa que também injete. O app desktop pede elevação ao abrir.
## Descoberta
- Varredura dos canais de **2.4, 5 e 6 GHz**, limitada pela placa e pelo _regulatory domain_.
- **Pontos de acesso e clientes** com SSID, BSSID, canal, sinal, tipo de segurança, detalhes de WPS e fabricante.
- **Modo monitor** ligado/desligado por netlink/`iw`, com recuperação automática se o NetworkManager retomar a interface.
- **Canais suportados** informados por rádio.
## Captura e análise
| Item | Suporte |
|---|---|
| 4-way handshake (EAPOL) | ✅ detectado e guardado para quebra offline |
| PMKID | ✅ detectado e guardado, inclusive caça sem cliente |
| Escrita e leitura de `.pcap` | ✅ compatível com as outras ferramentas 802.11 |
| Decifração CCMP | ✅ para um cliente cuja PSK já é conhecida |
| Parsers radiotap / 802.11 / EAPOL | ✅ |
## Quebra (WPA/WPA2-PSK)
| Modo | Suporte |
|---|---|
| Dicionário | ✅ rockyou + Probable-Wordlists por padrão (9,7 milhões de senhas) |
| Regras de mutação | ✅ estilo hashcat |
| Máscara/força bruta | ✅ `?d?l?u?s?a` |
| Verificação | ✅ PMK por PBKDF2-HMAC-SHA1 + MIC (4-way) ou PMKID |
| Exportação | ✅ `.hc22000` para hashcat/John |
WPA3/SAE **não** cai por dicionário de handshake; o escopo da quebra é WPA/WPA2-PSK.
## Ataques autorizados
- **Desautenticação** direcionada ou contínua, com `--yes` / confirmação explícita de autorização.
- **Teste de PMF** e **caça de PMKID sem cliente**.
- **WPS PIN / Pixie Dust** com o `reaver` embutido (GPL-2) ou `bully` quando instalado.
- **Evil Twin PSK**: clona um AP visto com portal cativo; a senha só é aceita depois de conferida contra o handshake real já capturado.
- **Evil Twin Enterprise** (802.1X/EAP): captura credenciais automaticamente, sem portal. Exige `hostapd-wpe` e uma segunda interface Wi-Fi em modo AP.
## Relatórios, auditoria e diagnóstico
- **Relatório de auditoria paginado** (capa, sumário, achados, pontuação de canais, trilha de auditoria), gerado localmente.
- **Trilha de auditoria encadeada por hash** para toda ativação de modo monitor e sessão de captura (`sabia audit show`).
- **`sabia doctor`** confere privilégios, ferramentas e interfaces antes da captura.
## O que o Sabiá não faz
- Quebrar handshake WPA3/SAE, ou recuperar senha sem handshake/PMKID capturado e uma candidata correspondente.
- Capturar no Windows ou no macOS; nessas plataformas só há análise offline.
- Dispensar a confirmação de autorização para `deauth`, WPS ou Evil Twin.
- Garantir cobertura de 5/6 GHz: depende da placa, do _regulatory domain_ e do driver.
- Fazer um cliente enterprise confiar num certificado autoassinado quando ele valida a cadeia (comum em perfil corporativo gerenciado).
- Acelerar a quebra por GPU ainda; a quebra é feita na CPU.
+67
View File
@@ -0,0 +1,67 @@
# What Sabiá supports
[English](support.md) · [Português](pt-BR/suporte.md)
Matrix of what exists today (version 1.0.3). For what comes next, see the [ROADMAP](../ROADMAP.md).
## Platforms
| | Discovery and capture | Offline analysis and cracking | Desktop app |
|---|---|---|---|
| Linux | ✅ root, adapter in monitor mode | ✅ | ✅ AppImage and deb |
| Windows | ❌ | ✅ `.pcap`, wordlist/rules/mask | ✅ installer |
| macOS | ❌ | ✅ `.pcap`, wordlist/rules/mask | ❌ no build |
Live capture uses `AF_PACKET` and netlink; it needs Linux, root (or `CAP_NET_ADMIN`/`CAP_NET_RAW`) and an adapter that supports monitor mode. Injection (`deauth`, Evil Twin) needs an adapter that also supports injection. The desktop app asks for elevation on startup.
## Discovery
- **2.4, 5 and 6 GHz** channel sweep, limited by the adapter and the regulatory domain.
- **Access points and clients** with SSID, BSSID, channel, signal, security type, WPS details and vendor.
- **Monitor mode** on/off through netlink/`iw`, with automatic recovery if NetworkManager takes the interface back.
- **Supported channels** reported per radio.
## Capture and analysis
| Item | Support |
|---|---|
| 4-way handshake (EAPOL) | ✅ detected and stored for offline cracking |
| PMKID | ✅ detected and stored, including clientless PMKID hunting |
| `.pcap` write and read | ✅ compatible with other 802.11 tools |
| CCMP decryption | ✅ for a client whose PSK is already known |
| Radiotap / 802.11 / EAPOL parsing | ✅ |
## Cracking (WPA/WPA2-PSK)
| Mode | Support |
|---|---|
| Dictionary | ✅ rockyou + Probable-Wordlists by default (9.7 million passwords) |
| Mutation rules | ✅ hashcat-style rules |
| Mask/brute force | ✅ `?d?l?u?s?a` |
| Verification | ✅ PBKDF2-HMAC-SHA1 PMK + MIC (4-way) or PMKID |
| Export | ✅ `.hc22000` for hashcat/John |
WPA3/SAE does **not** fall to a handshake dictionary attack; the cracking scope is WPA/WPA2-PSK.
## Authorized attacks
- **Deauthentication** directed or continuous, with `--yes` / explicit authorization checkbox.
- **PMF test** and **clientless PMKID hunt**.
- **WPS PIN / Pixie Dust** using the bundled `reaver` (GPL-2) or `bully` when installed.
- **Evil Twin PSK**: clones a seen AP with a captive portal; the password is only accepted after being checked against the real captured handshake.
- **Evil Twin Enterprise** (802.1X/EAP): captures credentials automatically, without a portal. It needs `hostapd-wpe` and a second Wi-Fi interface in AP mode.
## Reports, audit and diagnostics
- **Paginated audit report** (cover, table of contents, findings, channel score, audit trail), generated locally.
- **Hash-chained audit trail** for every monitor-mode activation and capture session (`sabia audit show`).
- **`sabia doctor`** checks privileges, tools and interfaces before capture.
## What Sabiá does not do
- Crack WPA3/SAE handshakes, or recover a password without a captured handshake/PMKID and a matching candidate.
- Capture on Windows or macOS; those platforms are offline analysis only.
- Bypass the authorization requirement for deauth, WPS or Evil Twin.
- Guarantee 5/6 GHz coverage: it depends on the adapter, regulatory domain and driver.
- Make an enterprise client trust a self-signed certificate when the client validates the chain (common in managed corporate profiles).
- Provide GPU acceleration yet; cracking is CPU-bound.
View File
File renamed without changes.
+4 -4
View File
@@ -16,15 +16,15 @@ mkdir -p "$cache/partial"
trap 'sudo chown -R "$(id -u):$(id -g)" "$cache" 2>/dev/null || true; rm -f "$cache/lock"' EXIT
opts=(-o "Dir::Cache::archives=$cache" -y)
# 1) com .deb em cache: instala direto, sem nem atualizar o índice do apt
if ls "$cache"/*.deb >/dev/null 2>&1 && timeout 300 sudo apt-get "${opts[@]}" install "${pkgs[@]}"; then
# 1) com .deb em cache e índice presente: instala direto, sem nem atualizar o índice do apt
if [ -z "$(dpkg --print-foreign-architectures)" ] && ls "$cache"/*.deb >/dev/null 2>&1 && ls /var/lib/apt/lists/*Packages* >/dev/null 2>&1 && timeout 300 sudo apt-get "${opts[@]}" install "${pkgs[@]}"; then
echo "instalado a partir do cache"
exit 0
fi
# 2) sem cache (ou incompleto): atualiza e instala, até 3 tentativas com tempo limite
# 2) sem cache (ou multiarch / incompleto): atualiza e instala, até 3 tentativas com tempo limite
for tentativa in 1 2 3; do
if timeout 240 sudo apt-get update && timeout 600 sudo apt-get "${opts[@]}" install "${pkgs[@]}"; then
if timeout 240 sudo apt-get update && { [ -z "$(dpkg --print-foreign-architectures)" ] || timeout 600 sudo apt-get "${opts[@]}" upgrade; } && timeout 600 sudo apt-get "${opts[@]}" install "${pkgs[@]}"; then
echo "instalado (tentativa $tentativa)"
exit 0
fi
+146
View File
@@ -0,0 +1,146 @@
#!/usr/bin/env bash
# Compila o app desktop (Tauri) e deixa os pacotes em dist-gui/, com os nomes
# que a release usa.
#
# scripts/build-gui.sh linux # AppImage e .deb; precisa de uma máquina Linux com os pacotes do Tauri
# scripts/build-gui.sh windows # instalador NSIS, compilado a partir do Linux
#
# O Windows usa cargo-xwin (SDK do Windows baixado na hora) e o NSIS, que o Tauri
# documenta como compilação cruzada experimental. Precisa de: nsis, lld, llvm,
# clang e `cargo install cargo-xwin`. O .msi não sai daqui (o WiX é só do Windows).
# `scripts/build-gui.sh windows-docker` roda o mesmo num container, sem instalar nada.
set -euo pipefail
cd "$(dirname "$0")/.."
platform=${1:?uso: build-gui.sh <linux|linux-x86_64|linux-aarch64|windows|windows-docker>}
tauri() { npx --yes @tauri-apps/cli@2 "$@"; }
if [ -z "${TAURI_SIGNING_PRIVATE_KEY:-}" ] && [ -f "$HOME/.tauri/sabia-updater.key" ]; then
export TAURI_SIGNING_PRIVATE_KEY="$(cat "$HOME/.tauri/sabia-updater.key")"
[ -f "$HOME/.tauri/sabia-updater.pass" ] && export TAURI_SIGNING_PRIVATE_KEY_PASSWORD="$(cat "$HOME/.tauri/sabia-updater.pass")"
fi
if [ "$platform" = windows-docker ]; then
docker volume create sabia-cargo >/dev/null
docker volume create sabia-rustup >/dev/null
mkdir -p dist-gui
exec docker run --rm -v "$PWD":/src -v sabia-cargo:/root/.cargo -v sabia-rustup:/root/.rustup \
-w /src -e HOST_UID="$(id -u)" -e HOST_GID="$(id -g)" \
-e TAURI_SIGNING_PRIVATE_KEY -e TAURI_SIGNING_PRIVATE_KEY_PASSWORD \
ubuntu:24.04 bash -c '
set -e
export DEBIAN_FRONTEND=noninteractive
apt-get update -qq
apt-get install -y -qq curl ca-certificates build-essential pkg-config nsis lld llvm clang nodejs npm unzip >/dev/null
[ -x /root/.cargo/bin/rustup ] || curl -fsSL https://sh.rustup.rs | sh -s -- -y --profile minimal >/dev/null
export PATH=/root/.cargo/bin:$PATH
command -v cargo-xwin >/dev/null || cargo install cargo-xwin --locked
scripts/build-gui.sh windows
chown -R "$HOST_UID:$HOST_GID" dist-gui apps/gui/src-tauri/target 2>/dev/null || true'
fi
rm -rf dist-gui
mkdir -p dist-gui
cd apps/gui/src-tauri
# Os ícones derivados do original não ficam todos no repositório.
[ -f icons/icon.ico ] || tauri icon icons/source.png -o icons
tauri_blob_cache="$HOME/.cache/tauri"
if [ -d /opt/hostedtoolcache ] && [ -w /opt/hostedtoolcache ]; then
tauri_blob_cache="/opt/hostedtoolcache/tauri-tools"
fi
mkdir -p "$tauri_blob_cache" "$HOME/.cache/tauri"
fetch_cached_tool() {
local name=$1 url=$2 patch_elf=${3:-0}
local cached="$tauri_blob_cache/$name"
if [ ! -s "$cached" ]; then
local tmp="$cached.tmp.$$"
curl -fsSL "$url" -o "$tmp"
if [ "$patch_elf" = 1 ]; then
dd if=/dev/zero of="$tmp" bs=1 count=3 seek=8 conv=notrunc 2>/dev/null
fi
chmod +x "$tmp"
mv -f "$tmp" "$cached"
fi
if [ "$cached" != "$HOME/.cache/tauri/$name" ]; then
cp -f "$cached" "$HOME/.cache/tauri/$name"
chmod +x "$HOME/.cache/tauri/$name"
fi
}
case "$platform" in
linux|linux-aarch64)
bundle="${CARGO_TARGET_DIR:-target}/release/bundle"
rm -rf "$bundle/appimage" "$bundle/deb"
tauri build --bundles appimage,deb
cp "$bundle"/appimage/*.AppImage "$bundle"/deb/*.deb ../../../dist-gui/
# O atualizador do Tauri (quando o projeto usa) precisa do .sig ao lado.
cp "$bundle"/appimage/*.AppImage.sig ../../../dist-gui/ 2>/dev/null || true
;;
linux-x86_64)
if [ "$(uname -m)" = x86_64 ]; then
bundle="${CARGO_TARGET_DIR:-target}/release/bundle"
rm -rf "$bundle/appimage" "$bundle/deb"
tauri build --bundles appimage,deb
else
rustup target add x86_64-unknown-linux-gnu >/dev/null
mkdir -p /tmp/x86_64-cross-tools "$HOME/.cache/tauri"
cat >/tmp/x86_64-cross-tools/ldd <<'EOF'
#!/usr/bin/env bash
exec qemu-x86_64 /lib64/ld-linux-x86-64.so.2 --list "$@"
EOF
chmod +x /tmp/x86_64-cross-tools/ldd
ln -sf /usr/bin/x86_64-linux-gnu-strip /tmp/x86_64-cross-tools/strip
for tool_rel in glib-2.0/gio-querymodules gdk-pixbuf-2.0/gdk-pixbuf-query-loaders libgtk-3-0t64/gtk-query-immodules-3.0 libgtk-3-0/gtk-query-immodules-3.0; do
if [ -x "/usr/lib/x86_64-linux-gnu/$tool_rel" ]; then
tname=$(basename "$tool_rel")
printf '#!/usr/bin/env bash\nexec qemu-x86_64 "/usr/lib/x86_64-linux-gnu/%s" "$@"\n' "$tool_rel" >"/tmp/x86_64-cross-tools/$tname"
chmod +x "/tmp/x86_64-cross-tools/$tname"
fi
done
fetch_cached_tool "linuxdeploy-07333c6-aarch64.AppImage" \
"https://github.com/tauri-apps/binary-releases/releases/download/linuxdeploy-07333c6/linuxdeploy-aarch64.AppImage" 1
fetch_cached_tool "linuxdeploy-plugin-appimage.AppImage" \
"https://github.com/linuxdeploy/linuxdeploy-plugin-appimage/releases/download/continuous/linuxdeploy-plugin-appimage-aarch64.AppImage" 1
fetch_cached_tool "runtime-x86_64" \
"https://github.com/AppImage/type2-runtime/releases/download/continuous/runtime-x86_64" 0
cat >"$HOME/.cache/tauri/linuxdeploy-07333c6-x86_64.AppImage" <<'EOF'
#!/usr/bin/env bash
export ARCH=x86_64
export NO_STRIP=true
export APPIMAGE_EXTRACT_AND_RUN=1
export LDAI_RUNTIME_FILE="$HOME/.cache/tauri/runtime-x86_64"
export LD_GTK_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu
export DEB_HOST_MULTIARCH=x86_64-linux-gnu
export PKG_CONFIG_ALLOW_CROSS=1
export PKG_CONFIG_LIBDIR=/usr/lib/x86_64-linux-gnu/pkgconfig:/usr/share/pkgconfig
export PATH="/tmp/x86_64-cross-tools:$PATH"
exec "$HOME/.cache/tauri/linuxdeploy-07333c6-aarch64.AppImage" "$@"
EOF
chmod +x "$HOME/.cache/tauri/linuxdeploy-07333c6-x86_64.AppImage"
export PKG_CONFIG_ALLOW_CROSS=1
export PKG_CONFIG_LIBDIR=/usr/lib/x86_64-linux-gnu/pkgconfig:/usr/share/pkgconfig
export CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=x86_64-linux-gnu-gcc
export CC_x86_64_unknown_linux_gnu=x86_64-linux-gnu-gcc
export CXX_x86_64_unknown_linux_gnu=x86_64-linux-gnu-g++
bundle="${CARGO_TARGET_DIR:-target}/x86_64-unknown-linux-gnu/release/bundle"
rm -rf "$bundle/appimage" "$bundle/deb"
tauri build --target x86_64-unknown-linux-gnu --bundles appimage,deb
fi
cp "$bundle"/appimage/*.AppImage "$bundle"/deb/*.deb ../../../dist-gui/
cp "$bundle"/appimage/*.AppImage.sig ../../../dist-gui/ 2>/dev/null || true
;;
windows)
rustup target add x86_64-pc-windows-msvc >/dev/null
bundle="${CARGO_TARGET_DIR:-target}/x86_64-pc-windows-msvc/release/bundle/nsis"
rm -rf "$bundle"
tauri build --runner cargo-xwin --target x86_64-pc-windows-msvc --bundles nsis
cp "$bundle"/*-setup.exe ../../../dist-gui/
cp "$bundle"/*-setup.exe.sig ../../../dist-gui/ 2>/dev/null || true
;;
*) echo "plataforma desconhecida: $platform" >&2; exit 2 ;;
esac
ls -1 ../../../dist-gui
+1 -1
View File
@@ -250,7 +250,7 @@ function rustLabels(source, file) {
// ---------------------------------------------------------------- coleta
const uiFiles = readdirSync(join(root, 'apps/gui/ui'))
.filter((f) => f.endsWith('.js') && f !== 'i18n-en.js' && f !== 'paged.polyfill.min.js')
.filter((f) => f.endsWith('.js') && f !== 'i18n-en.js' && f !== 'vendor')
.map((f) => join(root, 'apps/gui/ui', f));
uiFiles.push(join(root, 'apps/gui/ui/index.html'));
+49
View File
@@ -0,0 +1,49 @@
#!/usr/bin/env python3
"""Gera, a partir da logo oficial, o ícone base usado pelo Tauri.
A logo oficial é `logo.png` na raiz do repositório (RGBA, fundo transparente,
serve aos temas claro e escuro). Ela não é alterada aqui. Saída:
apps/gui/src-tauri/icons/source.png 1024 px, a arte sobre o fundo escuro com cantos arredondados;
o CI e `npx @tauri-apps/cli icon` geram o resto dos ícones.
Uso: python3 scripts/gerar-logo.py
"""
import os
from PIL import Image, ImageDraw
RAIZ = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")
ICONES = os.path.join(RAIZ, "apps/gui/src-tauri/icons")
FUNDO_ICONE = (10, 16, 28) # o azul-noite do fundo da arte original
def recortar_quadrado(im, margem=0.04):
"""Recorta no que é visível (opacidade > 16, ignora resquícios) e centraliza num quadrado."""
box = im.getchannel("A").point(lambda v: 255 if v > 16 else 0).getbbox()
im = im.crop(box)
w, h = im.size
lado = round(max(w, h) * (1 + 2 * margem))
quadro = Image.new("RGBA", (lado, lado), (0, 0, 0, 0))
quadro.paste(im, ((lado - w) // 2, (lado - h) // 2), im)
return quadro
def main():
logo = recortar_quadrado(Image.open(os.path.join(RAIZ, "logo.png")).convert("RGBA"))
lado = 1024
fundo = Image.new("RGBA", (lado, lado), FUNDO_ICONE + (255,))
arte = logo.resize((round(lado * 0.82),) * 2, Image.LANCZOS)
fundo.paste(arte, ((lado - arte.width) // 2, (lado - arte.height) // 2), arte)
mascara = Image.new("L", (lado, lado), 0)
ImageDraw.Draw(mascara).rounded_rectangle((0, 0, lado - 1, lado - 1), radius=round(lado * 0.2237), fill=255)
icone = Image.new("RGBA", (lado, lado), (0, 0, 0, 0))
icone.paste(fundo, (0, 0), mascara)
os.makedirs(ICONES, exist_ok=True)
destino = os.path.join(ICONES, "source.png")
icone.save(destino, optimize=True)
os.chmod(destino, 0o644)
print("icons/source.png")
if __name__ == "__main__":
main()
+6
View File
@@ -0,0 +1,6 @@
#!/usr/bin/env bash
# Liga o hook de pre-push do repositório (roda scripts/verificar.sh antes de cada push).
set -euo pipefail
cd "$(dirname "$0")/.."
git config core.hooksPath .githooks
echo "hook ligado: git push passa a rodar scripts/verificar.sh (desligue com: git config --unset core.hooksPath)"
+39
View File
@@ -0,0 +1,39 @@
#!/usr/bin/env bash
# Compila o CLI para uma plataforma e empacota como o atualizador espera:
# sabia-<tag>-<plataforma>.tar.gz com um .sha256 ao lado.
#
# scripts/package-cli.sh v1.0.3 linux-x86_64 x86_64-unknown-linux-gnu
#
# Plataformas: linux-x86_64 e linux-aarch64. O alvo diferente da máquina de build
# usa o linker cruzado correspondente (gcc-x86-64-linux-gnu). Escreve os arquivos
# na raiz do repositório e imprime os nomes.
set -euo pipefail
cd "$(dirname "$0")/.."
tag=${1:?uso: package-cli.sh <tag> <plataforma> <alvo-rust>}
platform=${2:?uso: package-cli.sh <tag> <plataforma> <alvo-rust>}
target=${3:?uso: package-cli.sh <tag> <plataforma> <alvo-rust>}
case "$target" in
x86_64-unknown-linux-gnu)
if [ "$(uname -m)" != x86_64 ]; then
export CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=x86_64-linux-gnu-gcc
export CC_x86_64_unknown_linux_gnu=x86_64-linux-gnu-gcc
fi ;;
esac
rustup target add "$target" >/dev/null 2>&1 || true
cargo build --release --locked -p sabia-cli --target "$target"
name="sabia-${tag}-${platform}"
rm -rf "$name"; mkdir "$name"
cp "${CARGO_TARGET_DIR:-target}/${target}/release/sabia" "$name/"
cp README.md LICENSE-MIT LICENSE-APACHE "$name/"
tar czf "$name.tar.gz" "$name"
asset="$name.tar.gz"
rm -rf "$name"
sha256sum "$asset" > "$asset.sha256"
echo "$asset"
echo "$asset.sha256"
Loaded 100 of 103 files, more files were not shown because too many files have changed in this diff. Show more