Commit Graph
100 Commits
Author SHA1 Message Date
StyxX65andClaude Opus 5.5 5e0ce6996b TODO: how to verify a web-UI update actually restarted the app
The web-UI update restarts via os.execv in the same process, so systemd's
ActiveEnterTimestamp doesn't change. Check for a "[skus] Loaded" startup
line after the release was pushed instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 14:57:12 +02:00
StyxX65andClaude Opus 5.5 5e60d17d60 Release 1.7.13
Build — Windows, Linux & macOS / GDPRScanner / linux (push) Has been cancelled
Build — Windows, Linux & macOS / GDPRScanner / macos (push) Has been cancelled
Build — Windows, Linux & macOS / GDPRScanner / windows (push) Has been cancelled
Build — Windows, Linux & macOS / Create GitHub Release (push) Has been cancelled
- Fix: result cards (and the Excel report) showed account IDs instead of
  names, with no Elev/Ansat badge, for scheduled scans of editor-saved
  profiles.
- VERSION, CHANGELOG and manual version strings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 14:43:00 +02:00
StyxX65andClaude Opus 5.5 20156ffc93 Show account names and roles on cards from scheduled scans
Cards from scheduled scans of editor-saved profiles showed the account's
GUID as its name and no Elev/Ansat badge: the profile editor stored bare
id strings, the scheduler passes them straight to run_scan, and run_scan
normalised them to displayName=<id>, userRole="other". Before 1.7.10 the
scan-start profile overwrite kept repairing profiles to named objects,
which is why this surfaced now.

- run_scan: _enrich_bare_users() looks bare accounts up in the directory
  (list_users, falling back to lookup_user) for name and role.
- M365Connector.build_role_sku_map(): the SKU map /api/users built inline,
  now shared so cards and the account list classify roles identically.
  lookup_user() also returns assignedLicenses.
- Profile editor saves {id, displayName, userRole} (manual accounts keep
  email/manual), like the 💾 save.
- _accountPill: an account_name equal to account_id counts as missing;
  name and role are filled from the loaded user list, so existing cards
  display correctly without a rescan.
- Excel export: the scan's account-name map no longer breaks on bare id
  strings (.get() on a str raised and dropped the whole map).

Tests: tests/test_account_names.py (10); the run_scan test fails on the
old engine with "Collecting emails — guid-henrik…".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 14:40:14 +02:00
StyxX65andClaude Opus 5.5 c55db48894 Document the post-1.7.12 M365+GWS scan report and update checks
TODO.md: open entry for "M365 not scanning M365+GWS accounts after the
1.7.12 update" — symptom, what was verified, likely half-applied update,
server checks to run after an update, what to collect if it recurs,
rollback option, and two possible hardening steps.

routes/CLAUDE.md: note that half-applied updates look like code bugs and
to verify server state before debugging a post-update regression.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 10:44:55 +02:00
StyxX65andClaude Opus 5.5 04efb462af Release 1.7.12
Build — Windows, Linux & macOS / GDPRScanner / linux (push) Has been cancelled
Build — Windows, Linux & macOS / GDPRScanner / macos (push) Has been cancelled
Build — Windows, Linux & macOS / GDPRScanner / windows (push) Has been cancelled
Build — Windows, Linux & macOS / Create GitHub Release (push) Has been cancelled
- Fix: Google-only accounts no longer reach the M365 scan (Graph 404s);
  an M365 scan with no M365 accounts selected no longer scans everyone.
- VERSION, CHANGELOG and manual version strings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 10:30:01 +02:00
StyxX65andClaude Opus 5.5 b5476228ba Keep Google-only accounts out of the M365 scan
Graph returned 404 for /users/google:<email>/mailFolders: Google-
Workspace-only accounts (id "google:<email>") reached the M365 scan.

- getSelectedUsers() fell back to "every ticked checkbox in the DOM"
  whenever no M365 account was ticked — including Google-only rows. The
  fallback is now only used before the user list has loaded, and never
  includes google: rows.
- startScan() skips the M365 part (with a log line) when M365 sources are
  ticked but no M365 accounts are: the server treats an empty account
  list as "every user in the tenant".
- run_scan: _resolve_manual_users -> _resolve_scan_users, which now also
  drops google: ids (reported as a phase message). Covers scheduled scans
  and profiles that already contain Google-only accounts. Never widens to
  all users.

Tests: 3 new in tests/test_manual_users.py; the run_scan one fails on the
old engine with exactly the reported Graph call.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 10:28:05 +02:00
StyxX65andClaude Opus 5.5 b60c4a5c47 Release 1.7.11
Build — Windows, Linux & macOS / GDPRScanner / linux (push) Has been cancelled
Build — Windows, Linux & macOS / GDPRScanner / macos (push) Has been cancelled
Build — Windows, Linux & macOS / GDPRScanner / windows (push) Has been cancelled
Build — Windows, Linux & macOS / Create GitHub Release (push) Has been cancelled
- Fix: selecting a profile ticked (and would scan) every M365-only and
  GWS-only account — regression in 1.7.10.
- VERSION, CHANGELOG and manual version strings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 10:21:14 +02:00
StyxX65andClaude Opus 5.5 37c9cbfd2c Fix selecting a profile ticking every M365-only and GWS-only user
_applyPendingProfileUsers() set u.selected = matches(u), where matches()
was `wanted.has(u.id) || (u.googleEmail && ...)`. For users without a
googleEmail (every M365-only and Google-only user) that evaluates to
undefined, not false — and both renderAccountList() and getSelectedUsers()
treat anything other than false as selected. So applying a profile ticked,
and would have scanned, all of those users in addition to the profile's own.

Coerce the googleEmail branch to a boolean.

Reproduced against a copy of real data: a 1-account profile ticked 56
(37 M365-only + 18 GWS-only extras); after the fix it ticks 1, and every
profile ticks only its own stored accounts.

Regression from bbe9c62 (released in 1.7.10).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 10:15:36 +02:00
StyxX65andClaude Opus 5.5 5df151724a Release 1.7.10: changelog + manual updates
Build — Windows, Linux & macOS / GDPRScanner / linux (push) Has been cancelled
Build — Windows, Linux & macOS / GDPRScanner / macos (push) Has been cancelled
Build — Windows, Linux & macOS / GDPRScanner / windows (push) Has been cancelled
Build — Windows, Linux & macOS / Create GitHub Release (push) Has been cancelled
- VERSION 1.7.9 -> 1.7.10 (patch: fixes and small behaviour changes only).
- CHANGELOG: 1.7.10 entry covering everything since 1.7.9 — Stop fixes,
  profile/manual-account fixes, delete_message, landing card,
  classification data path, scan-thread crash logging, repo move.
