Skip to content

Improve asset type icon management#1098

Open
tobmes42 wants to merge 16 commits into
dfir-iris:developfrom
PROCYDE:Improve-asset-type-icon-management
Open

Improve asset type icon management#1098
tobmes42 wants to merge 16 commits into
dfir-iris:developfrom
PROCYDE:Improve-asset-type-icon-management

Conversation

@tobmes42

Copy link
Copy Markdown

Problem:
When creating or editing an asset type (AssetsType) via the Web GUI, several limitations existed:

  • Uploaded icons were stored under a random 18-character string without a file extension → not recognizable and not reusable.
  • Previously uploaded icons could not be reselected (no dropdown for existing icons).
  • No magic-byte validation – any file with a .png/.svg extension was accepted.
  • With readOnlyRootFilesystem: true in the container, saving the symlink failed with "Read-only file system".
  • When no icon was provided for "compromised" and the "Use same icon" checkbox was unchecked, os.path.join(path, None) caused a TypeError, making the asset type list endpoint crash.
  • No client-side validation allowed submitting the form without setting both icons (or the checkbox).

Changes:
GUI / User Experience (modal_add_asset_type.html + manage.objects.js):

  • Both icon fields ("not compromised" / "compromised") now have a searchable selectpicker dropdown (data-live-search="true") listing all available icons from /iriswebapp/static/assets/img/graph.
  • Selecting an existing icon from the dropdown clears the file upload field (and vice versa).
  • A checkbox "Use the same icon for compromised as for not compromised" disables the compromised icon fields.
  • The uploaded file name is displayed in the .custom-file-label.
  • Client-side validation on submit:
  • If the checkbox is checked → "not compromised" must have an icon.
  • If the checkbox is not checked → both fields must have an icon (upload or selection).
  • In update mode, existing icons ("Current:") are taken into account.
  • Otherwise a notify_error toast is shown.

Backend / REST API (manage_assets_type_routes.py):

  • New helper _resolve_existing_icon(name): checks if an icon name exists in the graph directory and returns it.
  • During icon resolution: first checks if a file was uploaded, then if an existing icon was selected from the dropdown. If the checkbox is set, fpath_c = fpath_nc.
  • list_assets is now guarded against None icon values (returns empty string instead of os.path.join(…, None)), preventing the DataTable crash.
  • load_store_icon now has a None-guard if not file_storage or not file_storage.filename: return None to prevent the crash when the checkbox is set.

Icon Storage (marshables.py):

  • store_icon now uses the original file name (sanitized via secure_filename) instead of a random string.
  • On name collision, a numeric suffix is appended (file.png, file_1.png, …).
  • The file extension is preserved (.png/.svg) – uploaded icons are automatically recognized in the icon listing.
  • New validation function _is_valid_icon_content(): checks magic bytes.
  • PNG: signature \x89PNG\r\n\x1a\n
  • SVG: declaration <?xml or <svg (UTF-8, BOM-tolerant)
  • The stream is rewound after checking so file.save() can still write the full file.

Icon Listing (manage_assets_type_routes.py – Pages):

  • _list_graph_icons() now also detects custom uploaded icons (symlinks pointing into the asset store path) in addition to files with .png/.svg extension.
  • The list is sorted alphabetically and passed to both the add and update modals.

Affected files:

  • source/app/schema/marshables.py
  • source/app/blueprints/rest/manage/manage_assets_type_routes.py
  • source/app/blueprints/pages/manage/manage_assets_type_routes.py
  • source/app/blueprints/pages/manage/templates/modal_add_asset_type.html
  • ui/src/pages/manage.objects.js

tobmes42 and others added 16 commits July 13, 2026 08:08
* Fixed order of authentication methods for timeline filter endpoint.

* [ADD] Datastore and my profile support v2

* [ADD] Added missing files

* [ADD] Added search on all case elements

* [IMP] Improved global search

* [ADD] v2 activities endpoint with paging, scope and filters

* [IMP] Deduplicate activities within a one-minute bucket

* [ADD] v2 endpoints for Dim (Celery) tasks listing and detail

* [ADD] v2 endpoints for case close and reopen

* [FIX] Detach alert-shared IoCs before deleting a case

* [IMP] Independent open/close date bounds for case filters

