Pentest fixes and hardening#321
Conversation
Satisfies SLSA v1.2 Source track requirement for a documented Safe Expunging Process. Covers scope, permitted reasons, two-maintainer approval, procedure, and consumer notification.
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Bitnami removed bitnami/nginx:1.24 from Docker Hub in August 2025, breaking compose builds. Point to the bitnamilegacy mirror that Bitnami publishes for deprecated tags.
The celery-ocr service defined in docker-compose.yml builds with ROLE=celery-ocr, but flask/Dockerfile only handled 'flask' and 'celery' branches, so the image had no Python runtime and the container exited 127 with 'celery: not found'. Share the celery install path with celery-ocr, which needs the same deps.
The compose file defaulted ENV_FILE to .env, which is the local development file where POSTGRES_HOST=localhost. That mount made the bayanat container try to reach postgres via a local socket instead of the compose service. .env.docker already ships with the correct service hostnames (postgres, redis) and is the intended default for this compose file.
flask create-db builds the full schema from models, so running db upgrade after it conflicts on indexes the latest migrations try to create. Detect fresh vs existing DB via flask db current.
Swap ubuntu:24.04 for python:3.12-slim-bookworm in the runtime stage and use the official ghcr.io/astral-sh/uv image as the builder. Keeps the ROLE-based conditional install for celery-only deps (exiftool, ffmpeg) and drops incidental ubuntu bulk that is not actually used by the app. - builder stage installs only build headers; runtime stage installs only shared libraries (libpq5, pango, cairo, harfbuzz, libxml2, libxslt, libjpeg62-turbo, libopenjp2, libffi8, dejavu fonts) - libimage-exiftool-perl is kept in the builder because pyexifinfo's setup.py probes for the exiftool binary during wheel install - XDG_CACHE_HOME=/tmp/.cache silences fontconfig cache warnings during weasyprint PDF generation Verified end-to-end against a fresh compose stack: weasyprint PDF gen, psycopg2, pillow, lxml, exiftool, ffmpeg, nginx->uwsgi->Flask login all work. Image sizes drop ~160MB (bayanat) and ~210MB (celery).
The previous `service nginx status` check failed on the bitnami nginx image, which has no sysvinit, so the container always reported unhealthy even when nginx was serving correctly. Switch to a bash TCP probe against port 80 — no external tools required (curl and wget are not present in the image).
## Summary - Installer's systemd unit for `bayanat-celery` only subscribed to the default `celery` queue, but `ocr_single` tasks route to a dedicated `ocr` queue (`enferno/tasks/__init__.py`). - Any bulk OCR dispatched via the admin UI (`POST /admin/api/ocr/bulk`) or the `flask ocr process` CLI piled up in Redis with no consumer. - Single-media OCR (per-item UI button / `flask ocr extract`) was unaffected — it takes a sync path through `process_media_extraction_task()`. ## Fix Add `-Q celery,ocr` to the `ExecStart` line so the worker consumes both queues. ## Affected versions - v4.0.0 (GA install command ships the broken unit). ## Upgrade path ### Fresh install Nothing special — new installs get the fixed unit. ### Existing v4.0.0 installs (in-place patch, no reinstall needed) ``` sudo sed -i.bak 's|worker --autoscale 2,5 -B$|& -Q celery,ocr|' /etc/systemd/system/bayanat-celery.service sudo systemctl daemon-reload sudo systemctl restart bayanat-celery ``` Verify with `sudo journalctl -u bayanat-celery --since "30 seconds ago" | grep queues` — should list both `celery` and `ocr`. ## Test plan - [x] Reproduced on auto-update-simplified deployment: 6 tasks stuck in `ocr` queue with 0 processed, worker idle. - [x] Applied the same fix to prod2 manually: queue drains, `ocr_single` tasks processed end-to-end via Google Vision API. - [x] All 4 test media processed successfully (confidence 24-98%, zero failures). - [ ] Tag `v4.0.1` after merge and update docs installer URL to pin v4.0.1.
Bulletin/actor/incident history routes only checked the global view_*_history permission, never the per-item access of the parent record. A user with history-view permission but no group access to a restricted item could read its full revision payload via the history API. Resolve the parent entity and gate on current_user.can_access(parent) before the history query. Log denied attempts to Activity. Location history is unaffected (Location has no role-based access).
The PUT /api/extraction/<id> endpoint resolved the Extraction row directly by ID without re-checking the parent Media's group membership, mirroring the GET sibling. Any DA/Admin could overwrite text/status on extractions in groups they don't belong to, and the success response leaked the full text+history payload back. Resolve the parent Media, gate on current_user.can_access(media), and trim the success response to to_compact_dict so even authorised calls don't echo the full text/history block.
api_csv_analyze, api_xls_sheet and api_xls_analyze concatenated the caller-supplied filename onto IMPORT_DIR with no containment check, so '../../../../app/.env' resolved outside the import directory and the resulting file content was returned as parsed CSV. With Admin credentials this leaked SECRET_KEY, SECURITY_TOTP_SECRETS and SECURITY_PASSWORD_SALT. Add _resolve_import_path() that joins via werkzeug.safe_join, resolves symlinks, and asserts the candidate is inside IMPORT_DIR. Reject with 400 on traversal or missing filename, with a warning log.
The /login endpoint had no server-side throttle: Flask-Limiter was only attached to /csrf, the session-scoped failure counter is reset by starting a new session, and reCAPTCHA is off by default. An attacker could issue an unbounded number of POSTs against /login. Add a Redis-backed throttle keyed independently on (username) and (ip) with a 15-minute sliding window: 10 failures per username, 30 per IP. Throttle check runs in before_app_request for POST /login and returns 429 once either ceiling is hit. Counters increment in after_app_request on failed login and clear on success. Failed attempts are logged via the regular logger; reCAPTCHA stays as an optional secondary friction layer.
Interactive CRUD runs Bulletin.description / ActorProfile.description through SanitizedField, but the import helpers wrote raw, untrusted strings straight to those columns. Both fields are rendered with v-html in BulletinCard and ActorProfiles, so an attacker-controlled CSV cell or external metadata payload could carry stored XSS that fires when an analyst opens the record. Wrap the import-side writes in sanitize_string() so the same bleach allowlist used by SanitizedField applies before persistence. Covers sheet_import.set_description (actor profile), and the three bulletin.description sinks in media_import (update_description, video info description, text_content).
One test file covering 001 / 002 / 004 / 007 / 008. Each test mirrors the auditor's PoC payload so a future regression flips the test. Mix of e2e (history endpoint, extraction PUT, traversal endpoints) and unit (login throttle helpers, sanitize_string) depending on what was practical to wire through fixtures. 7ASec retest map: run 'uv run pytest tests/test_pentest_fixes.py -v'.
The previous fix reinvented sliding-window rate limiting on top of raw Redis incr/expire. Replace it with two stacked Flask-Limiter decorators applied to security.login view post-registration: one keyed by username, one by IP. Storage, TTL, sliding window, 429 response and X-RateLimit-* headers all come from the existing limiter setup. Limits live in settings.py (LOGIN_RATE_LIMIT_PER_USERNAME and LOGIN_RATE_LIMIT_PER_IP), env-overridable, no UI exposure. TestConfig uses tighter values so the e2e test trips quickly. Drops the custom helpers in rate_limit_utils.py, the throttle code in user/views.py, the _FakeRedis test class, three throttle helper unit tests, and the weak resolver unit test. Adds one e2e test that posts bad credentials repeatedly until 429. Net: -94 lines, leaning on a battle-tested library.
The installer created the bayanat role with createuser -s (full superuser) and inserted 'local all bayanat trust' into pg_hba.conf. Any local OS user could then open a socket connection and claim the bayanat role with no password and full superuser privileges. Drop -s on createuser so the role is a plain owner. Create pg_trgm and postgis as the postgres superuser at install time so the app role never needs CREATE EXTENSION privilege. Replace the 'trust' pg_hba rule with 'peer' so only processes running as the bayanat OS user can authenticate as the bayanat PG role. Idempotent for upgrades: existing installs get ALTER USER ... NOSUPERUSER and the trust rule is removed before the peer rule is inserted.
Celery export tasks (PDF / JSON / CSV / media) iterated the caller-supplied IDs and serialised every row, with no per-record group check. A user with can_export but no group access could submit restricted IDs in an export request; once an admin approved it (the approval UI gives no signal that items cross access boundaries), the requester received the full payload. Add _accessible_items helper that yields only rows the export requester is authorised to see (mirrors current_user.can_access on the direct GET path) and route all three generator tasks plus the media-attachment task through it. CSV path uses an inline check because each iteration mutates a DataFrame. Restricted items get a warning log; missing requester yields nothing (fail closed). UI surface (admin approval warning when items are out-of-group) deferred to a follow-up; this commit closes the data leak.
…r CLI The /api/create-admin route was reachable over the network without authentication during the install window. The supported quick-install script started uWSGI + Caddy before the operator visited the wizard, so the first network client could claim a fully privileged admin account. The same hole reopened any time the admin role had zero users. Drop the route entirely (and its sibling /api/check-admin). The installer now provisions the admin out-of-band: it generates a high-entropy password and runs 'flask install --username admin --password ...' before the service is exposed, then prints the credentials once for the operator. The setup wizard now requires an authenticated Admin session and starts from the language step. flask install gains --username and --password options for non-interactive use; if username is given without a password it generates and prints one. Drive-by: pre-commit ruff dropped 4 pre-existing unused imports (Bulletin, DynamicField, DateHelper, celery_app) and one redundant f-string in commands.py.
The credentials banner already prints username + password, but the operator still has to construct the login URL themselves. Add the fully-qualified /login URL to the banner and explain what the wizard covers after sign-in, so the post-install handoff is one copy + one click.
The native installer (bayanat script) provisions the initial admin via flask install before the service is reachable. The Docker entrypoint was not aligned: it ran create-db on a fresh volume but never created an admin, leaving Pattern A's network surface (wizard requires auth, no admin = no login) effectively unusable for compose users. Add a flask install --username admin call after schema creation. With no --password supplied the command generates a random one and prints the credentials banner to stdout, which docker-compose logs flask captures so the operator can retrieve it after first start.
Two issues from review: - The entrypoint comment referenced 'docker-compose logs flask' but the service in docker-compose.yml is named bayanat. - The deployment docs still told operators to run 'flask install' after startup, which now silently no-ops because the entrypoint bootstrapped the admin already, leaving them without credentials. Fix the comment and rewrite the docs: the first-run password is in 'docker-compose logs bayanat | grep -A4 "Generated password"'; the CLI fallback only mints a fresh password if no admin exists.
Reviewer flagged that the native installer's _bootstrap_admin generated
the password in shell and passed it via 'flask install --password "$pw"',
exposing it through /proc/<pid>/cmdline and 'ps' for the lifetime of
the child.
Add a --password-stdin flag to the flask install CLI (mirroring the
docker login --password-stdin pattern). The installer now feeds the
password over a pipe:
printf '%s\n' "$pw" | flask install --username admin --password-stdin
The password never appears in argv or environ. The Docker entrypoint
already used the safer shape (flask install --username admin generates
and prints) and is unchanged.
f-string substitution into postgresql:// and redis:// URLs broke when passwords contained URL-special characters (/, @, #, +, =). Surfaced during Docker bootstrap testing: a base64 password with '/' caused 'Port could not be cast to integer' at app boot. quote(safe='') around POSTGRES_USER/POSTGRES_PASSWORD/REDIS_PASSWORD fixes Postgres URI plus all four Redis URLs (broker, result, session, cache). Verified roundtrip via redis.from_url decode.
## Description Brings back the native PDF viewer (iframe) for normal viewing and keeps the canvas viewer for OCR. Default: native viewer (text selection, search, zoom, etc.) OCR dialog: still uses canvas renderer No changes to OCR pipeline ## How to Test Open a PDF → verify it uses the browser native pdf viewer with their own tools (marker, zoom, etc) Open OCR dialog → verify pdf canvas viewer + OCR still work Check fullscreen works Check images and DOCX still work ## Jira ID (if applicable) [BYNT-1679](https://syriajustice.atlassian.net/browse/BYNT-1679) [BYNT-1679]: https://syriajustice.atlassian.net/browse/BYNT-1679?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ --------- Co-authored-by: Nidal Alhariri <level09@gmail.com>
## Summary Patch release addressing 11 open Dependabot advisories in `uv.lock` (high + medium severity). All fixes are minor/patch-level dependency bumps. No code changes. | Package | From | To | Severity | Advisory | |---|---|---|---|---| | lxml | 6.0.2 | 6.1.0 | **high** | [GHSA-pp7h-53gx-mx7r](GHSA-pp7h-53gx-mx7r) — XXE in iterparse/ETCompatXMLParser | | pillow | 12.1.1 | 12.2.0 | **high** | [GHSA-2vfv-wwj6-7q47](https://github.com/advisories/GHSA-2vfv-wwj6-7q47) — FITS GZIP decompression bomb | | pypdf | 6.10.0 | 6.10.2 | medium | 3 advisories — RAM exhaustion via crafted PDFs | | python-dotenv | 1.2.1 | 1.2.2 | medium | symlink-following in set_key | | Mako | 1.3.10 | 1.3.11 | medium | path traversal in TemplateLookup | | pytest (dev) | 9.0.2 | 9.0.3 | medium | vulnerable tmpdir handling | `pip <= 26.0.1` (alert #159) is also flagged but no patched version is available yet upstream — left for a follow-up. Frontend advisories (`dompurify`, `vite`, `uuid` in `docs/package-lock.json`) are scoped to the docs site and tracked separately. ## Test plan - [x] `uv sync --extra dev` clean - [x] `uv run pytest -q` — **773 passed, 4 skipped** in 29.5 s - [ ] After merge, bump `production` and tag v4.0.2
|
BAY-01-015 looks fixed. I tested by changing the export ID in the URL/API to another user’s export. The server returned 403 Forbidden, so the export metadata is no longer exposed. Minor note: 403 vs 404 can still hint that an export ID exists, but that is low impact and not the main issue. Unrelated UX issues noticed while testing:
|
Bulletin/Actor to_dict() embedded full media metadata (filename, etag, title, OCR extraction text) in the medias array for any user who could read the parent, even those blocked from the media endpoints by _require_media_access. A non-Admin without can_access_media therefore still received the data the direct endpoints deny them. Gate the medias loop on a shared can_view_media() helper that mirrors the _require_media_access policy (Admin or can_access_media) and trusts CLI/Celery via has_request_context, matching check_history_access.
The single-OCR endpoint requires can_edit (assignment boundary) since OCR writes an extraction, but /api/ocr/bulk scoped items only by can_access (visibility), letting a non-Admin with can_access_media OCR-mutate media they can read but are not assigned to edit. Post-filter the resolved media ids by can_edit for non-Admins. The query-level access filter can't express can_edit (needs the parent's assignment + status), so it is done in Python; Admins skip the filter.
|
BAY-01-018 looks fixed for the main reported issue. I tested a bulk Bulletin update as a Mod with:
Only Bulletin 12 was updated. Activity Monitor only showed Bulletin 12, and only Bulletin 12 got a new revision history entry. Separate issue found while testing Incident bulk updates: Test:
Result: So the original restricted-ID logging issue seems fixed, but assignRelated can still create duplicate history entries for related Bulletins. |
|
BAY-01-022 looks fixed for the normal Bulletin update API, but I found a remaining bulk-update edge case. Test:
So the normal edit path is protected, but bulk update can still mutate peer-review-locked Bulletins. |
## Summary
Web/upload imports could silently get stuck in **Pending** with no error
log on the import row, even though the celery worker logged a traceback.
We hit this in production when Postgres dropped an idle connection:
`etl_process_file` raised `PendingRollbackError`, the original exception
was masked, and the import row was never marked Failed.
Two fixes:
1. **Defensive error handling in `etl_process_file`**
(`enferno/tasks/data_import.py`)
- Call `db.session.rollback()` before reusing the session in the except
branch. Otherwise a poisoned session causes the fail-marker query to
raise, masking the real exception and leaving the row stuck in Pending.
- Guard the fail-marker itself so a secondary failure logs and doesn't
replace the original exception.
2. **Connection health checks on the SQLAlchemy engine**
(`enferno/settings.py`)
- `pool_pre_ping=True`: ping each pooled connection before use, so
workers don't get handed a dropped connection.
- `pool_recycle=300` (overridable via `SQLALCHEMY_POOL_RECYCLE`): cycle
connections every 5 min, well under typical NAT/firewall idle timeouts.
Together: the first fix makes the symptom impossible regardless of root
cause; the second stops the underlying class of error from happening in
the first place.
## Observed in production
```
PendingRollbackError: Can't reconnect until invalid transaction is rolled back.
File "enferno/tasks/data_import.py", line 33, in etl_process_file
log = db.session.get(DataImport, data_import_id)
```
The line number points inside the except clause; the original exception
inside `MediaImport(...)` / `di.process(file)` was lost. We also saw
`OperationalError: server closed the connection unexpectedly` on the
same worker pool earlier the same day, confirming the connection-drop
pattern.
## Test plan
- [ ] Run existing test suite (`uv run pytest`) on a Postgres with a low
idle timeout and confirm no regressions.
- [ ] Trigger a web import and verify normal happy path still produces a
bulletin and `Ready` status.
- [ ] Force a transient DB error during `etl_process_file` (e.g. kill
the worker's connection) and confirm the import row ends in `Failed`
with the original exception in the log instead of `Pending`.
## Summary - `PERMANENT_SESSION_LIFETIME` is currently hardcoded to 3600s. This makes it configurable via the `SESSION_LIFETIME` environment variable, defaulting to 3600 (no behavior change for existing installs). - Lets operators tune the idle session timeout (Flask refreshes the cookie/Redis TTL on every request, so this effectively controls idle expiry) without code changes. ## Test plan - [ ] Unset `SESSION_LIFETIME` → sessions expire after 3600s of inactivity (unchanged default). - [x] Set `SESSION_LIFETIME=300` in `.env` → sessions expire after 5 min of inactivity. - [x] Service starts cleanly with the env var present and absent.
## Summary PR #312 replaced the `request.referrer` check on `/admin/api/media/chunk` with `request.form.get(\"source\") == \"import\"`. Dropzone drops its `params` option on chunked POSTs, so the form field is missing on every chunk request, `import_upload` is always `False`, and in S3 mode the chunk endpoint takes the **S3 push + local delete** branch. The import ETL then runs and finds no local file: ``` ValueError: The filename given was either non existent or was a directory File \"enferno/data_import/utils/media_import.py\", line 447, in upload_import info = exiflib.get_json(filepath)[0] ``` Every import in S3 mode has been failing since #312 merged. ## Fix Move `source=import` from Dropzone's `params` to the URL query string and check `request.args` instead of `request.form`. Query params live on the URL and ride along on every chunk POST regardless of how Dropzone shapes the body. Same security posture as the form-body check — both come from the client. The actual access control is the `current_user.has_role(\"Admin\")` gate three lines down, which is unchanged. ## Test plan - [ ] Upload a video via the media import tool with `FILESYSTEM_LOCAL=False`. Confirm the file lands in `enferno/media/` locally for ETL, then ends up in S3 after `MediaImport.upload()`. - [ ] Upload a video via the media import tool with `FILESYSTEM_LOCAL=True`. Confirm normal local-only behavior still works. - [ ] Confirm normal (non-import) Dropzone uploads under Bulletin/Actor still go straight to S3 in S3 mode. - [ ] Confirm a non-admin user can't bypass the extension check by hitting `/admin/api/media/chunk?source=import` directly (admin role gate still applies).
# Conflicts: # enferno/commands.py # enferno/settings.py # uv.lock
## Summary In-app visual redaction for document and image media. An analyst draws boxes over a PDF or scan, Bayanat burns them in permanently and saves a clean redacted copy as a **new** Media row. The original is never modified. ## How it works - **Engine** (`enferno/utils/redaction_utils.py`): PDFs via PyMuPDF `add_redact_annot` + `apply_redactions` + `scrub` (destructive: content removed, not overlaid); images via Pillow flatten to JPEG (EXIF dropped). - **Endpoint** `POST /api/media/<id>/redact` (Admin/DA): reads source bytes, burns, writes a new file, creates the Media row plus a `media_redaction` audit row (source, regions, user), logs an Activity. Existing redactions can also be deleted (#354). - **Available on bulletins and actors**: redact icon on the media card next to the OCR icon. - **Many copies of one doc**: redacted copies are grouped by their source media id and collapsed behind a "Show N redactions" toggle, so a document redacted once per detainee does not clutter the record. Optional per-copy label keeps them findable; copies can be edited in place. - **Migrations**: `e4f2a9c8d7b1` (additive `media_redaction` table) and `68396035f041` (`original_media_id` link). Both additive with clean downgrades, no data risk. - **Docs**: Redaction guide included. ## Status Ready for review and merge. UX has been finalized with the team and the flow has been validated on staging (sta.bayanat.org). ## Review focus - Irreversibility: PDF text/content actually gone under boxes; scanned image-only pages blanked per region (not the whole page); image EXIF dropped. - Original Media row/file immutability (redaction always produces a new row). - Access control on the endpoint (`@roles_accepted("Admin","DA")` + `can_access`). --------- Co-authored-by: Daniel Apodaca <apodacaduron@gmail.com> Co-authored-by: Daniel Apodaca <47479471+apodacaduron@users.noreply.github.com>
## Summary The `stable` branch was a Git Flow holdover pinned to commit `0b6f5f9` (the original notification system PR #50, hundreds of commits behind `main`). It carried no unique commits, no unique tags, and no documentation referenced it. Keeping it around was actively misleading — anyone browsing the branch list and assuming `stable` was the production branch would have been wrong by years of work. It has been deleted from origin. Releases are now tagged directly from `main` following modern trunk-based practice (which is what we just did with `v4.0.0`). This PR removes the four workflow files that listed `stable` as a push trigger so they no longer reference a non-existent branch. ## Changes - `.github/workflows/checks.yml` (Tests: pytest) — removed `- stable` - `.github/workflows/pip-audit.yml` (Security: Python) — removed `- stable` - `.github/workflows/fe-js-audit.yml` (Security: JavaScript) — removed `- stable` - `.github/workflows/semgrep.yml` (Security: SAST) — removed `- stable` All four workflows still trigger on `pull_request`, `workflow_dispatch`, `push` to `main`, and (where applicable) their existing `schedule:` cron entries. Functionality is unchanged. ## Verification before deletion Verified `stable` was safe to delete before pulling the trigger: - `git merge-base --is-ancestor origin/stable main` → YES (every commit on stable was already on main) - `git log origin/stable ^main` → empty (zero unique commits) - `git tag --merged origin/stable` vs `git tag --merged main` → no tags uniquely reachable from stable - No documentation, install guide, deploy script, or CODEOWNERS referenced `stable` ## Test plan - [ ] CI runs on this PR (pull_request trigger still works) - [ ] After merge, push to main still triggers all four workflows
## Description This PR updates several vendored front-end dependencies and adjusts the app’s static imports to match the new asset layout. ### Dependency Versions - `Vue`: before `3.5.21`, after `3.5.38` - `Axios`: before `1.16.0`, after `1.18.1` - `Sortable`: before `1.15.6`, after `1.15.7` - `Dropzone`: before `unknown`, after `5.9.3` - `LightGallery`: before `2.8.3`, after `2.9.0` - `Video.js`: before `unknown`, after `8.23.8` - `Force Graph`: before `1.42.18`, after `1.51.4` - `Croppr`: before `unknown`, after `2.3.0` - `docx-preview`: before `unknown`, after `0.3.7` ### Integration Updates - switches Dropzone imports to the new vendored path - disables Dropzone autodiscovery in `VueDropzone` to avoid double initialization - removes obsolete vendored files that are no longer referenced This is primarily a front-end dependency refresh, with the supporting app changes needed to keep existing pages and upload flows working with the updated assets. ## How To Test 1. Open shared app pages like dashboard, login, and setup wizard. Expected: pages render normally and no new console errors appear. 2. Open admin `Actors`, `Bulletins`, `Incidents`, and `Locations`. Expected: pages load correctly and normal interactions still work. 3. Test upload flows in admin/activity and import pages. Expected: Dropzone initializes once, drag-and-drop works, and uploads complete successfully. 4. Test sheet/media import pages with drag-and-drop ordering. Expected: Sortable/draggable behavior still works. 5. Spot check gallery/visualization pages. Expected: LightGallery and force-graph based views still load and behave normally.
Bumps [postcss](https://github.com/postcss/postcss) from 8.5.9 to 8.5.14. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/postcss/postcss/releases">postcss's releases</a>.</em></p> <blockquote> <h2>8.5.14</h2> <ul> <li>Fixed custom syntax regression (by <a href="https://github.com/43081j"><code>@43081j</code></a>).</li> </ul> <h2>8.5.13</h2> <ul> <li>Fixed <code>postcss-scss</code> commend regression.</li> </ul> <h2>8.5.12</h2> <ul> <li>Fixed reading any file via user-generated CSS.</li> <li>Added <code>opts.unsafeMap</code> to disable checks.</li> </ul> <h2>8.5.11</h2> <ul> <li>Fixed nested brackets parsing performance (by <a href="https://github.com/offset"><code>@offset</code></a>).</li> </ul> <h2>8.5.10</h2> <ul> <li>Fixed XSS via unescaped <code></style></code> in non-bundler cases (by <a href="https://github.com/TharVid"><code>@TharVid</code></a>).</li> </ul> </blockquote> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/postcss/postcss/blob/main/CHANGELOG.md">postcss's changelog</a>.</em></p> <blockquote> <h2>8.5.14</h2> <ul> <li>Fixed custom syntax regression (by <a href="https://github.com/43081j"><code>@43081j</code></a>).</li> </ul> <h2>8.5.13</h2> <ul> <li>Fixed <code>postcss-scss</code> commend regression.</li> </ul> <h2>8.5.12</h2> <ul> <li>Fixed reading any file via user-generated CSS.</li> <li>Added <code>opts.unsafeMap</code> to disable checks.</li> </ul> <h2>8.5.11</h2> <ul> <li>Fixed nested brackets parsing performance (by <a href="https://github.com/offset"><code>@offset</code></a>).</li> </ul> <h2>8.5.10</h2> <ul> <li>Fixed XSS via unescaped <code></style></code> in non-bundler cases (by <a href="https://github.com/TharVid"><code>@TharVid</code></a>).</li> </ul> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/postcss/postcss/commit/3ec13948ae0006e1bde2dfb545346341ac8b2dcf"><code>3ec1394</code></a> Release 8.5.14 version</li> <li><a href="https://github.com/postcss/postcss/commit/f2bb827b20b591080977412555aa3e5baf588620"><code>f2bb827</code></a> Update dependencies</li> <li><a href="https://github.com/postcss/postcss/commit/d75953d60854ad835fd21dde0b11081522341020"><code>d75953d</code></a> Merge pull request <a href="https://redirect.github.com/postcss/postcss/issues/2084">#2084</a> from 43081j/raw-raws-rawing</li> <li><a href="https://github.com/postcss/postcss/commit/68bd2139b5dcaf5a682bc2e8826d8557be2d1480"><code>68bd213</code></a> fix: always call <code>raw</code> to retrieve raw values</li> <li><a href="https://github.com/postcss/postcss/commit/af58cf1b7af02e9b9fcb138a4a2d7ef3450158b1"><code>af58cf1</code></a> Release 8.5.13 version</li> <li><a href="https://github.com/postcss/postcss/commit/f227dbd0e9443e5f33e18e633b8b4d2b55aac5ee"><code>f227dbd</code></a> Temporary ignore pnpm 11 config</li> <li><a href="https://github.com/postcss/postcss/commit/d3abd40d723cf3559e5ddb5fc738b7cb64e92bb0"><code>d3abd40</code></a> Update dependencies</li> <li><a href="https://github.com/postcss/postcss/commit/dd06c3e11362087bc18f9c20cee30fd82bda3de9"><code>dd06c3e</code></a> Revert stringifier changes because of the conflict with postcss-scss</li> <li><a href="https://github.com/postcss/postcss/commit/ae889c815fb88d785401a88f1a7dfc8cb11915fb"><code>ae889c8</code></a> Try to fix CI</li> <li><a href="https://github.com/postcss/postcss/commit/e0093e49bcf00347383a13e40bb1f67bc823ca15"><code>e0093e4</code></a> Move to pnpm 11</li> <li>Additional commits viewable in <a href="https://github.com/postcss/postcss/compare/8.5.9...8.5.14">compare view</a></li> </ul> </details> <br /> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/sjacorg/bayanat/network/alerts). </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Nidal Alhariri <level09@gmail.com>
Adds a Web Import guide page to the public docs. Web import (importing media from a URL via yt-dlp) was previously undocumented, and the proxy and cookies settings under System Administration appeared nowhere in the guide. The page covers: - What web import is, and how it differs from spreadsheet Data Import - Enabling it under System Administration and the three settings (proxy, allowed domains, cookies) - Proxy configuration, including the easiest option: a local Tor relay (`apt install tor` then `socks5h://127.0.0.1:9050`) - Cookies in Netscape format for authenticated downloads, with a throwaway-account warning - How the download flow works (proxy applied, cookie retry on auth failure) Also adds the page to the guide sidebar after Data Import. Verified with `vitepress build`.
…357) ## Problem Redacting an image whose orientation comes from an **EXIF tag** (rather than physically rotated pixels) produced two symptoms: the black boxes landed in the wrong place, and the saved redacted copy came back at 0°, not the orientation the user was looking at in the redaction dialog. Reported from a DA; reproduced by editing only the EXIF orientation tag of a sample (physically-rotated samples worked fine). ## Root cause `redact_image_bytes` opened the image with `Image.open(...).convert("RGB")`, which **ignores EXIF orientation**. Browsers do the opposite: they honor EXIF, so the redaction UI displayed the image rotated and captured the boxes in that oriented space. The backend then drew on the raw, un-rotated pixels and saved a JPEG with EXIF stripped, so both the box positions and the output orientation diverged from what the user saw. The DB `orientation` axis (manual/OCR rotation) was already handled via `rotate_rect_to_original`; EXIF was the missing axis. ## Fix Normalize with `ImageOps.exif_transpose` before drawing, so the backend operates on the same pixels the browser displayed. This is the existing house pattern: the OCR ingest path (`enferno/utils/ocr/image.py`) already does the exact same thing. Redaction was simply the one image path that skipped it. ## Test Added a test that builds a landscape image tagged EXIF orientation 6 (displayed portrait), redacts the displayed top-left quadrant, and asserts the output is the transposed dimensions with the box in the right place. Fails without the fix, passes with it. Full redaction suite green. --------- Co-authored-by: Daniel Apodaca <apodacaduron@gmail.com>
## What Cap `flask-security-too` to `>=5.7.0,<5.8.0`. ## Why `pip-audit` (CI) flags **GHSA-f66q-9rf6-8795**, a WebAuthn reauthentication freshness bypass affecting flask-security-too **5.8.0–5.8.1**. The advisory has **no patched release** yet (5.8.1 is the current latest on PyPI), so there is nothing to upgrade *to*. The project was already resolving/locking to **5.7.1**, which is **not** affected. CI failed only because `pip-audit .` re-resolves the open-ended `>=5.7.0` constraint to the newest matching release (5.8.1) rather than the shipped 5.7.1. Capping below 5.8 keeps both the resolver and the auditor on 5.7.1. ## Scope / risk - **No version change** — resolved version stays 5.7.1 (uv.lock diff is a single specifier line). - **No template work** — Bayanat overrides several flask-security templates with custom Vue-based ones; since the version is unchanged, none are affected. - Only `pyproject.toml` (+ a comment noting the trigger to lift the cap) and `uv.lock` change. ## Verified - `uv run pip-audit .` → **No known vulnerabilities found** (exit 0). - Full test suite: **782 passed, 4 skipped** (identical to baseline). Lift the cap once a patched 5.8.x/5.9 ships and the overridden security templates are re-verified against it.
## Summary Actor relations weren't mirroring correctly. If you related actor A to actor B as "Parent," both actors would show "Parent" instead of B correctly showing "Child." Depending on which actor you created the relation from, the relation could also get saved backwards in the database, not just displayed wrong. This is now fixed for all 5 directional relation types (Parent/Child, Superior/Subordinate officer, Unit/Subunit, Alleged Perpetrator/Injured Party, Member/Group), on both creating a new relation and editing an existing one's type — including editing from the bulletin or incident form, and relating during actor creation before the new actor has an id. Symmetric types (Sibling, Spouse, Same Person, Duplicate, Other) and cross-type relations (Actor↔Bulletin, Actor↔Incident) are unaffected — no mirroring is needed for those since they never had this problem. No backend or database changes — this is a frontend-only fix. Storage stays exactly as it was; the correct label is computed based on which actor you're viewing, and the correct type is saved based on which actor you're relating from. ## Test plan - [x] Create two actors, note which has the lower id and which has the higher id. - [x] Relate them as Parent/Child, starting from the lower-id actor. - [x] Lower-id actor shows "Parent". - [x] Higher-id actor shows "Child". - [x] Now do the same starting from the higher-id actor instead — pick "Parent" from there. - [x] Confirm it still comes out correct on both sides. - [x] Repeat for at least one more directional pair (e.g. Superior officer/Subordinate officer). - [x] Edit an existing relation's type (e.g. change Parent to Child) — confirm both actors update correctly. - [x] Create a brand new actor, relate an existing actor as Parent during creation (before saving), then check both profiles after save. - [x] From the bulletin edit form, change a related actor's relation type (e.g. Witness to Perpetrator) — confirm it saves exactly what you picked, not a different type. - [x] Sanity-check a symmetric type (e.g. Sibling) still shows the same on both sides. - [x] Sanity-check Actor↔Bulletin and Actor↔Incident relations are unaffected when related normally (no type change). ## Jira ID (if applicable) [BYNT-1721](https://syriajustice.atlassian.net/browse/BYNT-1721) [BYNT-1721]: https://syriajustice.atlassian.net/browse/BYNT-1721?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ --------- Co-authored-by: level09 <level09@gmail.com>
Bumps [dompurify](https://github.com/cure53/DOMPurify) from 3.3.3 to 3.4.7. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/cure53/DOMPurify/releases">dompurify's releases</a>.</em></p> <blockquote> <h2>DOMPurify 3.4.7</h2> <ul> <li>Hardened the handling of Shadow Roots when using <code>IN_PLACE</code>, thanks <a href="https://github.com/GameZoneHacker"><code>@GameZoneHacker</code></a></li> <li>Removed a problem leading to permanent hook pollution, thanks <a href="https://github.com/offset"><code>@offset</code></a></li> <li>Refactored the test suite and expanded test coverage significantly</li> </ul> <h2>DOMPurify 3.4.6</h2> <ul> <li>Fixed several issues with DOM Clobbering in <code>IN_PLACE</code> mode, thanks <a href="https://github.com/offset"><code>@offset</code></a> & <a href="https://github.com/Bankde"><code>@Bankde</code></a></li> <li>Hardened the checks for cross-realm <code>IN_PLACE</code> and Shadow DOM sanitization, thanks <a href="https://github.com/offset"><code>@offset</code></a> & <a href="https://github.com/Bankde"><code>@Bankde</code></a></li> <li>Added more test coverage for <code>IN_PLACE</code> and general DOM Clobbering attacks</li> <li>Bumped several dependencies where possible</li> </ul> <h2>DOMPurify 3.4.5</h2> <ul> <li>Fixed a bypass caused by the new HTML element <code>selectedcontent</code> added in 3.4.4, thanks <a href="https://github.com/KabirAcharya"><code>@KabirAcharya</code></a></li> </ul> <p><strong>Note that this is a security release for an issue introduced in 3.4.4 and should be upgraded to immediately.</strong></p> <h2>DOMPurify 3.4.4</h2> <ul> <li>Added the <code>selectedcontent</code> element to default allow-list, thanks <a href="https://github.com/lukewarlow"><code>@lukewarlow</code></a></li> <li>Added the <code>command</code> and <code>commandfor</code> attributes to default allowed-list, thanks <a href="https://github.com/lukewarlow"><code>@lukewarlow</code></a></li> <li>Added better template scrubbing for <code>IN_PLACE</code> operations, thanks <a href="https://github.com/DEMON1A"><code>@DEMON1A</code></a></li> <li>Added stronger checks for cross-realm windows, thanks <a href="https://github.com/DEMON1A"><code>@DEMON1A</code></a> & <a href="https://github.com/fg0x0"><code>@fg0x0</code></a></li> <li>Updated demo website and made sure it uses the latest from main</li> <li>Updated existing workflows, fuzzer, dependabot, etc., added more tests</li> <li>Bumped several dependencies where possible</li> </ul> <p>🚨 <strong>This release had been flagged as deprecated, please use DOMPurify 3.4.5 instead</strong> 🚨</p> <h2>DOMPurify 3.4.3</h2> <ul> <li>Fixed an issue with handling of nested Shadow DOM trees, thanks <a href="https://github.com/fishjojo1"><code>@fishjojo1</code></a></li> <li>Fixed the template regexes to be more robust against ReDoS attacks, thanks <a href="https://github.com/aleung27"><code>@aleung27</code></a></li> <li>Updated the node iteration code to catch more Shadow DOM related issues</li> <li>Updated Playwright and added Node 26 to test matrix</li> <li>Updated existing workflows, fuzzer, release signing, etc., added more tests</li> <li>Bumped several dependencies where possible</li> </ul> <h2>DOMPurify 3.4.2</h2> <ul> <li>Fixed an issue with URI validation on attributes allowed via <code>ADD_ATTR</code> callback, thanks <a href="https://github.com/nelstrom"><code>@nelstrom</code></a></li> <li>Fixed an issue with source maps referring to non-existing files, thanks <a href="https://github.com/cmdcolin"><code>@cmdcolin</code></a></li> <li>Updated existing workflows, fuzzer, release signing, etc., added more tests</li> <li>Bumped several dependencies where possible</li> </ul> <h2>DOMPurify 3.4.1</h2> <ul> <li>Fixed an issue with on-handler stripping for HTML-spec-reserved custom element names (<code>font-face</code>, <code>color-profile</code>, <code>missing-glyph</code>, <code>font-face-src</code>, <code>font-face-uri</code>, <code>font-face-format</code>, <code>font-face-name</code>) under permissive <code>CUSTOM_ELEMENT_HANDLING</code></li> <li>Fixed a case-sensitivity gap in the <code>annotation-xml</code> check that allowed mixed-case variants to bypass the basic-custom-element exclusion in XHTML mode</li> <li>Fixed <code>SANITIZE_NAMED_PROPS</code> repeatedly prefixing already-prefixed <code>id</code> and <code>name</code> values on subsequent sanitization</li> <li>Fixed the <code>IN_PLACE</code> root-node check to explicitly guard against non-string <code>nodeName</code> (DOM-clobbering robustness)</li> <li>Removed a duplicate <code>slot</code> entry from the default HTML attribute allow-list</li> <li>Strengthened the fast-check fuzz harness with explicit XSS invariants, an expanded seed-payload corpus, an additional idempotence property for <code>SANITIZE_NAMED_PROPS</code>, and a negative-control assertion ensuring the invariants actually fire</li> <li>Added regression and pinning tests covering the above fixes and two accepted-behavior contracts (<code>SAFE_FOR_TEMPLATES</code> greedy scrub, hook-added attribute handling)</li> <li>Extended CodeQL analysis to run on <code>3.x</code> and <code>2.x</code> maintenance branches</li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/cure53/DOMPurify/commit/ca30f070c360df162a3e3848e80e6fd3c9e74bff"><code>ca30f07</code></a> release: 3.4.7 (<a href="https://redirect.github.com/cure53/DOMPurify/issues/1414">#1414</a>)</li> <li><a href="https://github.com/cure53/DOMPurify/commit/bb7739e5bccec7e1ab3dae3f3e42d02db3acaaae"><code>bb7739e</code></a> release: 3.4.6 (<a href="https://redirect.github.com/cure53/DOMPurify/issues/1394">#1394</a>)</li> <li><a href="https://github.com/cure53/DOMPurify/commit/011b0c78f2a0f57ee54f5fcccb697a46ca6e63ea"><code>011b0c7</code></a> release: 3.4.5 (<a href="https://redirect.github.com/cure53/DOMPurify/issues/1382">#1382</a>)</li> <li><a href="https://github.com/cure53/DOMPurify/commit/5817ad969c15e67dfcd6cb37248d6e9c1553e7c3"><code>5817ad9</code></a> release: 3.4.4 (<a href="https://redirect.github.com/cure53/DOMPurify/issues/1374">#1374</a>)</li> <li><a href="https://github.com/cure53/DOMPurify/commit/520edb0371a9638f9b51f1798051299a250c686b"><code>520edb0</code></a> release: 3.4.3 (<a href="https://redirect.github.com/cure53/DOMPurify/issues/1352">#1352</a>)</li> <li><a href="https://github.com/cure53/DOMPurify/commit/6f67fd396a7b8c64294343999fe607ca1f5299c0"><code>6f67fd3</code></a> Sync/3.4.2 (<a href="https://redirect.github.com/cure53/DOMPurify/issues/1322">#1322</a>)</li> <li><a href="https://github.com/cure53/DOMPurify/commit/5b0cdbbf52331e854c0a2de875b1a3790ecec2b8"><code>5b0cdbb</code></a> chore: merge main into 3.x for 3.4.1 release (<a href="https://redirect.github.com/cure53/DOMPurify/issues/1301">#1301</a>)</li> <li><a href="https://github.com/cure53/DOMPurify/commit/09f59115a311469de5b625225760593e551f080a"><code>09f5911</code></a> test: added three more browsers to test setup (OSX, mobile)</li> <li><a href="https://github.com/cure53/DOMPurify/commit/5b16e0b892e82b1779d62b9928b43c4c4ff290b9"><code>5b16e0b</code></a> Getting 3.x branch ready for 3.4.0 release (<a href="https://redirect.github.com/cure53/DOMPurify/issues/1250">#1250</a>)</li> <li>See full diff in <a href="https://github.com/cure53/DOMPurify/compare/3.3.3...3.4.7">compare view</a></li> </ul> </details> <details> <summary>Install script changes</summary> <p>This version adds <code>prepare</code> script that runs during installation. Review the package contents before updating.</p> </details> <br /> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) You can trigger a rebase of this PR by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/sjacorg/bayanat/network/alerts). </details> > **Note** > Automatic rebases have been disabled on this pull request as it has been open for over 30 days. Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Nidal Alhariri <level09@gmail.com>
Bumps [uuid](https://github.com/uuidjs/uuid) to 14.0.0 and updates ancestor dependency [mermaid](https://github.com/mermaid-js/mermaid). These dependencies need to be updated together. Updates `uuid` from 11.1.0 to 14.0.0 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/uuidjs/uuid/releases">uuid's releases</a>.</em></p> <blockquote> <h2>v14.0.0</h2> <h2><a href="https://github.com/uuidjs/uuid/compare/v13.0.0...v14.0.0">14.0.0</a> (2026-04-19)</h2> <h3>⚠ BREAKING CHANGES</h3> <ul> <li>expect <code>crypto</code> to be global everywhere (requires node@20+) (<a href="https://redirect.github.com/uuidjs/uuid/issues/935">#935</a>)</li> <li>drop node@18 support (<a href="https://redirect.github.com/uuidjs/uuid/issues/934">#934</a>)</li> </ul> <h3>Features</h3> <ul> <li>drop node@18 support (<a href="https://redirect.github.com/uuidjs/uuid/issues/934">#934</a>) (<a href="https://github.com/uuidjs/uuid/commit/dc4ddb87272ed2843faccd130bcc41d492688bd3">dc4ddb8</a>)</li> </ul> <h3>Bug Fixes</h3> <ul> <li>expect <code>crypto</code> to be global everywhere (requires node@20+) (<a href="https://redirect.github.com/uuidjs/uuid/issues/935">#935</a>) (<a href="https://github.com/uuidjs/uuid/commit/f2c235f93059325fa43e1106e624b5291bb523c4">f2c235f</a>)</li> <li>Use GITHUB_TOKEN for release-please and enable npm provenance (<a href="https://redirect.github.com/uuidjs/uuid/issues/925">#925</a>) (<a href="https://github.com/uuidjs/uuid/commit/ffa31383e8e4e1f0b4e22e504561272041b8738c">ffa3138</a>)</li> </ul> <h2>v13.0.2</h2> <h2><a href="https://github.com/uuidjs/uuid/compare/v13.0.1...v13.0.2">13.0.2</a> (2026-05-04)</h2> <h3>Bug Fixes</h3> <ul> <li>rerelease to fix provenance. (<a href="https://github.com/uuidjs/uuid/commit/49ccb35f78c0c4ce1409dd2f1d89f83caadba10b">49ccb35</a>)</li> </ul> <h2>v13.0.1</h2> <h2><a href="https://github.com/uuidjs/uuid/compare/v13.0.0...v13.0.1">13.0.1</a> (2026-04-27)</h2> <h3>Bug Fixes</h3> <ul> <li>backport fix for GHSA-w5hq-g745-h8pq (<a href="https://github.com/uuidjs/uuid/commit/9d27ddf7046ce496ef39569ff84d948eeff9cb2a">9d27ddf</a>)</li> </ul> <h2>v13.0.0</h2> <h2><a href="https://github.com/uuidjs/uuid/compare/v12.0.0...v13.0.0">13.0.0</a> (2025-09-08)</h2> <h3>⚠ BREAKING CHANGES</h3> <ul> <li>make browser exports the default (<a href="https://redirect.github.com/uuidjs/uuid/issues/901">#901</a>)</li> </ul> <h3>Bug Fixes</h3> <ul> <li>make browser exports the default (<a href="https://redirect.github.com/uuidjs/uuid/issues/901">#901</a>) (<a href="https://github.com/uuidjs/uuid/commit/bce9d72a3ae5b9a3dcd8eb21ef6d1820288a427a">bce9d72</a>)</li> </ul> <h2>v12.0.1</h2> <h2><a href="https://github.com/uuidjs/uuid/compare/v12.0.0...v12.0.1">12.0.1</a> (2026-04-29)</h2> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/uuidjs/uuid/blob/main/CHANGELOG.md">uuid's changelog</a>.</em></p> <blockquote> <h2><a href="https://github.com/uuidjs/uuid/compare/v13.0.0...v14.0.0">14.0.0</a> (2026-04-19)</h2> <h3>Security</h3> <ul> <li>Fixes <a href="https://github.com/uuidjs/uuid/security/advisories/GHSA-w5hq-g745-h8pq">GHSA-w5hq-g745-h8pq</a>: <code>v3()</code>, <code>v5()</code>, and <code>v6()</code> did not validate that writes would remain within the bounds of a caller-supplied buffer, allowing out-of-bounds writes when an invalid <code>offset</code> was provided. A <code>RangeError</code> is now thrown if <code>offset < 0</code> or <code>offset + 16 > buf.length</code>.</li> </ul> <h3>⚠ BREAKING CHANGES</h3> <ul> <li><code>crypto</code> is now expected to be globally defined (requires node@20+) (<a href="https://redirect.github.com/uuidjs/uuid/issues/935">#935</a>)</li> <li>drop node@18 support (<a href="https://redirect.github.com/uuidjs/uuid/issues/934">#934</a>)</li> <li>upgrade minimum supported TypeScript version to 5.4.3, in keeping with the project's policy of supporting TypeScript versions released within the last two years</li> </ul> <h2><a href="https://github.com/uuidjs/uuid/compare/v12.0.0...v13.0.0">13.0.0</a> (2025-09-08)</h2> <h3>⚠ BREAKING CHANGES</h3> <ul> <li>make browser exports the default (<a href="https://redirect.github.com/uuidjs/uuid/issues/901">#901</a>)</li> </ul> <h3>Bug Fixes</h3> <ul> <li>make browser exports the default (<a href="https://redirect.github.com/uuidjs/uuid/issues/901">#901</a>) (<a href="https://github.com/uuidjs/uuid/commit/bce9d72a3ae5b9a3dcd8eb21ef6d1820288a427a">bce9d72</a>)</li> </ul> <h2><a href="https://github.com/uuidjs/uuid/compare/v11.1.0...v12.0.0">12.0.0</a> (2025-09-05)</h2> <h3>⚠ BREAKING CHANGES</h3> <ul> <li>update to typescript@5.2 (<a href="https://redirect.github.com/uuidjs/uuid/issues/887">#887</a>)</li> <li>remove CommonJS support (<a href="https://redirect.github.com/uuidjs/uuid/issues/886">#886</a>)</li> <li>drop node@16 support (<a href="https://redirect.github.com/uuidjs/uuid/issues/883">#883</a>)</li> </ul> <h3>Features</h3> <ul> <li>add node@24 to ci matrix (<a href="https://redirect.github.com/uuidjs/uuid/issues/879">#879</a>) (<a href="https://github.com/uuidjs/uuid/commit/42b6178aa21a593257f0a72abacd220f0b7b8a92">42b6178</a>)</li> <li>drop node@16 support (<a href="https://redirect.github.com/uuidjs/uuid/issues/883">#883</a>) (<a href="https://github.com/uuidjs/uuid/commit/0f38cf10366ab074f9328ae2021eea04d5f2e530">0f38cf1</a>)</li> <li>remove CommonJS support (<a href="https://redirect.github.com/uuidjs/uuid/issues/886">#886</a>) (<a href="https://github.com/uuidjs/uuid/commit/ae786e27265f50bcf7cead196c29f1869297c42f">ae786e2</a>)</li> <li>update to typescript@5.2 (<a href="https://redirect.github.com/uuidjs/uuid/issues/887">#887</a>) (<a href="https://github.com/uuidjs/uuid/commit/c7ee40598ed78584d81ab78dffded9fe5ff20b01">c7ee405</a>)</li> </ul> <h3>Bug Fixes</h3> <ul> <li>improve v4() performance (<a href="https://redirect.github.com/uuidjs/uuid/issues/894">#894</a>) (<a href="https://github.com/uuidjs/uuid/commit/5fd974c12718c8848035650b69b8948f12ace197">5fd974c</a>)</li> <li>restore node: prefix (<a href="https://redirect.github.com/uuidjs/uuid/issues/889">#889</a>) (<a href="https://github.com/uuidjs/uuid/commit/e1f42a354593093ba0479f0b4047dae82d28c507">e1f42a3</a>)</li> </ul> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/uuidjs/uuid/commit/7c1ea087a8149b57380fc8bb7f68c3a215cb6e4b"><code>7c1ea08</code></a> chore(main): release 14.0.0 (<a href="https://redirect.github.com/uuidjs/uuid/issues/926">#926</a>)</li> <li><a href="https://github.com/uuidjs/uuid/commit/3d2c5b0342f0fcb52a5ac681c3d47c13e7444b34"><code>3d2c5b0</code></a> Merge commit from fork</li> <li><a href="https://github.com/uuidjs/uuid/commit/f2c235f93059325fa43e1106e624b5291bb523c4"><code>f2c235f</code></a> fix!: expect <code>crypto</code> to be global everywhere (requires node@20+) (<a href="https://redirect.github.com/uuidjs/uuid/issues/935">#935</a>)</li> <li><a href="https://github.com/uuidjs/uuid/commit/529ef0899f5dd503d2ee90d690585d63d78bc212"><code>529ef08</code></a> chore: upgrade TypeScript and fixup types (<a href="https://redirect.github.com/uuidjs/uuid/issues/927">#927</a>)</li> <li><a href="https://github.com/uuidjs/uuid/commit/086fd7976f11433edf9ac80be876b3ad243fe087"><code>086fd79</code></a> chore: update dependencies (<a href="https://redirect.github.com/uuidjs/uuid/issues/933">#933</a>)</li> <li><a href="https://github.com/uuidjs/uuid/commit/dc4ddb87272ed2843faccd130bcc41d492688bd3"><code>dc4ddb8</code></a> feat!: drop node@18 support (<a href="https://redirect.github.com/uuidjs/uuid/issues/934">#934</a>)</li> <li><a href="https://github.com/uuidjs/uuid/commit/0f1f9c9c9cedbae5a1d363d5406c5dfbabe81404"><code>0f1f9c9</code></a> chore: switch to Biome for parsing and linting (<a href="https://redirect.github.com/uuidjs/uuid/issues/932">#932</a>)</li> <li><a href="https://github.com/uuidjs/uuid/commit/e2879e64bf125add903c1eff6e0860542c605013"><code>e2879e6</code></a> chore: use maintained version of npm-run-all (<a href="https://redirect.github.com/uuidjs/uuid/issues/930">#930</a>)</li> <li><a href="https://github.com/uuidjs/uuid/commit/ffa31383e8e4e1f0b4e22e504561272041b8738c"><code>ffa3138</code></a> fix: Use GITHUB_TOKEN for release-please and enable npm provenance (<a href="https://redirect.github.com/uuidjs/uuid/issues/925">#925</a>)</li> <li><a href="https://github.com/uuidjs/uuid/commit/0423d49df2dc8efc300c804731d25f4d7e0fccc4"><code>0423d49</code></a> docs: remove obsolete v1 option notes (<a href="https://redirect.github.com/uuidjs/uuid/issues/915">#915</a>)</li> <li>Additional commits viewable in <a href="https://github.com/uuidjs/uuid/compare/v11.1.0...v14.0.0">compare view</a></li> </ul> </details> <details> <summary>Maintainer changes</summary> <p>This version was pushed to npm by <a href="https://www.npmjs.com/~GitHub%20Actions">GitHub Actions</a>, a new releaser for uuid since your current version.</p> </details> <br /> Updates `mermaid` from 11.14.0 to 11.15.0 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/mermaid-js/mermaid/releases">mermaid's releases</a>.</em></p> <blockquote> <h2>mermaid@11.15.0</h2> <h3>Minor Changes</h3> <ul> <li> <p><a href="https://redirect.github.com/mermaid-js/mermaid/pull/7174">#7174</a> <a href="https://github.com/mermaid-js/mermaid/commit/0aca21739c0d1fcaaa206e04a6cd574ebc415483"><code>0aca217</code></a> Thanks <a href="https://github.com/milesspencer35"><code>@milesspencer35</code></a>! - feat(sequence): Add support for decimal start and increment values in the <code>autonumber</code> directive</p> </li> <li> <p><a href="https://redirect.github.com/mermaid-js/mermaid/pull/7512">#7512</a> <a href="https://github.com/mermaid-js/mermaid/commit/8e17492f7365ba50896382feb69a23efd9d8a22d"><code>8e17492</code></a> Thanks <a href="https://github.com/aruncveli"><code>@aruncveli</code></a>! - feat(flowchart): add datastore shape</p> <p>In Data flow diagrams, a datastore/warehouse/file/database is used to represent data persistence. It is denoted by a rectangle with only top and bottom borders, and can be used in flowcharts with <code>A@{ shape: datastore, label: "Datastore" }</code>.</p> </li> <li> <p><a href="https://redirect.github.com/mermaid-js/mermaid/pull/6440">#6440</a> <a href="https://github.com/mermaid-js/mermaid/commit/9ad8dde6d049adde85d8ed2d476c09b5820f3f4b"><code>9ad8dde</code></a> Thanks <a href="https://github.com/yordis"><code>@yordis</code></a>, <a href="https://github.com/lgazo"><code>@lgazo</code></a>! - feat: add Event Modeling diagram</p> </li> <li> <p><a href="https://redirect.github.com/mermaid-js/mermaid/pull/7707">#7707</a> <a href="https://github.com/mermaid-js/mermaid/commit/27db774627be1cee881961dfd0d2cb21cd01b79d"><code>27db774</code></a> Thanks <a href="https://github.com/txmxthy"><code>@txmxthy</code></a>! - feat(architecture): expose four fcose layout knobs for <code>architecture-beta</code> diagrams (<code>nodeSeparation</code>, <code>idealEdgeLengthMultiplier</code>, <code>edgeElasticity</code>, <code>numIter</code>) so authors can tune layout density and spread overlapping siblings without changing diagram source</p> </li> <li> <p><a href="https://redirect.github.com/mermaid-js/mermaid/pull/7604">#7604</a> <a href="https://github.com/mermaid-js/mermaid/commit/bf9502fb6012a4b724679b401ac928f5ee55161c"><code>bf9502f</code></a> Thanks <a href="https://github.com/M-a-c"><code>@M-a-c</code></a>! - feat(class): add nested namespace support for class diagrams via dot notation and syntactic nesting</p> <p>If you have namespaces in class diagrams that use <code>.</code>s already and want to render them without nesting (≤v11.14.0 behaviour), you can use set <code>class.hierarchicalNamespaces=false</code> in your mermaid config:</p> <pre lang="yaml"><code>config: class: hierarchicalNamespaces: false </code></pre> </li> <li> <p><a href="https://redirect.github.com/mermaid-js/mermaid/pull/7272">#7272</a> <a href="https://github.com/mermaid-js/mermaid/commit/88cdd3dc0aab9577174561b04e14760c565a232b"><code>88cdd3d</code></a> Thanks <a href="https://github.com/xinbenlv"><code>@xinbenlv</code></a>! - feat(sankey): add outlined label style, configurable nodeWidth/nodePadding, and custom node colors</p> </li> </ul> <h3>Patch Changes</h3> <ul> <li> <p><a href="https://redirect.github.com/mermaid-js/mermaid/pull/7737">#7737</a> <a href="https://github.com/mermaid-js/mermaid/commit/e9b0f34d8d82a6260077764ee45e1d7d90957a0f"><code>e9b0f34</code></a> Thanks <a href="https://github.com/ashishjain0512"><code>@ashishjain0512</code></a>! - fix: prevent unbalanced CSS styles in classDefs</p> </li> <li> <p><a href="https://redirect.github.com/mermaid-js/mermaid/pull/7737">#7737</a> <a href="https://github.com/mermaid-js/mermaid/commit/37ff937f1da2e19f882fd1db01235db4d01f4056"><code>37ff937</code></a> Thanks <a href="https://github.com/ashishjain0512"><code>@ashishjain0512</code></a>! - fix: create CSS styles using the CSSOM</p> <p>This removes some invalid CSS and normalizes some CSS formatting.</p> </li> <li> <p><a href="https://redirect.github.com/mermaid-js/mermaid/pull/7508">#7508</a> <a href="https://github.com/mermaid-js/mermaid/commit/bfe60cc67b9a6dec64f9161f58e4d24a06c42b65"><code>bfe60cc</code></a> Thanks <a href="https://github.com/biiab"><code>@biiab</code></a>! - fix(stateDiagram): <code>end note</code> now only closes a note when used on a new line</p> </li> <li> <p><a href="https://redirect.github.com/mermaid-js/mermaid/pull/7737">#7737</a> <a href="https://github.com/mermaid-js/mermaid/commit/faafb5d49106dd32c367f3882505f2dd625aa30e"><code>faafb5d</code></a> Thanks <a href="https://github.com/ashishjain0512"><code>@ashishjain0512</code></a>! - fix(gantt): add iteration limit for <code>excludes</code> field</p> </li> <li> <p><a href="https://redirect.github.com/mermaid-js/mermaid/pull/7737">#7737</a> <a href="https://github.com/mermaid-js/mermaid/commit/65f8be2a42faf869b811469571983cba7eeeca99"><code>65f8be2</code></a> Thanks <a href="https://github.com/ashishjain0512"><code>@ashishjain0512</code></a>! - fix: disallow some CSS at-rules in custom CSS</p> </li> <li> <p><a href="https://redirect.github.com/mermaid-js/mermaid/pull/7726">#7726</a> <a href="https://github.com/mermaid-js/mermaid/commit/1502f32f3c5fb944925b0c527fbbde3c4f041824"><code>1502f32</code></a> Thanks <a href="https://github.com/aloisklink"><code>@aloisklink</code></a>! - fix(wardley): fix unnecessary sanitization of text</p> </li> <li> <p><a href="https://redirect.github.com/mermaid-js/mermaid/pull/7578">#7578</a> <a href="https://github.com/mermaid-js/mermaid/commit/1f98db8e326299ac97a2fa60abfd509d8f5f16e2"><code>1f98db8</code></a> Thanks <a href="https://github.com/Gaston202"><code>@Gaston202</code></a>! - fix(class): self-referential class multiplicity labels no longer rendered multiple times</p> <p>Fixes <a href="https://redirect.github.com/mermaid-js/mermaid/issues/7560">#7560</a>. Resolves an issue where cardinality labels on self-referential class relationships were rendered three times due to edge splitting in the dagre layout. The fix ensures that each sub-edge only carries its relevant label positions.</p> </li> <li> <p><a href="https://redirect.github.com/mermaid-js/mermaid/pull/7592">#7592</a> <a href="https://github.com/mermaid-js/mermaid/commit/2343e38498a3b31f8ce5e79f1f009e0b56fbe086"><code>2343e38</code></a> Thanks <a href="https://github.com/knsv-bot"><code>@knsv-bot</code></a>! - fix(sequence): add background box behind alt/else section title labels in sequence diagrams</p> </li> <li> <p><a href="https://redirect.github.com/mermaid-js/mermaid/pull/7589">#7589</a> <a href="https://github.com/mermaid-js/mermaid/commit/7fb9509b8b5cb1dc48519dc60cf6cdc6afba0462"><code>7fb9509</code></a> Thanks <a href="https://github.com/NYCU-Chung"><code>@NYCU-Chung</code></a>! - fix(block): prevent column widths from shrinking when mixing different column spans</p> </li> <li> <p><a href="https://redirect.github.com/mermaid-js/mermaid/pull/7632">#7632</a> <a href="https://github.com/mermaid-js/mermaid/commit/3f9e0f15bedc1e2c71ddb6b34192d1a21124cfc2"><code>3f9e0f1</code></a> Thanks <a href="https://github.com/ekiauhce"><code>@ekiauhce</code></a>! - fix(sequence): correct messageAlign label position for right-to-left arrows in sequence diagrams</p> </li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/mermaid-js/mermaid/commit/41646dfd43ac83f001b03c70605feb036afae46d"><code>41646df</code></a> Merge pull request <a href="https://redirect.github.com/mermaid-js/mermaid/issues/7739">#7739</a> from aloisklink/ci/fix-release</li> <li><a href="https://github.com/mermaid-js/mermaid/commit/2671f5c44a1515960ebc41c09a365c41860f95ee"><code>2671f5c</code></a> docs: fix v11.15.0 release</li> <li><a href="https://github.com/mermaid-js/mermaid/commit/f4bf04b5db8bed603e40ed3d5ce5228d6b07754e"><code>f4bf04b</code></a> Merge pull request <a href="https://redirect.github.com/mermaid-js/mermaid/issues/7738">#7738</a> from mermaid-js/changeset-release/master</li> <li><a href="https://github.com/mermaid-js/mermaid/commit/abfb563e1dcbd46d617f44a6361bd6d926dc6289"><code>abfb563</code></a> Version Packages</li> <li><a href="https://github.com/mermaid-js/mermaid/commit/60b289f428d0a0832ad95ed4e1fb326344e23532"><code>60b289f</code></a> Release Candidate 11.15.0 (<a href="https://redirect.github.com/mermaid-js/mermaid/issues/7737">#7737</a>)</li> <li><a href="https://github.com/mermaid-js/mermaid/commit/d37c0db39ca2405b4473361063df2c47109dc2c9"><code>d37c0db</code></a> Merge pull request <a href="https://redirect.github.com/mermaid-js/mermaid/issues/7730">#7730</a> from aloisklink/fix/fix-edgeLabelRightLeft-changes</li> <li><a href="https://github.com/mermaid-js/mermaid/commit/5ab5a2895fa8b7e90de85b43a4b99aa50b39b0f1"><code>5ab5a28</code></a> docs: improve nested namespace changeset</li> <li><a href="https://github.com/mermaid-js/mermaid/commit/18f8b4c5bf67aface3485272b48042f2fdd6fad2"><code>18f8b4c</code></a> fix: revert endEdgeLabelLeft/endEdgeLabelRight change</li> <li><a href="https://github.com/mermaid-js/mermaid/commit/504b2eb73d4d827baa817efd47ab6f44ae769b5a"><code>504b2eb</code></a> Merge pull request <a href="https://redirect.github.com/mermaid-js/mermaid/issues/7726">#7726</a> from aloisklink/fix/correct-unnecessary-html-escapes...</li> <li><a href="https://github.com/mermaid-js/mermaid/commit/1502f32f3c5fb944925b0c527fbbde3c4f041824"><code>1502f32</code></a> fix(wardley): fix unnecessary sanitization of text</li> <li>Additional commits viewable in <a href="https://github.com/mermaid-js/mermaid/compare/mermaid@11.14.0...mermaid@11.15.0">compare view</a></li> </ul> </details> <br /> You can trigger a rebase of this PR by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/sjacorg/bayanat/network/alerts). </details> > **Note** > Automatic rebases have been disabled on this pull request as it has been open for over 30 days. Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Nidal Alhariri <level09@gmail.com>
…zard guides (#352) Adds four guide pages for public features that previously had no documentation. All content was verified against the code and kept free of secrets and internal infrastructure details. - **Transcription** (`guide/transcription.md`) — local Whisper transcription of audio/video, model trade-offs, language detection, reviewing/correcting transcripts, searchability. - **Media Import** (`guide/media-import.md`) — bulk creation of Bulletins from media files via upload or server path, processing options (parse/OCR/transcribe), dedup. Includes the admin-only server-path import with the `ETL_ALLOWED_PATH` restriction and a warning to scope it to a dedicated folder. - **Account Security** (`guide/account-security.md`) — 2FA (TOTP) with recovery codes, passkeys/security keys, Google SSO, password policy, login CAPTCHA, and session behaviour. Framed as what users self-manage vs. what admins enforce. - **Setup Wizard** (`deployment/setup-wizard.md`) — the first-run browser wizard, what each step covers, and the CLI alternative. Sidebar entries added under the relevant sections. Verified with `vitepress build` (no dead links).
## Problem The session idle timeout (`PERMANENT_SESSION_LIFETIME`) never fires. The notification poller hits `GET /admin/api/notifications` every 60s, and with Flask-Session's default `SESSION_REFRESH_EACH_REQUEST=True` each request rewrites the Redis session TTL back to full lifetime. A user who leaves a tab open is never logged out. ## Fix Mark the automatic poll with an `X-Silent-Poll` header and override `should_set_storage` so requests carrying it don't refresh the session expiry (unless they actually modified the session). All other requests, including user actions in the notification drawer, still slide the session. - `enferno/app.py` — per-request opt-out in `should_set_storage`. - `enferno/static/js/mixins/notification-mixin.js` — only the interval-driven `refetchNotifications` sends the header; user-initiated loads do not. ## Tests `tests/test_session_poll_refresh.py`: silent poll leaves the TTL untouched; a normal request still refreshes it.
## Summary Event types now have an independent **Incidents** scope, separate from Bulletins. Previously the Incidents screen reused the `for_bulletin` flag, so an event type assignable to Bulletins was automatically assignable to Incidents, with no way to scope a type to Incidents alone. This adds a third toggle alongside the existing Actors and Bulletins ones, mirroring the established `for_incident` pattern already used by Labels. ## How it works - **Model/API/validation**: new `Eventtype.for_incident` column, serialized in `to_dict`, accepted in `from_json` and the validation model; the eventtypes API now accepts `typ=for_incident`. - **Admin editor**: a "For Incidents" checkbox (three equal columns: Actors / Bulletins / Incidents) and a matching table column. - **Incident consumers** point at the new flag instead of `for_bulletin`: the event picker (`incidents.html`) and the search/filter sidebar (`IncidentSearchBox.js`), matching how the Actor and Bulletin search boxes use their own flags. - **Seed data** (`enferno/data/eventtypes.csv`): new `for_incident` column, set equal to `for_bulletin` per row so fresh installs match the migration backfill. ## Migration `c7d2e9f4a1b8` adds the column and backfills `for_incident = for_bulletin` for existing rows, so the Incidents event-type list is unchanged on day one. Admins can then curate it independently. No data is removed; existing incident events keep their types regardless of the flag. ## Tests Eventtype CRUD test exercises the new field; factory and `create_eventtype_for` helper extended to support incident-scoped types. All eventtype lookup tests pass.
Fixes for the 7ASec BAY-01 engagement, on top of baseline
8615ff11b. One commit per finding, taggedfix(BAY-01-NNN):so the retest maps fixes bygit log --grep BAY-01 pentest-fixes.42 of 43 fixed. Open:
BAY-01-017(release signing), a product decision, not a code gap.Finding to commit
017 is unaddressed pending the signing decision. minisign keypair exists, public key pinned; manual
sudo bayanat updatestays as the fallback.Partials worth flagging
020: filename enumeration closed (opaque names). Per-item auth on/api/serve/inline/<filename>is a separate parent-binding change, deferred.028issue 6: passwords are out of argv (in-container config file +REDISCLI_AUTH). Delivery stays via env vars because Compose can't mount env-sourced secrets intoread_onlycontainers.031: app DB role stripped of superuser/createdb/createrole/replication and CONNECT revoked from PUBLIC, but keeps table ownership because dynamic-fields runsALTER TABLEat runtime.Retest
Backend regression tests:
uv run pytest tests/test_pentest_fixes.py. No frontend harness in this repo; 034 verified by review. Each commit message has root cause and where to verify. Fresh install recommended (005 changed the install flow: the installer now generates and prints the admin password once).