- Manuals (EN/DA): version, Stop -> "Stopping…" and scheduled-job stop,
  scans no longer change profiles, manual accounts in the profile editor.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 10:07:01 +02:00
StyxX65andClaude Opus 5.5 606d8e944b Make Stop stop: no replay, no restart, immediate feedback
Build — Windows, Linux & macOS / GDPRScanner / linux (push) Has been cancelled
Build — Windows, Linux & macOS / GDPRScanner / macos (push) Has been cancelled
Build — Windows, Linux & macOS / GDPRScanner / windows (push) Has been cancelled
Build — Windows, Linux & macOS / Create GitHub Release (push) Has been cancelled
Pressing Stop could look like the scan restarting, and for scheduled jobs
it really did restart the Google stage.

- Scheduler: check the abort flag between stages and never clear the
  Google abort after a Stop (that started a fresh all-users Google scan).
  A stopped job is recorded as "cancelled" and skips email/retention;
  scheduler_done carries cancelled=true.
- SSE: scan_cancelled and file_scan_done now end the current scan like
  scan_done, and sse.replay_messages() never replays a finished scan.
  Previously a stopped M365 scan (and every file scan) stayed "current",
  so each reconnect replayed it and re-showed Stop + progress.
- Frontend: scan_cancelled clears the M365 running flag and only tears
  down the UI when no Google/file scan is still running; scheduler_done /
  scheduler_error clear all running flags.
- run_scan: Stop during collection now ends the scan (checkpoint, finish,
  scan_cancelled) instead of announcing scan_start / "Scanning…".
- Stop button: switches to a disabled "Stopping…" state on click; all
  show/hide goes through _setStopBtnDisplay(), which resets it when hidden
  or when a new scan starts.

Adds tests/test_stop_scan.py (10 tests).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 10:03:12 +02:00
StyxX65andClaude Opus 5.5 9cf23da907 Point docs at git.fejl40.nu after the move from GitHub
- README / CONTRIBUTING: clone from the Gitea repo.
- SECURITY: Gitea has no private security advisories; report to
  support@fejl40.nu instead.
- RELEASING: release URL on Gitea, plus a note that build.yml still uses
  the GitHub-only action-gh-release step, so no releases are produced on
  Gitea yet (server self-update via git is unaffected).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 09:49:06 +02:00
StyxX65andClaude Opus 5.5 1c79ac15b7 Scan manually added accounts
Build — Windows, Linux & macOS / GDPRScanner / linux (push) Has been cancelled
Build — Windows, Linux & macOS / GDPRScanner / macos (push) Has been cancelled
Build — Windows, Linux & macOS / GDPRScanner / windows (push) Has been cancelled
Build — Windows, Linux & macOS / Create GitHub Release (push) Has been cancelled
Manual accounts were never scanned:
- Sidebar "add user" entries had no platform, and getSelectedUsers()
  required platform === 'm365', so they were dropped from every scan.
- Profile-editor manual accounts were stored as "manual:<email>", which
  is not a Graph user id, and applying the profile never put them in the
  sidebar list, so they could not be selected.

Now the editor looks the email up via /api/users/lookup and stores the
directory id plus name/email (falling back to manual:<email> only when the
lookup fails). Applying a profile adds its manual accounts to the sidebar
and resolves legacy manual:<email> entries, logging any that can't be
found. The scan engine resolves remaining manual:<email> ids (e.g. from
scheduled scans) via the new M365Connector.lookup_user(), skipping and
reporting failures without widening to all users.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 09:27:56 +02:00
StyxX65andClaude Opus 5.5 bbe9c62ff9 Fix profiles losing their selected users
Starting an M365 scan wrote the sidebar state back into the active profile
via _save_settings(), replacing its user_ids with the sidebar selection
(minus Google-only users and anything hidden by the role filter). With no
profile active it overwrote profiles[0]. Profiles are loaders, so scan
start no longer writes them; profile_id is audit-only.

The profile editor built its account list from S._allUsers only, so
manually added accounts, users not yet loaded, and users gone from the
directory were not rendered and got dropped on the next save. They now
render as checked "Manual" / "Not loaded" rows, and user_ids "all" is kept
when saved before users load.

Applying a profile cleared the pending user IDs before the un-awaited
Google merge had added google:<email> users, leaving them unticked.
Pending IDs are now kept until all are matched or the user list is final,
and _mergeGoogleUsers() re-applies them. A saved google:<email> also
matches an M365 user merged to platform 'both'.

Adds regression tests for scan start leaving profiles untouched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 09:22:32 +02:00
StyxX65 b77bf70ff5 Fix deprecated smbprotocol.query_info import
Build — Windows, Linux & macOS / GDPRScanner / linux (push) Has been cancelled
Build — Windows, Linux & macOS / GDPRScanner / macos (push) Has been cancelled
Build — Windows, Linux & macOS / GDPRScanner / windows (push) Has been cancelled
Build — Windows, Linux & macOS / Create GitHub Release (push) Has been cancelled
smbprotocol.query_info is a thin deprecation shim (from smbprotocol.file_info
import * + a DeprecationWarning) scheduled for removal in the library's next
major release. Switched to importing FileDirectoryInformation directly from
smbprotocol.file_info, the actual current location - confirmed present there
in the installed package. Pure rename, no behavior change: all 227 tests
still pass, and the warning is gone from the test output.
2026-09-03 22:59:38 +02:00
StyxX65andClaude Sonnet 5 edc820051e Fix classification/ directory never matching where data files lived
Build — Windows, Linux & macOS / GDPRScanner / linux (push) Has been cancelled
Build — Windows, Linux & macOS / GDPRScanner / macos (push) Has been cancelled
Build — Windows, Linux & macOS / GDPRScanner / windows (push) Has been cancelled
Build — Windows, Linux & macOS / Create GitHub Release (push) Has been cancelled
The code has always expected classification/m365_skus.json and
classification/google_ou_roles.json (present since the initial commit
in m365_connector.py, google_connector.py, and build_gdpr.py's own
skus_dir variable) - but the actual data files lived in skus/ under a
different name (education.json) or just the wrong directory. Both
loaders degrade to a fallback on failure, so this was never fatal, but
it meant:

- M365 SKU-based role classification silently ran on fragment-matching
  only, never touching the real 13 student + 11 staff SKU list.
- Google Workspace OU-based role classification silently used a
  hardcoded 4-prefix fallback missing real categories the actual
  config has - /Elev, /Pupils (student), /Teachers, /Admin (staff).
  _load_ou_roles() had a bare `except Exception: return <fallback>`
  with no logging at all, so this had zero visibility anywhere.