* [ADD] v2 endpoints for managing modules and their hooks

* [ADD] v2 endpoints for customer contacts CRUD and embed contacts in customer detail

* [FIX] Paginated v2 envelope returns the next page number instead of a boolean

* [ADD] Search query parameter on the v2 customers listing endpoint

* [ADD] v2 endpoints for the Case Objects taxonomy family backed by a shared CRUD class

* [ADD] v2 CRUD endpoints for case templates accepting plain JSON bodies

* [ADD] v2 endpoint exposing the introspected case template schema for interactive editors

* [ADD] v2 endpoints for report templates including render-against-case with user case-access gating

* [ADD] v2 endpoint to replace a report template's file in place via multipart PUT

* [ADD] v2 access-control endpoints covering users, groups, members, case-access, customers, audit, MFA reset and API-key rotation

* [ADD] v2 read-only endpoints for severities, TLP, alert statuses + resolutions, analysis statuses, event categories and task statuses

* [ADD] v2 server settings endpoints with versions block, partial-update PUT and POST backup, deprecating the legacy /manage/settings routes

* [UPD] Updated dash and activities

* [FIX] Fixed GHSA-g588-5gmf-p5cx by excluding MFA secrets and webauthn credentials from user schemas and marking sensitive fields load-only

Backport of f1d41f4 from v2.4.29. UserSchema, UserFullSchema, and
BasicUserSchema were leaking mfa_secrets and webauthn_credentials in
serialized responses, letting an attacker bypass MFA or impersonate
the user (CWE-201, SBA-ADV-20260126-04). user_password is now load-only
on UserSchema, and DSFileSchema.file_local_name (server-side path) is
declared load-only at the schema layer instead of being del'd in the
route handler.

The matching v2 schema (UserSchemaForAPIV2) already had these guards;
this commit aligns the legacy schemas still in use.

* [FIX] Fixed GHSA-w78h-mx7h-qm3h by allowlisting writable fields on user, profile and taxonomy admin endpoints

Backport of f84b7b9 from v2.4.29, extended to cover the v2 surface
that didn't exist when the original fix shipped.

The legacy /manage/users, /manage/asset-type, /manage/ioc-types,
/profile/update endpoints accepted the raw request body and handed it
straight to the marshmallow schema. The schemas' `unknown = EXCLUDE`
drops unknown keys but every declared field on the schema is still
accepted — so a caller could overwrite the primary key (mass-assignment
of asset_id / type_id), self-promote to admin via user_isadmin on the
profile endpoint, or set arbitrary attributes the GUI never exposes
(SBA-ADV-20260128-01 / CWE-915).

Each writable endpoint now goes through an explicit per-resource
allowlist before the schema is loaded. The taxonomy CRUD on
rest/v2/manage_routes/case_objects.py declares the allowlist on the
shared TaxonomyConfig so the asset-type, ioc-type, classification,
state and evidence-type sub-blueprints all inherit the protection
without duplication.

GHSA-g588 already made user_password load_only on UserSchema, so the
legacy /manage/users/add no longer needs the `del udata['user_password']`
guard either; remove it.

* [FIX] Fixed GHSA-vjc3-7jwv-j9qf by tightening the post-login next-url allowlist

Backport of 371c640 from v2.4.29, adapted to develop's _is_safe_url
in business/auth.py.

The previous check only verified that urlparse(target).scheme and
urlparse(target).netloc were empty. urlparse treats `attacker.com?cid=1`
as a path with an empty netloc, so the guard accepted attacker-controlled
hosts; browsers resolving the resulting `Location: attacker.com` header
route the user to the attacker's origin (CWE-601 / SBA-ADV-20260126-02).

_is_safe_url now requires the target to be a non-empty string starting
with a single `/` (rejecting both protocol-relative `//evil.com` and
backslash variants like `/\\evil.com`) and free of control characters,
with the urlparse check kept as defense in depth.

* [FIX] Fixed GHSA-qhqj-8qw6-wp8v by confining datastore file ops to DATASTORE_PATH and allowlisting writable upload fields

Backport of 57c1b80 from v2.4.29, extended to the v2 datastore route
that didn't exist when the original fix shipped.

