* fix(security): tenant guard on the GL-line read RPCs (PR #624 follow-up) get_unlinked_gl_lines and get_account_gl_lines_for_matching are SECURITY DEFINER and EXECUTE-able by anon/authenticated, so any authenticated (or anonymous) caller could invoke them directly over /rest/v1/rpc with another company's id and read its general-ledger lines — a cross-tenant read that bypasses the API routes' requireCompanyId() guard. Confirmed against the DB: anon and authenticated both hold EXECUTE, and SECURITY DEFINER sidesteps RLS. Add an in-function guard constraining anon/authenticated callers to their own companies (the same boundary user_company_ids()/RLS enforces). Trusted callers are untouched — service_role (the enable-banking reconciliation cron) and direct / superuser access (migrations, the pg-real harness) are not anon/authenticated, so the predicate is a no-op and behaviour is unchanged. A foreign company id now yields zero rows, not data. Scope: hardens the two READ RPCs that expose ledger data. The remaining company-scoped SECURITY DEFINER RPCs are writes / sequence generators with their own internal authorization; a broader audit of that set is tracked separately. pg-real coverage: a company-B member probing company A gets zero rows from both RPCs, while a company-A member and direct/superuser access still see the data. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(security): revoke EXECUTE from PUBLIC/anon on the GL-line read RPCs Defense-in-depth follow-up to the tenant guard. The guard already returns zero rows to an anon/authenticated caller probing another company; this additionally strips the EXECUTE privilege so an unauthenticated (anon) caller cannot invoke the financial-ledger RPCs at all. Supabase grants EXECUTE to PUBLIC as well as to anon, and anon is a member of PUBLIC — revoking only anon is insufficient, so revoke both, then keep the two legitimate callers: authenticated (the API routes call via the user's session; the in-function guard scopes them to their own companies) and service_role (the enable-banking reconciliation cron). Verified on the DB: anon EXECUTE = false, authenticated/service_role = true. Adds an anon-role pg test asserting the call is rejected at the privilege layer. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(security): read JWT role from claims object in the GL-line RPC guard The 20260611120000 guard used auth.role() to detect the caller's role, but auth.role() reads the individual request.jwt.claim.role GUC first and only some installs fall back to the claims object. PostgREST sets the claims OBJECT (the individual claim.* GUCs are deprecated), and the pg-real harness sets request.jwt.claims (+ claim.sub for auth.uid()) but NOT claim.role — so on an auth.role() without the object fallback it returns NULL and the guard's NOT IN ('anon','authenticated') branch was TRUE, skipping the membership check. A pg-real test caught it: an authenticated non-member could still read another company's GL lines (the guard failed open in that environment). Read the role straight from request.jwt.claims (exactly what auth.role() itself falls back to), so the guard enforces in every environment regardless of which JWT-claim GUCs are populated. Verified on the DB: an authenticated non-member evaluates both guard branches false → row excluded. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
gnubok
Open-source Swedish accounting software for sole traders (enskild firma) and limited companies (aktiebolag).
What is gnubok?
gnubok implements double-entry bookkeeping compliant with Swedish accounting law (Bokforingslagen). It supports the BAS 2026 chart of accounts, handles VAT declarations (momsdeklaration), SIE import/export, and enforces 7-year document retention. Built for sole traders and limited companies operating in Sweden.
Features
- Double-entry bookkeeping -- BAS 2026 chart of accounts, draft/commit workflow, sequential voucher numbering
- Invoicing -- Create, send, and track invoices with mixed VAT rates and PDF generation
- Bank reconciliation -- PSD2 bank connection via Enable Banking, 4-pass automatic matching
- VAT declaration -- SKV 4700 form mapping, per-rate breakdown, EU/export handling
- Tax reports -- NE-bilaga, INK2, SRU export for Skatteverket
- Supplier invoices -- Registration, payment tracking, input VAT deduction
- Document archive -- SHA-256 integrity, 7-year retention enforcement, full archive ZIP export
- SIE import/export -- Standard Swedish accounting interchange format
- Extension system -- Opt-in plugins for AI categorization, receipt OCR, email, calendar, and more
Self-Hosting
git clone https://github.com/erp-mafia/gnubok.git
cd gnubok
./setup.sh # Prompts for Supabase credentials, generates .env
docker compose up -d
You need a Supabase project and must apply the database migrations before first use. See SELF-HOSTING.md for the full step-by-step guide, including Supabase setup, auth configuration, optional features (AI, email, push notifications), and troubleshooting.
Development Setup
Prerequisites: Node.js 20+, a Supabase project.
npm install
npm run dev # Start dev server (auto-generates extension registry)
npm test # Run tests
npm run build # Production build
npm run lint # ESLint
Tech Stack
- Framework: Next.js 16 (App Router), React 19, TypeScript (strict)
- Database: Supabase (PostgreSQL + Row Level Security + email/password auth + TOTP MFA)
- Styling: Tailwind CSS 4 + shadcn/ui
- Integrations: Enable Banking (PSD2), Anthropic SDK, LangChain, OpenAI, Resend, JSZip
Documentation
- SELF-HOSTING.md -- Full self-hosting guide (Docker, Supabase setup, migrations, optional features)
- CLAUDE.md -- Architecture, bookkeeping engine, database conventions, extension system
- CONTRIBUTING.md -- Development workflow, code style, pull request process
- SECURITY.md -- Vulnerability reporting policy
Contributing
Contributions are welcome. See CONTRIBUTING.md for the full guide.
All commits require a DCO sign-off (git commit -s).
License
AGPL-3.0-or-later with an extension exception: third-party extensions that interact solely through the documented Extension API may be licensed under any terms, including proprietary. See LICENSE for details and NOTICE for third-party attributions.