Fix: moved both files to classification/ (git mv, so history is
preserved), updated .gitignore's exception rule from !skus/*.json to
!classification/*.json, fixed the stale "skus/google_ou_roles.json"
docstring, and added a module logger + proper warning/error logging to
_load_ou_roles() matching the pattern _load_sku_data() already used -
so if this class of bug recurs, it won't be silent again.

Verified: both loaders now log successful loads with the real counts
(13+11 SKUs, 4+6 OU prefixes including the previously-missing
categories), all 227 tests still pass, and a full app boot confirms
the startup warning is gone and the app still serves normally.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 22:58:24 +02:00
StyxX65 2260a451a5 Fix deprecated fitz import, log unhandled scan-thread crashes
Build — Windows, Linux & macOS / GDPRScanner / linux (push) Has been cancelled
Build — Windows, Linux & macOS / GDPRScanner / macos (push) Has been cancelled
Build — Windows, Linux & macOS / GDPRScanner / windows (push) Has been cancelled
Build — Windows, Linux & macOS / Create GitHub Release (push) Has been cancelled
document_scanner.py: `import fitz` is PyMuPDF's deprecated import
alias, scheduled for removal - switched both sites to `import pymupdf
as _fitz`. Pure rename, no behavior change (all 227 tests still pass).

routes/scan.py: the background scan thread caught no exceptions, so
an unexpected crash in run_scan() escaped into Python's default
unhandled-thread-exception hook instead of the app's own logging/audit
trail. The scan lock was already released correctly via `finally` -
now the crash itself is also logged (`_log.exception`, full traceback)
and audit-recorded before that. Confirmed by test_lock_released_when_
run_scan_raises: the PytestUnhandledThreadExceptionWarning it
triggered is now gone, with no change to the test's own assertions.
2026-09-03 22:55:07 +02:00
StyxX65andClaude Opus 4.8 efbbeb7306 Restore M365Connector.delete_message (was an orphaned method body)
Build — Windows, Linux & macOS / GDPRScanner / linux (push) Has been cancelled
Build — Windows, Linux & macOS / GDPRScanner / macos (push) Has been cancelled
Build — Windows, Linux & macOS / GDPRScanner / windows (push) Has been cancelled
Build — Windows, Linux & macOS / Create GitHub Release (push) Has been cancelled
The def line for delete_message had been lost, leaving its body as
unreachable dead code at the end of _delete() and no delete_message
attribute on the connector. Deleting an Outlook message therefore failed
with "'M365Connector' object has no attribute 'delete_message'". Restored
the method (soft-delete: move to Deleted Items, fall back to DELETE).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 15:43:46 +02:00
StyxX65andClaude Opus 4.8 54f8848e30 Document renderGrid landing-card hiding in static/js/CLAUDE.md
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 14:49:43 +02:00
StyxX65andClaude Opus 4.8 8a446509c6 Hide landing/last-scan card whenever results render
The live scan_file_flagged handler showed the grid but never hid
#emptyState / #lastScanSummary, so when a scan ran with the landing
card visible, results appeared underneath it until a manual refresh
(which re-ran loadOpenItems and cleared it). Hide both panels in
renderGrid whenever files are present, covering every render path
(live SSE, open-items load, history, filters).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 14:45:50 +02:00
StyxX65andClaude Opus 4.8 d55778ab35 Release 1.7.9: changelog + manual updates
Build — Windows, Linux & macOS / GDPRScanner / linux (push) Has been cancelled
Build — Windows, Linux & macOS / GDPRScanner / macos (push) Has been cancelled
Build — Windows, Linux & macOS / GDPRScanner / windows (push) Has been cancelled
Build — Windows, Linux & macOS / Create GitHub Release (push) Has been cancelled
Document this cycle's changes: open-items default results view,
interrupted-scan recovery, restored user/group badges, the SMTP
username-key fix, and the new "always send via SMTP" toggle. Stamp
manuals (EN/DA) to 1.7.9.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 11:36:41 +02:00
StyxX65andClaude Opus 4.8 874c3ccec1 Add "prefer SMTP" toggle to skip Microsoft Graph for email
When the M365 connector is connected the app always tries Graph first,
and a Graph 202 ends the send — so report mail to recipients Exchange
silently drops (Google-hosted subdomains of the O365 domain) never
reaches them, even with working SMTP configured.

New prefer_smtp flag gates all three Graph branches (smtp_test,
send_report, _maybe_send_auto_email) so they go straight to SMTP. UI
toggle #st-smtpPreferSmtp in Settings → E-mailrapport, saved/loaded by
scheduler.js, with da/de/en strings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 11:30:45 +02:00
StyxX65andClaude Opus 4.8 526e2b0b78 Fix SMTP auth: settings tab saved wrong config keys
The Settings → E-mailrapport tab (scheduler.js) saved the SMTP username
as `user` and TLS flag as `starttls`, but every backend reader expects
`username`/`use_tls` (routes/email.py). Result: username was always
empty, server.login() was skipped, and the SMTP server rejected the
send — surfacing as a misleading "authentication failed" message even
with a valid App Password. The bug was latent because Graph is preferred
whenever M365 is connected, so the SMTP path was rarely exercised.

- scheduler.js: send/load canonical keys (username, use_tls). The
  send-report modal (scan.js) already used these.
- _load_smtp_config(): normalise legacy user→username / starttls→use_tls
  so configs saved before the fix work without re-entry.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 11:25:15 +02:00
StyxX65andClaude Opus 4.8 b661a94f98 Restore user/group badges on DB-loaded result cards
The card badge only rendered when f.account_name was set, and the
group (role) badge was nested inside that same check. But save_item
never persisted account_name — only account_id (a GUID) and user_role.
Live SSE cards carried account_name so badges showed during a scan;
now that the grid loads finalized scans from the DB, the gap is exposed
and both badges vanish for earlier scans.

- Persist account_name (migration 11 + save_item) so future scans show
  the user badge. Both M365 and Google cards already carry it.
- _accountPill() in results.js drives the group badge off user_role
  alone (shows for legacy rows) and resolves a best-effort user label:
  account_name → S._allUsers (id/email) → email-style account_id → omit.
  Both card layouts share the one helper.

Legacy rows still lack account_name (never captured), but now show the
group badge and a resolved/email user label where possible.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 10:15:19 +02:00
StyxX65andClaude Opus 4.8 29d9168643 Recover unfinished scans so their items aren't stranded
get_session_items / get_open_items / latest_scan_id all require
finished_at IS NOT NULL, but the M365 and Google engines return early
on abort (skipping finish_scan) and a process kill mid-scan (deploy,
OOM, crash) never reaches it either. Result on prod: 41/42 scans had
finished_at NULL, so 291 already-saved flagged items were invisible —
the grid showed nothing.

- finalize_orphan_scans(): finalises every finished_at-NULL scan; runs
  once at startup before the scheduler (nothing is scanning at boot, so
  any unfinished scan is dead). Recovers existing stranded items and
  guards against future mid-scan restarts.
- run_scan: finalise the DB scan on the abort early-return too, so a
  stopped scan's items stay visible without waiting for a restart.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 09:51:22 +02:00
StyxX65 7bf589bf7a Update ZORAXY_SETUP.md 2026-06-22 09:21:08 +02:00
StyxX65andClaude Opus 4.8 68076eba52 Show all open (unactioned) items by default, not just the last scan
The default results view loaded only the latest scan session (±300s
window), so items dropped out of sight once a newer scan started — and
a long scheduled scan could show little or nothing on browser open.

Add get_open_items(): every flagged item with no disposition (or status
'unreviewed') across all scans, deduped by id to the latest finished
scan. GET /api/db/flagged now serves it when no ?ref is given; ?ref=N
still loads a specific past session. Frontend loadHistorySession(null)
routes to a new loadOpenItems() loader. Rename the banner button to
"Open items" (da/de/en).

get_session_items() default is unchanged — export.py and
scan_scheduler.py still rely on latest-session for the current scan's
report/email.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 09:19:55 +02:00
StyxX65andClaude Fable 5 67f66c8441 Document self-update system and related changes in CLAUDE.md
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-16 12:16:14 +02:00
StyxX65andClaude Fable 5 8bb482925f Release 1.7.8
Build — Windows, Linux & macOS / GDPRScanner / linux (push) Has been cancelled
Build — Windows, Linux & macOS / GDPRScanner / macos (push) Has been cancelled
Build — Windows, Linux & macOS / GDPRScanner / windows (push) Has been cancelled
Build — Windows, Linux & macOS / Create GitHub Release (push) Has been cancelled
- CHANGELOG: cut the 1.7.8 release (dated 2026-06-16); reset Unreleased.
- VERSION: 1.7.7 -> 1.7.8.
- Manuals (DA + EN): bump version stamps.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-16 11:56:12 +02:00
StyxX65andClaude Fable 5 f84c8516df Reliably restore last session on refresh after a server restart
The page-load restore was one-shot and bailed when a completed scan's
replayed scan_phase left a running flag set; sse_replay_done (the other
retry) only fires for a non-empty replay buffer, which is empty after a
restart — so refreshing post-update showed a blank grid despite the
results being in the DB. The watchdog now retries the restore on each
4s poll while nothing is shown and no scan runs, clearing stale flags
first. /api/scan/status also reports google_running separately so a
refresh during a live Google scan is no longer treated as idle.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-16 11:53:07 +02:00
StyxX65andClaude Fable 5 9fd1aa1f8a Manuals: describe new share-link create flow
After Create the form clears and the new link appears highlighted in
the Active links list, copied from there — not from a preview row.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-15 10:52:22 +02:00
StyxX65andClaude Fable 5 da356fb310 Release 1.7.7
Build — Windows, Linux & macOS / GDPRScanner / linux (push) Has been cancelled
Build — Windows, Linux & macOS / GDPRScanner / macos (push) Has been cancelled
Build — Windows, Linux & macOS / GDPRScanner / windows (push) Has been cancelled
Build — Windows, Linux & macOS / Create GitHub Release (push) Has been cancelled
- CHANGELOG: cut the 1.7.7 release (dated 2026-06-15); reset Unreleased.
- VERSION: 1.7.6 -> 1.7.7.
- Manuals (DA + EN): bump version stamps.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-15 10:12:00 +02:00
StyxX65andClaude Fable 5 bdba80e72d Remove stale link preview from share modal after create
The generated-link "Copy link:" row stayed visible after creating,
looking like the form hadn't reset — but the new link was already in
the Active links list with its own Copy button. Drop the redundant
preview row; on create, reset the form and briefly highlight the new
entry in the active list. Removes the now-dead shareNewLinkRow markup
and copyShareLink().

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-15 10:11:03 +02:00
StyxX65andClaude Fable 5 c26dd7d320 Add Zoraxy HTTPS setup guide, correct SECURITY.md bind address
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 15:20:33 +02:00
StyxX65andClaude Fable 5 841311a6bd Release 1.7.6
Build — Windows, Linux & macOS / GDPRScanner / linux (push) Has been cancelled
Build — Windows, Linux & macOS / GDPRScanner / macos (push) Has been cancelled
Build — Windows, Linux & macOS / GDPRScanner / windows (push) Has been cancelled
Build — Windows, Linux & macOS / Create GitHub Release (push) Has been cancelled
- CHANGELOG: cut the 1.7.6 release (dated 2026-06-11); reset Unreleased.
- VERSION: 1.7.5 -> 1.7.6.
- Manuals (DA + EN): bump version stamps.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 15:02:44 +02:00
StyxX65andClaude Fable 5 dd19be8bbf Close leaked listening socket on update restart
Werkzeug sets its server socket inheritable unconditionally, so the
os.execv restart carried it into the new process as a zombie listener:
one PID listening on both 5100 (never accepted) and 5101 (the real
server). Mark all fds above stderr close-on-exec before exec'ing so
the old socket dies and the new server rebinds the original port.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 15:01:17 +02:00
StyxX65andClaude Fable 5 c43725ca7f Release 1.7.5
Build — Windows, Linux & macOS / GDPRScanner / linux (push) Has been cancelled
Build — Windows, Linux & macOS / GDPRScanner / macos (push) Has been cancelled
Build — Windows, Linux & macOS / GDPRScanner / windows (push) Has been cancelled
Build — Windows, Linux & macOS / Create GitHub Release (push) Has been cancelled
- CHANGELOG: cut the 1.7.5 release (dated 2026-06-11); reset Unreleased.
- VERSION: 1.7.4 -> 1.7.5.
- Manuals (DA + EN): bump version stamps.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 14:42:06 +02:00
StyxX65andClaude Fable 5 a1712ae178 Make static files revalidate so the UI is fresh after updates
No Cache-Control header meant browsers cached JS/CSS heuristically for
days; after a server update (including the in-app self-update reload)
the backend was new but the frontend stayed stale. SEND_FILE_MAX_AGE
_DEFAULT=0 forces ETag revalidation — 304 when unchanged, fresh file
immediately after an update.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 14:39:45 +02:00
StyxX65andClaude Fable 5 c1cddb8ea7 Release 1.7.4
Build — Windows, Linux & macOS / GDPRScanner / linux (push) Has been cancelled
Build — Windows, Linux & macOS / GDPRScanner / macos (push) Has been cancelled
Build — Windows, Linux & macOS / GDPRScanner / windows (push) Has been cancelled
Build — Windows, Linux & macOS / Create GitHub Release (push) Has been cancelled
- CHANGELOG: cut the 1.7.4 release (dated 2026-06-10); reset Unreleased.
- VERSION: 1.7.3 -> 1.7.4.
- Manuals (DA + EN): bump version stamps.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 15:33:16 +02:00
StyxX65andClaude Fable 5 9cbd93e1f5 Reset all share modal fields after creating a link
Create only cleared the label; scope type, user email, date range, and
expiry carried over, so the next link silently inherited the previous
link's scope. Extracted openShareModal's reset logic into
_resetShareForm() and call it after every successful create.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 15:32:03 +02:00
StyxX65andClaude Fable 5 d4cf2db347 Release 1.7.3
Build — Windows, Linux & macOS / GDPRScanner / linux (push) Has been cancelled
Build — Windows, Linux & macOS / GDPRScanner / macos (push) Has been cancelled
Build — Windows, Linux & macOS / GDPRScanner / windows (push) Has been cancelled
Build — Windows, Linux & macOS / Create GitHub Release (push) Has been cancelled
- CHANGELOG: cut the 1.7.3 release (dated 2026-06-10); reset Unreleased.
- VERSION: 1.7.2 -> 1.7.3.
- Manuals (DA + EN): bump version stamps.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 15:24:27 +02:00
StyxX65andClaude Fable 5 d6bf80a68a Keep the same port across app restarts
The port probe did a plain bind() without SO_REUSEADDR, so TIME_WAIT
connections left by the previous instance (e.g. the in-app update
restart) made the port look occupied and the app hopped to the next
one. Probe with SO_REUSEADDR like Werkzeug binds, and give the
requested port a 10-second grace period before auto-incrementing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 15:23:18 +02:00
StyxX65andClaude Fable 5 679f91da2c Use page origin for share links except when browsing at localhost
The LAN-IP rewrite in _getShareBaseUrl() exists to fix unusable
127.0.0.1 links; applying it to every origin meant links copied behind
a reverse proxy pointed at http://<LAN-IP>:5100, bypassing TLS. HTTPS
and non-localhost origins are now used as-is.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 15:14:33 +02:00
StyxX65andClaude Fable 5 c79e7097ea Release 1.7.2
Build — Windows, Linux & macOS / GDPRScanner / linux (push) Has been cancelled
Build — Windows, Linux & macOS / GDPRScanner / macos (push) Has been cancelled
Build — Windows, Linux & macOS / GDPRScanner / windows (push) Has been cancelled
Build — Windows, Linux & macOS / Create GitHub Release (push) Has been cancelled
- CHANGELOG: cut the 1.7.2 release (dated 2026-06-10); reset Unreleased.
- VERSION: 1.7.1 -> 1.7.2.
- Manuals (DA + EN): bump version stamps.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 15:10:59 +02:00
StyxX65andClaude Fable 5 35e767b506 Fix copy buttons doing nothing over plain HTTP
navigator.clipboard is undefined in non-secure contexts, so the direct
writeText() call threw synchronously and the execCommand fallback in its
.catch() never ran. _copyText() now feature-detects the API, falls back
to execCommand('copy'), then to a prompt() for manual copying. log.js
reuses the helper; _getShareBaseUrl() caches the LAN-IP lookup so token
Copy buttons stay within the click gesture execCommand requires.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 15:09:34 +02:00
StyxX65andClaude Fable 5 652031b31d Release 1.7.1
Build — Windows, Linux & macOS / GDPRScanner / linux (push) Has been cancelled
Build — Windows, Linux & macOS / GDPRScanner / macos (push) Has been cancelled
Build — Windows, Linux & macOS / GDPRScanner / windows (push) Has been cancelled
Build — Windows, Linux & macOS / Create GitHub Release (push) Has been cancelled
- CHANGELOG: cut the 1.7.1 release (dated 2026-06-10); reset Unreleased.
- VERSION: 1.7.0 -> 1.7.1.
- Manuals (DA + EN): bump version stamps; document the new
  Settings -> General -> Software update group (check/install/auto-update,
  git-checkout-only, self-restart, refused during scans).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 14:50:31 +02:00
StyxX65andClaude Fable 5 df54b20735 Document software updates in README, refresh test suite table
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 14:47:58 +02:00
StyxX65andClaude Fable 5 a325349ecd Fix stale ~/.gdpr_scanner_* paths in help text, docs, and UI strings
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 14:41:23 +02:00
StyxX65andClaude Fable 5 6a4b0e1706 Show delta token source count, add hint bubble, fix README data paths
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 14:27:14 +02:00
StyxX65andClaude Fable 5 c0e45df440 Add software update from Settings GUI and update_gdpr.sh script
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 12:54:29 +02:00
StyxX65andClaude Fable 5 fcf32f3751 Release 1.7.0
- CHANGELOG: cut the 1.7.0 release (dated 2026-06-10); reset Unreleased.
- VERSION: 1.6.28 → 1.7.0.
- Manuals (DA + EN): bump version stamps; correct the redaction section
  (cards are now kept/greyed until the next scan, not removed) and add the
  same keep-until-next-scan note to the deletion section, including the
  partial-failure behaviour.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 12:06:36 +02:00
StyxX65andClaude Fable 5 95f1f39a1f Keep data-subject-deleted cards in grid until next scan
Apply the keep-until-next-scan behaviour to deleteSubjectItems: mark the
deleted items _deleted (using deleted_ids from the response) and keep them
greyed in the grid instead of filtering them out. Also fixes a latent bug
where renderGrid() was called with no argument and threw on files.forEach,
which the surrounding try/catch swallowed as a false "Delete failed" after a
successful erasure.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 11:47:52 +02:00
StyxX65andClaude Fable 5 386831c423 Keep bulk-deleted cards in grid until next scan
Extend the keep-until-next-scan behaviour to the bulk delete modal: instead
of removing matched cards on success, mark them _deleted and keep them greyed
with a "🗑 Deleted" badge and hidden buttons. /api/delete_bulk now returns
deleted_ids so the grid marks exactly the items the server actually deleted —
partial failures stay active and re-deletable. Already-handled (_deleted /
_redacted) items are excluded from the bulk-delete match set so they aren't
re-counted or re-processed.

201 tests pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 11:46:14 +02:00
StyxX65andClaude Fable 5 ed3c3a80d6 Keep deleted cards in grid until next scan
Mirror the redact behaviour for the card delete button (🗑): instead of
removing the card on success, mark the item _deleted and keep it in the grid
— greyed via card-resolved, shown with a red "🗑 Deleted" badge, action
buttons hidden so it can't be re-processed. The grid is rebuilt on the next
scan run, clearing the markers. results.js only — no server change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 11:44:10 +02:00
StyxX65andClaude Fable 5 7c1c2b390d Keep selected card in view when opening preview
Opening the preview panel narrows .grid-area and reflows the auto-fill grid
to fewer columns, moving the clicked card to a new row. The single-frame
scrollIntoView ran while the browser's scroll-anchoring re-adjusted scrollTop
mid-reflow, so the card scrolled out of view. Disable scroll anchoring on
.grid-area (overflow-anchor:none) and defer the scroll by two animation
frames against the settled layout, centring the card (block:'center').

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 11:35:04 +02:00
StyxX65andClaude Fable 5 d82a0d6004 Keep redacted cards in grid until next scan
Redacting a card (✏) previously removed it from the grid and from
S.flaggedData/S.filteredData immediately. Now the item is marked _redacted
and kept: greyed via card-resolved styling, shown with a "✏ Redacted" badge,
and its delete/redact buttons hidden so it can't be re-processed. The grid is
rebuilt on the next scan run, which clears the markers. results.js only — no
server change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 11:30:41 +02:00
StyxX65andClaude Fable 5 1b3d7f5698 Fix card action buttons clipped in grid view (missing position:relative)
The real cause behind the invisible redact/delete buttons: .card lacked
position:relative, so the position:absolute action buttons (delete, redact)
and the bulk-select checkbox anchored to the viewport instead of the card
and were clipped by .card overflow:hidden. They only showed in list view,
where those elements are position:static. Add position:relative to .card so
all three position within each card. Keep the 0.35 baseline opacity on the
redact button for discoverability.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 11:24:00 +02:00
StyxX65andClaude Fable 5 39500edfbc Changelog: note redact button visibility fix
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 11:21:37 +02:00
StyxX65andClaude Fable 5 35fd00437f Fix redact button invisible in grid view
.card-redact-btn had opacity:0 at rest (only opacity:1 on .card:hover), so
the ✏ redact button was completely invisible in the default grid/thumbnail
view — it only showed in list view, which forces opacity:1. Give it the same
0.35 baseline opacity as .card-delete-btn so it's discoverable at rest and
brightens on hover. The button was always rendered in the DOM; this is a
pure visibility fix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 11:20:06 +02:00
StyxX65andClaude Fable 5 c39d68ca19 Document XSS escaping + secret-encryption hardening
- CHANGELOG: add Unreleased ### Security section covering the stored XSS
  in the results grid, the reflected XSS in /api/thumb, and the Claude API
  key now being encrypted at rest.
- CLAUDE.md / static/js/CLAUDE.md: add the esc() / _html_esc escaping rule
  for scan-derived strings and the onclick-JSON &quot; pattern.
- CLAUDE.md / routes/CLAUDE.md: note that secret config fields use the
  machine-keyed Fernet and must be read via a decrypting accessor
  (get_claude_api_key()), never config.json directly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 11:15:39 +02:00
StyxX65andClaude Fable 5 b6d2915d49 Harden XSS escaping and encrypt Claude API key at rest
- results.js: add esc() helper and apply to all scan-derived fields
  (name, account_name, folder, source, modified, label, img alt) across
  card/list/preview/subject-lookup/related views. Scan-derived strings can
  carry attacker-controlled markup (e.g. a OneDrive file named with HTML),
  so they must be escaped before innerHTML/attribute embedding. Also escape
  the related-docs onclick JSON to match the delete/redact &quot; pattern.
- cpr_detector._placeholder_svg: escape label/name before embedding — served
  as image/svg+xml via /api/thumb?name=, so an unescaped value was a
  reflected-XSS vector when the URL is opened directly.
- cpr_detector: remove 44-line unreachable duplicate of the face-detection
  body left inside _extract_audio_metadata after its return.
- app_config: encrypt claude_api_key at rest with the machine-keyed Fernet
  (same as the SMTP password); add get_claude_api_key() for decryption.
  Legacy plaintext keys still read and are re-encrypted on next save.
  Update readers in document_scanner.py and routes/app_routes.py.

201 tests pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 11:06:36 +02:00
StyxX65 1903115e02 CLAUDE.md restructured 2026-06-08 14:44:37 +02:00
StyxX65 f845a2f686 ### Fixed - **Cards not shown after browser refresh** — when the browser reconnected to the SSE stream after a completed scan, the scan_phase events in the replay buffer temporarily set S._m365ScanRunning = true (all running flags start at false after a page reload). The watchdog's loadHistorySession call fired in this window and bailed on the stale flag; once scan_done cleared the flag, _initialStatusChecked was already true so loadHistorySession was never retried. Fixed by having the sse_replay_done handler retry loadHistorySession(null) when no scan is running and S._historyRefScanId is still null after replay. 2026-06-08 14:28:24 +02:00
StyxX65 79e589b525 Bugfix in Scheduler 2026-06-04 14:47:01 +02:00
StyxX65 fa6601ffdd Bugfixes 2026-06-01 15:15:43 +02:00
StyxX65 4e5a8934d7 Fix Google scan not stopping cleanly before a new scan starts 2026-05-29 04:53:42 +02:00
StyxX65 66986a16f9 ※ recap: Extended in-place CPR redaction to Google Drive, SFTP, SMB, and local PDFs, then updated CLAUDE.md and both manuals. Everything is committed and all 201 tests pass. (disable recaps in /config) 2026-05-28 17:53:53 +02:00
StyxX65 034ced943e Extended document redaction to Google Drive, SFTP, SMB, and local PDFs Extends the ✂ in-place redaction feature beyond local DOCX/XLSX/CSV/TXT files to cover all remaining file source types and adds PDF support for local files. 2026-05-28 17:47:02 +02:00
StyxX65 6ce7583b26 Added NER/AI integration 2026-05-28 11:50:10 +02:00
StyxX65 6e0dc8ee92 Minor changes to layout in Manuals 2026-05-28 11:23:20 +02:00
StyxX65andClaude Sonnet 4.6 26c45165b9 v1.6.28 — Scheduled report-only jobs, compliance audit log, and documentation update
- Scheduled jobs can now run in report-only mode (skip scan, email latest DB results)
- Compliance audit log records all significant admin actions in an immutable DB table
- VERSION bumped to 1.6.28; CHANGELOG [Unreleased] sealed as [1.6.28] — 2026-05-28
- Both manuals updated: CPR-only mode, OCR language, file redaction, related documents,
  date-range token scoping, report-only jobs, audit log tab, two new FAQ entries
- TODO.md updated with all completed tasks

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 11:08:52 +02:00
StyxX65andClaude Sonnet 4.6 744813f4ac Add compliance audit log
Immutable audit_log table in the scanner DB records every significant
admin action (profile save/delete, token create/revoke, PIN changes,
source add/update/delete, scheduler job changes, scan start/stop, SMTP
save, dispositions, item delete/redact). GET /api/audit_log exposes
entries newest-first. New Audit Log tab in the Settings modal renders
the table on demand. Settings modal widened 540→640 px and tab labels
set to white-space:nowrap so the six-tab row fits on one line.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 10:51:23 +02:00
StyxX65 4ef2dfb352 Date-range scoping for viewer tokens 2026-05-28 10:34:55 +02:00
StyxX65 c820d6f6db Two bugs in the abort mechanism: 1. POST /api/scan/stop only set state._scan_abort (M365/file abort event) but never touched state._google_scan_abort. Now sets both. 2. _check_abort() inside _run_google_scan imported gdpr_scanner._scan_abort (= state._scan_abort, the M365 event) instead of using the module-level _scan_abort alias (= state._google_scan_abort). This meant the dedicated /api/google/scan/cancel endpoint — which correctly sets _google_scan_abort — was silently ignored by the scan loop. Fixed to use the module-level alias consistently. Also aligned the end-of-scan checkpoint-clear check. 2026-05-28 10:20:22 +02:00
StyxX65andClaude Sonnet 4.6 7ffd8370f4 Fix Stop button not halting Google Workspace scan
Two bugs in the abort mechanism:

1. POST /api/scan/stop only set state._scan_abort (M365/file abort event)
   but never touched state._google_scan_abort. Now sets both.

2. _check_abort() inside _run_google_scan imported gdpr_scanner._scan_abort
   (= state._scan_abort, the M365 event) instead of using the module-level
   _scan_abort alias (= state._google_scan_abort). This meant the dedicated
   /api/google/scan/cancel endpoint — which correctly sets _google_scan_abort
   — was silently ignored by the scan loop. Fixed to use the module-level
   alias consistently. Also aligned the end-of-scan checkpoint-clear check.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 10:19:54 +02:00
StyxX65andClaude Sonnet 4.6 2c5f5d3283 Add OCR language override setting
Operators can now choose Tesseract language pack(s) per profile via a
sidebar select (#optOcrLang) and profile editor (#peOptOcrLang). Presets:
dan+eng (default), dan, eng, dan+eng+deu, dan+eng+swe, dan+eng+fra. The
ocr_lang option flows from the UI through all three scan engines (M365
files/attachments, Google Drive, Gmail) down to document_scanner.scan_pdf
and scan_image — including the spawned PDF-OCR subprocess worker.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 09:59:40 +02:00
StyxX65 23b9555dcf Built-in file redaction for local files 2026-05-27 14:49:06 +02:00
StyxX65andClaude Sonnet 4.6 c490b3d76a Merge remote CHANGELOG entries and add Preview section to CLAUDE.md
Resolved conflict in CHANGELOG.md: combined the two bug fixes from the
remote branch (stale history results, selected card scroll) with the
local Gmail/Drive preview fix under a single [1.6.26] — 2026-04-29 entry.
Added Preview dispatch documentation to CLAUDE.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 13:43:59 +02:00
StyxX65 051a53ae85 Update CHANGELOG.md 2026-05-27 13:40:21 +02:00
Henrik Højmark 99157e6fd7 Update CHANGELOG for version 1.6.26
Updated release date for version 1.6.26 and added detailed fixes related to scan history, card visibility, and Google Drive/Gmail previews.
2026-05-27 13:38:40 +02:00
StyxX65 78fb406422 Fixed two bugs: selected cards staying visible after preview opens, and stale history results showing when a new scan starts. 2026-04-29 15:18:58 +02:00
StyxX65 a76df463e8 Changelog updated 2026-04-27 18:47:43 +02:00
StyxX65 ce5a5f1cbb Fixed Gmail and Google Drive preview: items were being sent to the Microsoft Graph API instead of handled correctly. 2026-04-26 11:04:05 +02:00
StyxX65 d84e57239a Add CPR cross-referencing (related documents)
Clicking any flagged card that contains CPR hits now shows a "Related documents" section in the preview panel,
  listing other items from the same scan session that share at least one CPR number. Items are ordered by number of
  shared CPRs; clicking any entry opens it in the preview panel. Works in both live mode and scan history mode.

  Implementation
  - GDPRDb.get_related_items() — SQL self-join on the existing cpr_index table using the same symmetric 300 s session
  window as get_session_items. No new data collection needed.
  - GET /api/db/related/<item_id>?ref=N — new endpoint in routes/database.py, consistent with the ?ref convention used
   by /api/db/flagged.
  - #previewRelated div injected between the metadata block and disposition row in the preview panel.
  - _loadRelated(f) in results.js fetches and renders the list; window._openRelated() resolves items from the live
  grid or falls back to the API response for history-mode items.

  Also
  - Added keyword/FTS5 search as a deferred idea in SUGGESTIONS.md
  - Updated CHANGELOG.md, README.md, and CLAUDE.md
2026-04-25 21:15:50 +02:00
StyxX65 8b55e9d933 Extended the M365 checkpoint/resume mechanism to all three scan engines. Each engine writes its own +file (checkpoint_m365.json, checkpoint_google.json, checkpoint_file_{source_id}.json) every 25 + items. 2026-04-25 20:30:59 +02:00
StyxX65 2254e00481 recap: Added email and phone number detection as opt-in scan options across all three engines, plus translation fixes. Both CHANGELOG and SUGGESTIONS are updated — everything is committed and ready to test. 2026-04-25 19:33:28 +02:00
StyxX65 56a744d896 Fixed missing translation in Sources 2026-04-25 10:57:41 +02:00
StyxX65 9da4403bdf Update VERSION 2026-04-25 08:51:28 +02:00
StyxX65 e35bbe78a5 Added SFTP to sources 2026-04-25 08:48:54 +02:00
StyxX65 360eb1caed Bugfixes in media detection 2026-04-21 21:42:54 +02:00
StyxX65 d42518dc81 Added tests for Video & Audio
feat: video/audio metadata scanning, profile rename fix, route tests

  - Scan .mp4/.mov/.avi/.mkv and .mp3/.flac/.ogg/.m4a/.wma (+ 7 more)
    for GPS coordinates, artist/author, title, comment — metadata only,
    no frame or audio analysis. Uses mutagen (added to requirements.txt).
    GPS-tagged phone recordings now flag with gps_location like photos.

  - Fix _extract_audio_metadata silently returning empty results:
    mutagen.File() first positional arg is `filename`, not `fileobj` —
    was passing BytesIO as the filename. Fixed to keyword args.

  - Fix profile copy rename not reflected in left column until modal
    reopen: _pmgmtSaveFullEdit called loadProfiles() but never
    _renderProfileMgmt(). Added re-render and active-row highlight.

  - Add TestProfileRoutes (10 tests) covering all profile API endpoints
    including a rename regression test. Total: 182 tests.

  - generate_fixtures.py now produces 6 audio/video fixtures (14–19):
    2 MP3, 2 FLAC, 2 MP4 — 4 flagged, 2 negative cases.
2026-04-21 21:26:58 +02:00
StyxX65 2a2d79de90 Added testing of Profile 2026-04-21 20:51:37 +02:00
StyxX65 f7f1194d63 Fix: Profile copy rename not reflected in left column until modal reopen 2026-04-21 20:33:16 +02:00
StyxX65 08d811b329 Update README.md 2026-04-21 18:53:15 +02:00
StyxX65 f3a4c60136 Delete GDPR_ERRORLOG.md 2026-04-21 18:48:02 +02:00
StyxX65 c350014b16 fix: scan button stuck, CPR dedup crash, role scope filter, profile race conditions; add auto-email toggle and route integration tests 2026-04-21 18:43:25 +02:00
StyxX65 7c1afca80b Bugfixes
fix: select mode onclick exports, multi-source progress counter, OCR       page-by-page
2026-04-21 13:12:54 +02:00
StyxX65andClaude Sonnet 4.6 d8083eb0c0 feat: interface PIN, bulk disposition tagging, Google Drive delta scan, OCR memory fixes
- Interface PIN: optional session-level auth gate for the main scanner UI
  (Settings → Security → Interface PIN). Salted SHA-256 in config.json,
  rate-limited (5 attempts/5 min per IP). /view and viewer auth exempt.
  New /login page, before_request hook, GET/POST/DELETE /api/interface/pin,
  POST /api/interface/pin/verify, POST /api/interface/logout.

- Bulk disposition tagging: Select mode (filter bar "Vælg" button) reveals
  per-card checkboxes. Bulk tag bar at bottom of grid; POST /api/db/disposition/bulk.
  Disposition stats bar (total · unreviewed · retain · delete · % reviewed)
  updates after every save.

- Google Drive delta scan: uses Drive Changes API when delta is enabled.
  Per-user token stored as gdrive:{email} in delta.json. Load-then-merge
  save avoids racing with concurrent M365 token writes.

- PDF OCR OOM fix: render one page at a time with convert_from_path
  (first_page=N, last_page=N). Added _ocr_mem_ok() psutil guard (500 MB
  threshold) before each page render across scan_pdf, redact_fitz_pdf,
  redact_pdf.

- Email test message translation fix: routes/email.py returns structured
  {ok, method, recipients} instead of a hardcoded English string;
  scheduler.js builds the translated message client-side.

- Docs: CHANGELOG, README, TODO, MANUAL-EN, MANUAL-DA all updated.
  Lang files (en/da/de) extended with bulk, interface PIN, and SMTP keys.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 18:46:45 +02:00
StyxX65 b2bfa40f27 v1.6.20 — Scan history, user-scoped sharing, export fixes, email fixes
New features

  Scan history browser
  Results from any past scan session can now be reviewed without running a new scan. On page load the latest
  completed session is loaded automatically. A Sessions button opens a picker listing all past sessions with
  date, sources, item count, and Delta/Latest badges. All filters, exports, and disposition tagging work
  normally in history mode. Starting a new scan exits history mode.

  User-scoped viewer tokens (#34)
  Viewer token links can now be restricted to a specific employee so they only see their own flagged files —
  across both M365 and Google Workspace. The Share modal's scope selector gains a User option with a searchable
   name autocomplete. Selecting a person stores both their M365 and GWS email addresses; the server filters by
  account_id IN (list) so items from either platform are included. The viewer header shows the person's full
  name in a locked identity badge.

  ---
  Bug fixes

  GWS and local/SMB results missing from exports
  Two silent failures caused Google Workspace and file-scan results to disappear from Art.30 and Excel exports
  after a page reload:
  - google_scan.py called _db.end_scan() (method doesn't exist — should be finish_scan), so GWS scan records
  never got finished_at set and were permanently excluded from get_session_items()
  - google_scan.py emitted scan_done instead of google_scan_done, breaking SSE teardown logic
  - File scan called begin_scan() with keyword arguments it doesn't accept, silently leaving _db_scan_id = None
   so local/SMB items were never written to the database

  Graph sendMail reported as failure despite email being delivered
  _post() called r.json() unconditionally. Graph's sendMail returns HTTP 202 with no body on success, causing a
   JSONDecodeError that was caught and reported as a send failure. Fixed with r.json() if r.content else {}.

  Graph error hidden by generic SMTP message
  When Graph failed and no SMTP host was saved, the real Graph error was swallowed by "No SMTP host
  configured". The error is now surfaced directly.

  Gmail vs Google Workspace SMTP errors
  Auth failure messages now distinguish between personal Gmail (@gmail.com) and Google Workspace custom-domain
  accounts. Workspace errors point to the admin console (SMTP relay, 2-Step Verification policy) rather than
  the user's personal security settings.
2026-04-18 13:59:27 +02:00
StyxX65andClaude Sonnet 4.6 c9aab19a97 feat: scan history browser, user-scoped viewer tokens, export fixes, email fixes (v1.6.20)
- Scan history browser (history.js, GET /api/db/sessions, get_sessions(),
  get_session_items(ref_scan_id)) — review any past session without rescanning
- User-scoped viewer tokens (#34) — scope by individual employee across M365
  and GWS; autocomplete from Accounts list; dual-email support
- Fix: GWS scan never marked finished (end_scan → finish_scan) and emitted
  wrong SSE event (scan_done → google_scan_done), excluding GWS items from all
  exports
- Fix: file scan begin_scan called with wrong keyword args (TypeError swallowed),
  so local/SMB items were never written to DB
- Fix: Graph sendMail reported failure on success — _post() now returns {} on
  empty 202 response instead of raising JSONDecodeError
- Fix: Graph error hidden behind generic "No SMTP host" message when both Graph
  and SMTP were unavailable
- Fix: Gmail vs Google Workspace SMTP error messages distinguished by username
  domain; Workspace errors point to admin console, not personal security settings
- Docs: update README, MANUAL-EN, MANUAL-DA, CLAUDE.md, TODO.md, CHANGELOG.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 13:57:54 +02:00
StyxX65 e64d7eb958 Update DEPENDENCIES.md 2026-04-12 14:53:07 +02:00