datastore_delete_file and datastore_get_local_file_path now resolve the
recorded file_local_name and refuse to operate on a path outside the
configured DATASTORE_PATH. Even though the dsf row is legitimately
fetched through (file_id, case_id), a stored path pointing elsewhere
(stale row from a different mount, attacker-seeded value) would let an
unlink or send_file escape the datastore — CWE-22.

The legacy /datastore/file/add + /datastore/file/update routes and the
v2 add_file + update_file methods now project the multipart form down
to an allowlist before the schema is loaded, blocking mass-assignment
of file_id, file_local_name, file_case_id, file_sha256, file_size,
added_by_user_id, file_date_added and friends.

* [FIX] Fixed GHSA-8hwq-v6vm-9grr by blocking alert re-attribution and requiring POST on logout

Backport of b202f54 from v2.4.29, extended to cover the v2 alerts
update endpoint that didn't exist when the original fix shipped.

Alert update (legacy single + batch, and v2 PUT /alerts/<id>) now
strips alert_id, alert_customer_id and alert_creation_time from the
payload before the schema is loaded. Allowing those via the API let a
user with write access to one customer re-attribute an alert to a
customer they cannot see — silently hiding it from the rightful owner
and planting it under another tenant's view (CWE-863 /
SBA-ADV-20260128-05).

The logout endpoint moves from GET to POST. A plain <img src="/logout">
on a third-party page would otherwise log the user out without consent
(RFC 7231 §4.2.1: GET must be safe; CWE-650 / SBA-ADV-20260128-03). The
sidenav link wraps a hidden form so existing accessibility / styling
keeps working.

* [FIX] Sanitised HTML custom attributes via bleach and forced unsafe datastore downloads as attachments

* [FIX] Moved no-cache headers into the server block and added long-lived caching for /static

* [DEL] Removed GraphQL surface and dependencies — GHSA-3mxh-x92q-9r25

* [UPD] Bumped iris-webhooks to 1.0.9 and hardened docker base images with apt upgrade

* [FIX] Fixed access control ordering so group access overrides customer access on cases

* [ADD] Added 'neq' and 'not_like' operators to the filter condition builder

* [FIX] Tagged alert comment edits as 'alerts' instead of 'events' so activity attribution is right

* [FIX] Hardened MFA flow with session-bound user, server-side secret, lockout and allowlisted DS file_tags

* [ADD] Added statistics_read and custom_dashboards_{read,write,share} permission bits

* [UPD] Folded statistics_read into custom_dashboards_read so Statistics ships as a seeded dashboard

* [ADD] Added /api/v2/dashboard/kpis with the compact KPI block for the home tile

* [ADD] Added CustomDashboard and CustomDashboardWidget models with migration seeding the Statistics system dashboard

* [ADD] Added custom dashboard query engine schema and named-aggregation registry for MTTD MTTR false-positive escalation and sliding-window metrics

* [ADD] Added /api/v2/custom-dashboards endpoints for CRUD render schema and presets with system-dashboard guard

* [ADD] Added pytest suite for custom-dashboards CRUD render system-guard and ownership scoping

* [FIX] Imported ac_current_user_has_permission from blueprints.access_controls where develop actually defines it

* [FIX] Deferred get_user import in business.access_controls to break circular import with manage_users_db

* [FIX] Reconciled pre-existing custom_dashboard table by adding missing is_system column and relaxing owner_id NOT NULL before seeding

* [FIX] Accept is_system and filters_schema in CustomDashboardSchema and ignore unknown widget/section keys so the seed Statistics dashboard renders

* [FIX] Dropped duplicate group-column field from seed Statistics widgets so charts render one dataset with category x-axis instead of one dataset per category

* [IMP] Returned rendered widgets grouped by section so the view page can preserve section titles dividers and ordering

* [FIX] Skipped duplicate-label projection in the query engine so widgets that name the same column in fields and group_by render one column not two

* [ADD] Added /api/v2/me/context endpoint returning iris_version demo_mode and effective permissions so the SPA can gate menu entries and show the running version without giving every user server_administrator access

* [ADD] Added migration that idempotently creates the saved_filters table so deployments where db.create_all never ran (or where a partial restore skipped it) can persist alerts and cases saved filters

* [ADD] Added /api/v2/cases-filters endpoints (list create get update delete) reusing the alerts saved-filter business layer with filter_type='cases' so the cases overview can persist private and public filter presets

