Skip to content

Architecture

zach115th edited this page Jul 20, 2026 · 2 revisions

Architecture

Source layout

iris-ng/
├── source/
│   ├── app/
│   │   ├── blueprints/          # HTTP layer (auth, ACL, I/O validation)
│   │   │   ├── pages/           # Jinja-rendered pages
│   │   │   └── rest/
│   │   │       ├── case/        # Legacy verb-based API  (/case/..., /manage/...)
│   │   │       └── v2/          # REST-style API  (/api/v2/...)
│   │   ├── business/            # Business logic (no HTTP, no DB)
│   │   ├── datamgmt/            # DB access (SQLAlchemy ORM only)
│   │   ├── iris_engine/
│   │   │   ├── ai/              # All AI orchestrators
│   │   │   └── working_timeline/ # Working-timeline parsers + resolvers
│   │   ├── models/              # SQLAlchemy ORM models
│   │   ├── alembic/             # Schema migrations
│   │   └── resources/
│   │       └── ai_prompts/      # Markdown system-prompt files
│   └── iris_misp_sync_module/   # MISP sync module (iris module wheel)
├── ui/
│   ├── src/                     # Svelte 5 + Vite bundled pages
│   └── public/assets/           # Static assets (vendored libs, CSS, plain JS)
├── docker/
│   ├── webApp/                  # App + worker Dockerfile + entrypoint
│   ├── nginx/                   # TLS + proxy (conf baked at build, not volume-mounted)
│   └── db/                      # Postgres Dockerfile
└── scripts/                     # Dev and maintenance helpers

Three-layer rule

Imports may only flow downward. Cross-layer imports in either direction are forbidden.

blueprints  (HTTP — auth, ACL, request/response I/O)
    ↓
business    (domain logic — no HTTP objects, no ORM session)
    ↓
datamgmt    (DB access — SQLAlchemy session, ORM queries)

Two parallel APIs

Both are served by the same Flask app. Prefer /api/v2/ for new clients.

API family Path prefix Response shape Notes
Legacy /case/..., /manage/... {status, message, data} wrapper v2.0.2 spec
REST v2 /api/v2/... Payload directly as JSON body — no wrapper iris-ng additions

Client gotcha: a v2 endpoint returns the payload directly — reading resp.data (the legacy shape) returns undefined. Read the body as-is: Array.isArray(resp) ? resp : (resp.data ?? resp).

Svelte + Jinja hybrid frontend

IRIS-NG v2.5.0-beta.1 introduced a Svelte 5 + Tailwind + shadcn-svelte frontend under ui/. Legacy page templates are Jinja (source/app/blueprints/pages/).

  • Bundled JS (ui/src/pages/*.js → Vite → image) — changes require an image rebuild
  • Static assets (ui/public/assets/) — baked into the image at build time; deploy patches with docker cp and rebuild source to survive the next rebuild
  • Jinja templates (source/app/.../templates/) — volume-mounted; take effect after docker restart iriswebapp_app (gunicorn caches templates; auto_reload = False)

Module system

Iris modules are Python wheels loaded at runtime. Each module registers hook handlers that celery dispatches via task_hook_wrapper.

Celery fork-safety: worker processes use NullPool (configured in source/app/__init__.py when "worker" in sys.argv). This prevents Postgres connection fd corruption when prefork children inherit a shared QueuePool. Do not add per-module db.session.remove() shims — they detach ORM objects handed in by task_hook_wrapper.

Module hooks fired

Hook Trigger
on_postload_case_create Case created
on_postload_case_update Case updated
on_postload_ioc_create IOC created
on_postload_ioc_update IOC updated

Database migrations

Alembic manages schema versions. The begin_transaction() wrapper in source/app/alembic/env.py is critical — upstream left it commented out, causing all column-add migrations to silently fail. iris-ng restores it.

New tables are also created via db.create_all() at startup (before Alembic runs), so ORM __table_args__ is the authoritative place for CHECK constraints and UNIQUE constraints — don't rely on migration files alone for these.

CSRF

All authenticated POST/PUT/DELETE to /api/v2/ require an X-CSRFToken header. Read the token from <input id="csrf_token"> (rendered by {{ form.hidden_tag() }} on every authenticated page). For multipart/form-data requests, append csrf_token as a form field instead of (or in addition to) the header.

TLP id mapping

ID Level
1 TLP:RED
2 TLP:AMBER
3 TLP:GREEN
4 TLP:CLEAR
5 TLP:AMBER+STRICT

Clone this wiki locally