Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
6 changes: 3 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,10 @@ stored score; bump it whenever a feature, weight, or threshold changes.
produces clear errors on encryption mode mismatches. The archive is as
sensitive as the live database — IMAP secrets are never included and must
be re-entered on the target machine via `nodary set-secret`.
- README donate Payment Link for Jacobrakai Foundation
(Donate / Support Jacobrakai Foundation — JACOBRAKAI FOUNDATION 501(c)(3)),
- README donate Payment Link for JACOBRAKAI FOUNDATION
(Donate / Support JACOBRAKAI FOUNDATION — JACOBRAKAI FOUNDATION 501(c)(3)),
plus `.github/FUNDING.yml` custom Stripe URL for the GitHub Sponsor button.
- README notes Jacobrakai Foundation 501(c)(3) status (EIN 33-3382083) and
- README notes JACOBRAKAI FOUNDATION 501(c)(3) status (EIN 33-3382083) and
IRS Letter 947 dated September 3, 2026.
- Privacy-invariant regression tests (`tests/test_privacy_invariants.py`) and
a `SECURITY.md` threat-model stub. After a synthetic sync with rich
Expand Down
9 changes: 5 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

## Ground rules (these are the product)

1. **Nothing leaves the machine.** No telemetry, no cloud calls, no external
scoring APIs, no "anonymous" stats. The only network connection this
codebase may open is the read-only IMAP session (and Flask bound to
127.0.0.1). PRs adding any other outbound traffic will be rejected.
1. **Nothing leaves the machine.** No telemetry, no cloud scoring APIs, no
"anonymous" stats. Allowed network use is the read-only IMAP session,
optional OAuth2 token refresh to the allowlisted Google/Microsoft token
endpoints in `src/nodary/auth/oauth2.py`, and Flask bound to 127.0.0.1.
PRs adding any other outbound traffic will be rejected.
2. **No new persisted content.** Body text, subjects, filenames, and full
URLs are never written to the database. If a feature needs new data,
derive the minimal structural value (count, extension, registrable
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ Recompute all derived profiles, tiers, and scores from locally stored facts:

```sh
uv run nodary rebuild
uv run nodary status
```

Run `nodary rebuild` after a `tldextract` upgrade to clear PSL drift. The
Expand Down Expand Up @@ -397,11 +398,11 @@ uv run nodary set-secret <account_id>
installations.
- Nodary never sends mail and never modifies mailbox contents.

## Jacobrakai Foundation
## JACOBRAKAI FOUNDATION

JACOBRAKAI FOUNDATION (EIN 33-3382083) is an IRC 501(c)(3) public charity
under 170(b)(1)(A)(vi), effective February 11, 2025. Contributions are
deductible. IRS Letter 947 dated September 3, 2026.

[Donate / Support Jacobrakai Foundation](https://donate.stripe.com/eVq4gy97DanS9h60phfrW00)
[Donate / Support JACOBRAKAI FOUNDATION](https://donate.stripe.com/eVq4gy97DanS9h60phfrW00)
— JACOBRAKAI FOUNDATION 501(c)(3)
12 changes: 7 additions & 5 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,11 @@ verdict that a message is malicious.
- Bind the dashboard off `127.0.0.1`, or load external dashboard assets.
- Publish red-team or exploit writeups against third-party mail providers.

The only network connection the codebase may open is the read-only IMAP
session (plus Flask bound to localhost). Public Suffix List data, the
freemail list, and confusables are vendored; they are not fetched at
runtime.
Allowed network use is the read-only IMAP session, optional OAuth2 token
refresh to the allowlisted Google/Microsoft token endpoints in
`src/nodary/auth/oauth2.py`, and Flask bound to localhost. Public Suffix
List data, the freemail list, and confusables are vendored; they are not
fetched at runtime.

## Privacy invariants

Expand Down Expand Up @@ -82,4 +83,5 @@ and `tests/test_ui.py`.

This is an offline local app. If Nodary persists forbidden content, writes
credentials into the database, or opens outbound traffic beyond read-only
IMAP, open a GitHub issue against this repository.
IMAP and the allowlisted OAuth2 token endpoints, open a GitHub issue against
this repository.
2 changes: 1 addition & 1 deletion docs/DESIGN.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Nodary Design

Status: **current implementation, v0.3.0**. The database schema in
Status: **current implementation, v0.3.1**. The database schema in
`src/nodary/storage/schema.sql` is authoritative for table/column details.

Nodary is a local-first, deterministic behavioral-analysis tool for BEC and
Expand Down
4 changes: 2 additions & 2 deletions docs/SECURITY-BOUNDARIES.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ Local-first, read-only analysis. This document states the isolation invariants t
## Trust boundary

- **Process:** `nodary sync` and `nodary ui` run as the local user. Dashboard binds `127.0.0.1` only (never `0.0.0.0`). No cloud scoring, no telemetry.
- **Network:** No outbound requests after install. `tldextract` uses vendored PSL snapshot (`suffix_list_urls=()`), confusables from `data/uts39/confusables.txt`; `auth/oauth2.py` allowlists only Google + Microsoft token endpoints.
- **Network:** After install, outbound traffic is limited to read-only IMAP and the allowlisted Google/Microsoft OAuth2 token endpoints in `auth/oauth2.py`. `tldextract` uses a vendored PSL snapshot (`suffix_list_urls=()`); confusables come from `data/uts39/confusables.txt`.
- **TLS:** `ui/tls.py` generates mkcert local CA cert; fallback plain HTTP warns. No secrets in DB.

## Data projection allowlist

`ui/server.py:_SCORED_MESSAGE_COLS` is the ONLY projection for scored rows. It explicitly excludes subject, body, filenames, full URLs. The dashboard is self-contained HTML (`src/nodary/ui/templates/index.html` — 605 lines, inline style+script, `tests/test_ui.py` asserts no `http://` `src=`).
`ui/server.py:_SCORED_MESSAGE_COLS` is the ONLY projection for scored rows. It explicitly excludes subject, body, filenames, full URLs. The dashboard is self-contained HTML (`src/nodary/ui/templates/index.html` — inline style+script; `tests/test_ui.py` asserts no `http://` `src=`).

```sql
_SCORED_MESSAGE_COLS = "m.id, m.message_id, m.sender_id, m.from_email_norm,
Expand Down
4 changes: 2 additions & 2 deletions docs/TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ work; `CHANGELOG.md` remains the release-history source of truth.
structure parsing without persisting body text, subjects, filenames, or
full URLs. Why: keeps the behavioral profile privacy-preserving.
- [x] Sender/domain profiles, trust tiers 0-3, and deterministic scoring engine
v1.0.0 with stored feature explanations. Why: scores are recomputable and
inspectable.
v1.1.0 (`ENGINE_VERSION`) with stored feature explanations. Why: scores
are recomputable and inspectable.
- [x] `nodary rebuild` replay path. Why: profiles, tiers, and scores can be
regenerated from fact tables after backfills or schema/profile changes.
- [x] SQLCipher-at-rest support keyed from the OS keychain, plus visible
Expand Down
Loading