* [IMP] Made the cases filter endpoint accept a recursive group tree ({logic items}) on top of the legacy flat list and rewrote build_filter_case_query to walk the tree so the UI can express (A and B) or (C and D) style queries and also added a severity column to the advanced filter switch

* [FIX] Enforce ownership on saved-filter PUT and DELETE in both alerts-filters and cases-filters so a user who can read a public filter cannot mutate someone else's row and force created_by from the session user on PUT to block mass-assignment

* [FIX] Made the alembic helper functions reuse op.get_bind() instead of spinning up a fresh engine per call so a single migration boot no longer leaks several SQLAlchemy connection pools and exhausts Postgres max_connections with FATAL sorry too many clients already mid-startup

* [ADD] Added avatar_blob avatar_mime and avatar_updated_at columns to the user table with an idempotent migration so each user can store their profile picture as a normalised 256x256 PNG that the SPA can fetch from a single endpoint

* [ADD] Added avatar endpoints (GET users id avatar for anyone authenticated POST DELETE me avatar for self-service POST DELETE manage users id avatar for admins) with Pillow normalisation to 256x256 PNG dropped avatar_blob from the User schemas to keep bytes out of JSON dumps and surfaced user_id on the per-case activities feed so the activity panel can attach the right avatar to each row

* [FIX] Allowed blob: in img-src CSP nginx headers so authenticated avatar object URLs render

* [ADD] Exposed note revisions on v2 (list get delete restore) and added notes_restore_revision so the SPA can re-list every snapshot taken on each note edit and roll the title and body back to any prior version

* [ADD] v2 admin endpoints for the CustomAttribute taxonomy

* [ADD] expose has_mini_sidebar in /me/context and a PUT /me/preferences to persist UI preferences

* [ADD] v2 endpoints to list other cases where an IOC or asset was previously seen

* [ADD] Enforce MFA verification on token-authenticated endpoints with brute-force throttle, re-enrollment guard, refresh propagation and integration tests

* [ADD] user followed cases + multiple named timelines per case

* [ADD] war room foundation: models, ACL, REST CRUD + case attachment + members

* [ADD] war room chat/tasks/notes/timelines/graph/sitreps/datastore REST + business

* [FIX] emit war-room activity events from REST mutations (attach/detach/members/tasks/notes)

* [ADD] war room chat activity_type column + inference; classify case activity for fine-grained stream filtering

* [ADD] live-merge UserActivity into war-room stream; drop chat-side activity_type column dependency

* [REM] war room graph board (models, REST, business, migration drop)

* [ADD] war room: customer name on case attachments, creator/closer attribution on tasks

* [ADD] more war-room slash commands (/decision /assign /detach /state /priority /summary /whoami /help)

* [FIX] war-room stream: explicit error on unknown / failing slash commands (no more silent fall-through to literal /text storage)

* [ADD] war-room cases enrich (owner/dates/state/tasks) + /people endpoint; surface generic slash-error refs

* [REM] /whoami slash command (low signal)

* [FIX] add PATCH to CORS Access-Control-Allow-Methods (war-room update + every v2 PATCH endpoint was blocked by preflight)

* [ADD] war-room timeline event PATCH + category column + drag-between-timelines support

* [ADD] war-room Stream threads — parent_message_id + thread_title columns + follower table + /thread slash + reply/follow/retitle endpoints (gated on pre-migration DBs)

* [FIX] war-room threads support probe — use fresh engine connection, don't pin negative result so a freshly-applied migration is picked up without restart

* [ADD] /api/v2/cases/{id}/access/me endpoint + allow read_only access on notes_directories.search (was full_access only — broke read-only users' notes view)

* [IMP] bump postgres image to 18-alpine + add pg12→pg18 migration script and 3.0.0 upgrade notes

* [IMP] threads-support probe queries the column directly on a fresh connection — search_path / inspector-cache issues were giving spurious negatives; distinguish UndefinedColumn (42703) from other DB errors in logs

* [FIX] /me/followed-cases — use CaseSchemaForAPIV2 so dashboard tile gets case_name + case_customer

* [REM] evtx2splunk + iris_evtx module — drop the two wheels, evtxdump_binaries, requirements entries, and the Dockerfile chmod step

* [FIX] PG18 PGDATA path — pin to /var/lib/postgresql/data/pgdata in compose and upgrade script (image refuses mountpoint-as-PGDATA)

* [ADD] pass VITE_ALLOWED_HOSTS + make ORIGIN overridable in frontend service

* [FIX] api_auth — return 401 instead of 500 when token/session references a missing user

users_get_active() raises ObjectNotFoundError when a JWT or session
points at a deleted/inactive user, which propagated to the Flask
exception handler and logged a 500 on routine calls like POST
/auth/logout. Wrap the lookup so the JWT path reports "invalid"
(401) and the legacy/session paths fall through as unauthenticated.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* [ADD] search — case summaries as a new searchable type in /search

* [FIX] cases overview — Tags filter was silently dropped; wire it via EXISTS(CaseTags JOIN Tags)

* [FIX] post_init — only seed default group permissions on first creation; admin edits no longer reset on worker boot

* [FIX] AuthorizationGroupSchema — respect partial PATCH; don't wipe group_permissions when the key is absent

* [IMP] war-room sitreps — publish is a state marker only; edit and delete stay allowed after publish

* [ADD] war-room chat — /decision, /pin, /note as thread replies; trace-log endpoint; server-side search on /chat

* [FIX] alembic env — restore context.begin_transaction() so migrations actually commit

* [ADD] war-room archive — archived_at/by columns, list filter, archive/unarchive endpoints, open-first sort

* [ADD] per-user preferences — user.preferences JSONB + /users/me/preferences/<key> endpoints

* [ADD] OIDC session→JWT exchange endpoint + provider discovery fallback + single-use session flag

* [ADD] nginx cert autodetect + reload polling, CA-bundle drop-in dir, IRIS_HOSTNAME single-source-of-truth, certbot deploy-hook

* [ADD] docker-compose.new-ui overlay + frontend Dockerfile for the SvelteKit UI service

* [ADD] notification core — notification/notification_setting tables + service + hook listeners + /api/v2/notifications endpoints + /notifications SocketIO namespace + /users/mentionable directory for analyst mention search

* [ADD] incidents + investigation flows (self-conditioned, alert/incident/both target, deploy-to-existing) + incident-rules with nested AND/OR conditions, back-fill, JSONB path support (alert_context.foo.bar)

* [FIX] enforce customer-scope authorisation on incident-rules + investigation-flows (getters/list/create/update/deploy/backfill), server-owned rule_created_by/flow_created_by attribution, public proxies for cross-module access-control helpers

* [IMP] incidents: comments, audit trail, incident_id alert filter, incident-statuses endpoint, escalate crash fix

* [ADD] mail: inbound ingest + outbound send with rules engine and secrets manager

* [FIX] pg12-to-pg18 upgrade: preserve hyphens in compose project name and detect stopped iriswebapp_db container

* [IMP] activity log: cover war rooms + case timelines + admin modules, add war_room_id scoping and /_diag notifications endpoint

* [FIX] compose: disable adapter-node BODY_SIZE_LIMIT so multi-MB avatar and multi-GB datastore uploads aren't truncated

* [FIX] notifications: drop /_diag endpoint and remove UserActivity.war_room relationship that broke gunicorn boot (WarRoom unresolvable at mapper-config time)

* [ADD] on_postload hooks for war room and incidents

* [IMP] incidents: escalate/merge to case, propagate status to alerts, expose source-incident on cases

* [ADD] incidents: correlation graph endpoint, unlink alert/incident from case, business helpers

* [ADD] collab: server-side Yjs relay, per-doc rooms, socket JWT-in-auth fallback

* [FIX] collab: GFM tables in seeder + auto re-seed via seeder_version bump

* [FIX] collab: unflatten single-line GFM pipe tables from legacy imports

* [IMP] rename Incident -> AlertCluster, IncidentRule -> ClusterRule across models, migrations, business, REST, schemas, hooks, celery tasks, tests

---------

Co-authored-by: fatpeppapig <cezary@falba.net>
Co-authored-by: whitekernel <74464599+whikernel@users.noreply.github.com>
Co-authored-by: whitekernel <whitekernel@dfir-iris.org>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (1)
  • api_*

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 850d3eef-0350-4d10-80b9-270072488361

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant