Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
A domain is a top-level container in CISO Assistant. It represents an organisational scope — a business unit, a subsidiary, or any boundary you want to manage access and reporting around.
Domains are the platform's primary mechanism for access control and reporting boundaries: a user's roles are granted on a domain, and most reports, dashboards, and audit roll-ups can be filtered by domain.
A domain can have a parent domain (parent_folder internally) — that's how you build a tree of sub-domains beneath a top-level one. The hierarchy lets you mirror the shape of your organisation in the platform: a "Group" domain on top, "Region" or "Subsidiary" domains beneath, "Business unit" or "Programme" domains beneath those, and so on.
When a parent-child relationship is in place, two things follow:
Permissions can flow downward. A user with a role on a parent domain can be configured to see and act on objects in its sub-domains, without re-assigning them at every level.
Reporting can roll up. Dashboards and analytics aggregate across a domain and its descendants, so leadership-level views work the same way the org chart does.
The root is reserved for global, cross-organisation objects (built-in catalogues, the global library) — your own domains always live one level under it, even when no other hierarchy is in place.
Not every domain needs to be an IAM boundary. Sometimes you want a domain purely as an organisational container — a folder in the tree to group related work — without the platform spinning up the per-role user groups that come with a "real" scoping domain.
The Create IAM groups checkbox on the domain form controls this:
On (default for new domains) — the platform auto-provisions one user group per role for the domain. Anyone who needs access to objects in this domain gets placed into one of those groups; the domain is a true IAM scope.
Off — no user groups are created. The domain exists in the tree, can be picked from selectors, and can host objects, but it carries no scoping machinery of its own. Access flows from whatever parent domain it sits under.
Turn the flag off when you want sub-domains that are just structure — for example, breaking a "Subsidiary" domain into a "2025 audits" / "2026 audits" tree purely for organisation, without giving each year its own IAM surface. The setting is shown by the on-form help text: "IAM groups are used to assign roles to users."
The flag is not a one-way choice. You can flip it on a domain at any time — turning it on later provisions the per-role user groups, turning it off later removes them. So a domain you initially created as a pure folder can later become a real IAM scope (or vice versa) without recreating it or moving its content.
The hierarchy is not frozen at creation. Editing a domain lets you change its Parent domain (PRO), which moves the whole sub-tree — the domain itself and every domain beneath it — under the new parent. Use this to:
Reorganise as your business changes (a programme becomes a subsidiary, two business units merge).
Promote a sub-domain to top-level by setting its parent back to the root.
Re-parent for IAM reasons (giving a parent role-holder access to a sub-tree that wasn't previously under them).
The platform prevents cycles — you can't move a domain under one of its own descendants — but everything else is reachable.
Almost every operational object in CISO Assistant is bound to a domain: assessments and audits, applied controls, evidences, risk scenarios, assets, tasks, policies, findings, incidents, exceptions, contracts, entities, and so on. The domain a record lives in is what drives who can see it and how it rolls up in reports.
Because reorganisations happen, the domain assignment is not permanent:
One at a time — edit any object and pick a different Domain in the form. The platform re-evaluates IAM scoping on save, so the object disappears from one domain's views and appears in the other's.
In bulk — for models that opt in to bulk operations, the toolbar exposes a Change folder action: select multiple rows in the table, choose the destination domain, and the move is applied across the selection in one go. Useful when reorganising a subsidiary into its own sub-tree, or pulling a programme's controls into a dedicated domain.
A handful of objects whose domain is forced by a parent (e.g. risk scenarios always inherit their risk assessment's domain) intentionally don't expose the batch Change folder action — moving the parent moves the children.
— how users, teams, and entities get scoped to a domain

CISO Assistant is built around a small set of design principles. A lot of object boundaries, naming choices, and workflow shapes only make sense once you have these in mind.
The applied control is the unifying object in CISO Assistant. Everything the organisation does to manage risk and prove compliance is captured as an applied control — a technical safeguard, an organisational process, a documented policy, a tested recovery plan. Once that's in place, the rest of the platform connects to it:
An audit assesses requirements; each requirement assessment links to the applied controls that satisfy it.
A risk scenario lowers its current and residual levels by attaching the applied controls in place and planned.
A task records that an applied control was actually exercised on a given date and produces the evidence to prove it.
Evidence lives on an applied control — and through it, substantiates every requirement and scenario the control supports.
An incident response invokes applied controls; a vulnerability is mitigated by them; a policy is one.
Authoring an applied control once and reusing it across all the places it applies — instead of redoing the same work per audit, per risk study, per framework — is the productivity gain that justifies the whole architecture.
The corollary of putting applied controls at the centre is that everything around them must be decoupled, so that one applied control can serve many consumers without being rewritten for each:
Security controls are decoupled from compliance requirements — a single applied control can satisfy many requirements across many frameworks.
Risk assessments are decoupled from frameworks — the same risk scenario can inform multiple compliance audits.
Assets are decoupled from threat scenarios — assets exist independently of any specific risk study and can be reused across them.
The payoff is reuse, end-to-end. One applied control answers many requirements. One assessment covers many frameworks. One asset participates in many scenarios. One evidence file substantiates everything the underlying control supports.
A glossary of the terms used in CISO Assistant. Where a user-facing term differs from the internal model name, both are given.
Accreditation — Formal authorisation that a system, environment, or product has met security and compliance requirements. Captured as an object in project-management workflows, often required for go-live.
Actor — The unifying handle for anyone who can own or be assigned work in CISO Assistant. An actor wraps exactly one of three underlying objects: a User, a Team, or an Entity. Auto-created when its underlying object is created — not managed directly. See Actors and teams.
Applied control — The main building block of the action plan: a concrete action your team has implemented or will implement. It can be technical, organisational, a process, a policy, a piece of documentation — anything that materially changes risk or compliance posture. Applied controls are always defined by the organisation and can be attached to the global domain or to a specific domain. They may derive from a reference control for consistency, or be created independently.
Asset — Anything of value worth protecting. Primary assets are core resources directly contributing to the organisation's main objectives (business processes, data, intellectual property). Supporting assets indirectly aid primary functions (IT systems, services, locations, people).
Asset assessment — A per-asset row inside a business impact analysis, capturing recovery posture (documented, tested, targets met), associated controls, evidence, and the escalation thresholds that describe how impact grows over time.
Assessment — Umbrella term covering audits (compliance work), risk assessments, business impact analyses, and entity assessments.
Attack path — In EBIOS RM, the route an attacker may take from a starting point — through stakeholders or supporting assets — to reach a target objective.
Audit — The evaluation of a perimeter against a framework, producing a per-requirement view of status, score, and evidence. Internally a ComplianceAssessment.
Audit log — Append-only record of significant actions taken in the platform (creations, edits, permission changes, logins). PRO feature.
Auditee mode — A read-only UX mode aimed at external assessors who need to inspect an audit without being granted full access to the platform. Gated by the auditee_mode feature flag.
Business impact analysis (BIA) — A structured assessment of the operational, financial, and reputational impact of disruption to specific assets or business processes. Outputs feed into resilience planning.
Campaign — An orchestration object for running one assessment exercise across many targets at once: an internal campaign targets your perimeters, a third-party campaign targets your third parties. PRO feature.
Catalog object — A reusable building block of CISO Assistant: framework, threat, risk matrix, reference control, mapping, security advisory, CWE. Catalog objects are packaged into libraries.
Commitment — The delivery date an owner has actually promised for a piece of remediation work, and the record of the negotiation that produced it. Distinct from an ETA, which anyone can type in.
Dashboard — A configurable view of metrics and progress indicators, scoped to a perimeter or a domain.
Data breach — In a privacy register, an incident affecting personal data, with notification status and response actions.
Data contractor — A third party involved in a processing as data processor, sub-processor, or joint controller. Distinct from a generic supplier entity — captures the privacy-specific role.
Data recipient — A party (internal team, external service, public body) that personal data is disclosed to as part of a processing.
EBIOS RM — The French ANSSI risk-management method, supported natively in the platform as its own object graph (studies, feared events, stakeholders, strategic and operational scenarios, kill chains).
Elementary action — In EBIOS RM, an atomic step an attacker can perform. Composed into operating modes.
Entity — Scope of an external review — typically a vendor or third party.
Entity assessment — The actual review of an entity. Can trigger or be linked to an audit.
Feared event — In EBIOS RM, the undesirable outcome to be avoided on a primary asset — for example, a confidentiality breach of customer data.
Filtering label — A free-form tag that can be attached to most objects for categorisation, filtering, and reporting.
Finding — A single issue to be remediated — a non-compliance, an observation, a vulnerability found in a pentest. Usually collected in a findings binder, but can stand alone.
Findings binder — A formal record collecting the issues raised by one review — an audit, a security review, a pentest, an external assessor's report — and driving them through to closure. Internally
Generic collection — A flexible grouping object in project-management workflows: a "bag" of related items that doesn't fit a more specific schema.
Incident — A security or operational event being investigated or tracked. Distinct from a risk (potential) or a vulnerability (weakness).
Inherent risk — The natural risk level of a scenario without any applied controls. The top tier of CISO Assistant's three-tier model — useful for ranking scenarios by their underlying severity, independently of the mitigation already in place. Surfaced in the UI when the inherent_risk feature flag is on.
Journey — An instance of a applied to a domain. Carries the scaffolded objects created at apply time and a step list with per-step statuses, notes, and completion timestamps. See .
Journey step — One row of work inside a journey: a title, description, optional pointer to a target object or internal route, and a status (not started, in progress, done, skipped).
Kill chain — In EBIOS RM, an ordered sequence of attacker steps culminating in the target objective.
Library — Container object bundling one or more catalog objects (frameworks, matrices, threats, reference controls, mappings, security advisories).
Managed document — A document tracked through a controlled lifecycle (draft, in-review, validated, published, deprecated) and pinned to a parent object such as a policy. Each iteration is a .
Mapping — Based on the . Allows moving an assessment from framework A to framework B while reusing existing requirement assessments.
Metric definition — A reusable specification for a measurable indicator (formula, target, unit, scope). Defined once, instantiated per perimeter.
Metric instance
Operating mode — In EBIOS RM, a specific way an attacker can carry out an operational scenario, composed of elementary actions.
Operational scenario — In EBIOS RM, the detailed "how" of a strategic scenario — the assets touched, the steps taken, and the techniques used.
Organisational issue — A documented context element describing an internal or external problem affecting the organisation.
Organisational objective — A documented strategic or operational goal of the organisation.
Perimeter — A scoped subset of a domain that an audit or risk assessment applies to. Unlike a domain, a perimeter does not enforce role-based access control. Perimeters were previously called "Projects".
Personal access token (PAT) — A long-lived authentication token a user can issue from their profile to authenticate API calls. Alternative to session-based login; used by scripts, integrations, the CLI, and the MCP server.
Personal data — In a privacy register, any data referring to an identified or identifiable individual.
Quantitative risk hypothesis — A parameter set (loss-event frequency, magnitude distribution) feeding a quantitative risk scenario.
Quantitative risk scenario — A scenario inside a quantitative risk study, evaluated via Monte-Carlo simulation over loss distributions.
Quantitative risk study — A risk study using quantitative methods rather than a risk matrix. Sibling to qualitative risk assessment and EBIOS RM.
Rating — A quick form outcome rule that classifies the answers to its scales into named levels, through a grid or rules: a DPIA's privacy risk crosses severity and likelihood. See .
Recap — The roll-up view of a business impact analysis, aggregating asset assessments and their escalation thresholds into a single readout.
Recovery target — A documented commitment for restoring an asset after disruption, typically a Recovery Time Objective (RTO) or Recovery Point Objective (RPO). Tracked on an asset assessment as "documented", "tested", and "targets met" flags.
Scenario selection — The risk scenarios kept by the Selection bar of a risk assessment. The scenario table, the matrices, the action plan and the exports all work on that subset. See .
Security advisory — A catalogued security warning published by a vendor or CERT (e.g. CVE entries). Linked to vulnerabilities and affected assets.
Security exception — A documented, time-bound deviation from a requirement, control or risk treatment, with owners and an expiration date. See .
Task definition — A reusable specification of a task: default assignee, owner, recurrence rule, expected evidence. Defining a task creates one or more task occurrences over time. Internally a TaskTemplate.
Task node — Internal model name for a task occurrence. See Task occurrence.
Task occurrence — A scheduled instance of a task definition, with a due date, a status (pending → in progress → completed/cancelled), and the evidence collected when the task ran. Internally a TaskNode.
URN — Uniform Resource Name. A unique identifier used to link to catalog objects across libraries.
User — A person with an account on the platform.
User group — A combination of a role and a domain, on which you place users. Auto-created when a domain is created.
Validation flow — A configurable approval workflow that mirrors an organisation's internal review or management-approval process — peer-review, sign-off by a security lead, formal acceptance by a steering committee. Attached to objects whose state changes warrant such review (e.g. risk acceptance, audit close-out), it captures the human approval step inside the platform rather than enforcing it as a hard technical gate.
Vulnerability — A weakness in a system or process that could be exploited by a threat. Tracked with severity, status, and linked applied controls.
Webhook endpoint — A registered URL CISO Assistant calls when configured events happen (e.g. an audit closed, an applied control updated). Used to notify external systems and trigger downstream automation.
Workflow — A small automation drawn on a canvas: a trigger, steps and wires. Runs inside CISO Assistant with the permissions of the user who published it, and only inside its domain. See .
Contract — A third-party agreement attached to a supplier entity or solution, with terms, dates, and renewal information.
Control — Generic term. Disambiguate against applied control (concrete instance) and reference control (template).
Current risk — The risk level given the applied controls already in place — the state of risk today. The middle tier of CISO Assistant's three-tier model: inherent (no controls) → current (existing controls) → residual (existing + planned controls).
Custom field (also custom attribute) — An organisation-defined attribute that can be attached to platform objects (projects, risks, assets, suppliers, contracts, and more) to capture typed, filterable, searchable metadata beyond the built-in fields.
CWE — Common Weakness Enumeration. A catalogued category of software weakness, used to tag vulnerabilities and security advisories.
Data subject — The category of individuals whose personal data is processed (customers, employees, prospects, etc.). Surfaces in the privacy register and right-request workflows.
Data transfer — A record of cross-border or cross-entity movement of personal data, with destination, legal basis, and safeguards.
Document revision — A single revision of a Managed document. Carries a version number and a lifecycle status (draft → in review → change requested → validated → published → deprecated).
Domain — The top-level container in CISO Assistant: a business unit, subsidiary, project, or any boundary used for organising work and isolating permissions via role-based access control. Sub-domains nest underneath. Internally a Folder. Demo and Starter are reserved for internal features.
DPIA — Data protection impact assessment (GDPR Art. 35): the analysis of a processing likely to result in a high risk to people's rights. Carried out as a quick form on the processing; once accepted, it is the processing's DPIA of record. See DPIA and screening.
DPIA screening — The short check, also called pre-DPIA, that decides whether a processing needs a DPIA. Once accepted, it sets the processing's DPIA required flag.
Escalation threshold — A point-in-time / impact pair attached to an asset assessment inside a BIA: "after 4 hours of outage, impact is high". Lets a BIA model how disruption escalates rather than recording a single worst-case impact.
Evidence — A document, screenshot, configuration sample, or any other artifact attached to an applied control or requirement assessment to substantiate compliance.
Evidence revision — A single versioned iteration of an evidence object. Replacing an attachment creates a new revision rather than overwriting the previous one; revisions carry a version number, an SHA-256 integrity hash, optional observation, and a link to the task occurrence that produced them when applicable.
External rating — A score published about one of your third parties by an outside rating service (SecurityScorecard, Bitsight, CyberVadis, …), recorded with the provider's scale so readings from different providers can be compared.
FindingsAssessmentFocus mode — A workspace mode that filters the entire UI to a single domain, hiding objects belonging to other domains. PRO feature.
Folder — Internal model name for a domain. See Domain.
Framework — A set of requirements covering patterns and expectations needed to comply with a regulation, prepare a certification, or establish a foundation. Shipped as a YAML library.
Preset — A reusable template describing a guided workflow: a set of starter objects to scaffold (audit, risk assessment, etc.) plus an ordered list of steps to follow. Library-backed or authored locally; applied to a domain to produce a Journey.
Processing — In a privacy register, an activity that operates on personal data (collection, storage, transfer, deletion). Captures purpose, lawful basis, recipients, and retention. Its GDPR role (controller, joint controller or data processor) puts it in the controller or the processor register.
Processing nature — A catalogued type of processing operation (collection, storage, transfer, disclosure, deletion, …) used to characterise a processing.
Processor register — The record of processings carried out on behalf of clients (GDPR Art. 30(2)): processings whose GDPR role is data processor, with the clients they serve. The controller register (Art. 30(1)) holds the rest. See Privacy register.
Project — In the project-management module, a planned initiative with deliverables and milestones. Distinct from the legacy meaning of "project" in older CISO Assistant documentation — see Perimeter.
Purpose — In a privacy register, the lawful reason for which personal data is processed.
Representative — The person responsible for answering the questionnaire or requirements of an entity assessment.
Requirement — A single normative statement inside a framework.
Requirement assessment — The evaluation of one requirement inside an audit (status, score, evidence, applied controls).
Requirement mapping set — Internal model name for the catalog object backing a mapping library. See Mapping.
Residual risk — The risk level expected once all planned applied controls have been implemented — the target state of the action plan. The bottom tier of CISO Assistant's three-tier model (inherent → current → residual), and the figure used as input to risk-acceptance decisions.
Responsibility matrix — An assignment of actors to activities, used in project workflows and accreditation processes. Supports RACI, RASCI, and RAPID conventions.
Responsibility role — The role attached to an actor on an activity inside a responsibility matrix (e.g. R/A/C/I in RACI, or R/A/S/C/I in RASCI). Defined per matrix.
Right request — In a privacy register, a data-subject request under GDPR or equivalent (access, rectification, deletion, portability).
Risk acceptance — Formal record of an organisation's decision to tolerate a residual risk without further treatment. Carries an approval workflow; approval requires the Approver role.
Risk assessment (also risk study) — A scenario-based evaluation of risk over a perimeter.
Risk matrix — A configurable lookup table that derives risk level from probability and impact. Imported from a library. The matrix is fixed per risk assessment once the assessment is created.
Risk scenario — A building block of a risk assessment: combines threats, assets, and existing controls into a story whose probability and impact can be evaluated.
Role — A bundle of permissions. Four built-in roles ship with the platform; PRO editions also support custom roles.
Domain Manager — can set up and access everything on a domain.
Analyst — can input and read data, but cannot change a domain's settings.
Reader — read-only on the domain's items.
Approver — can validate workflows on objects for a domain (e.g. risk acceptance).
Role assignment — The attachment of a user (or user group) to a role within a domain. The unit of access control.
RO/TO couple — In EBIOS RM, the pairing of a Risk Origin (who attacks) with a Target Objective (what they want). The seed for strategic and operational scenarios.
Solution — A product or service provided by an entity.
Stakeholder — In EBIOS RM, an internal or external party with a relationship to the studied system. Evaluated for trust level and dependency.
Strategic scenario — In EBIOS RM, the high-level "what" of an attack: a Risk Origin, a Target Objective, a path through stakeholders, and an outcome.
Task template — Internal model name for a task definition. See Task definition.
Team — A named grouping of users used for collaborative ownership of objects, with a leader, optional deputies, members, and an optional team email for notification routing. Distinct from a User group (which is role-scoped to a domain — see the disambiguation in Actors and teams).
Terminology — An organisation's overrides to the platform's default labels, used to align the UI with internal vocabulary.
Third-party workspace — The folder holding a third party's questionnaire, one per entity per domain. Access granted there does not reach up into the parent domain, which is what makes it safe to give an external party an account. Internally an enclave Folder.
Threat — A catalogued source of harm — reusable across scenarios. Optional: assessments can be performed without referencing threats explicitly.
How the Community and PRO editions differ, how contributor seats are counted, and where each edition can run
CISO Assistant ships in two editions:
Community — the open-source edition, free to use, with the platform's core GRC capabilities. Self-hosted only.
PRO — the commercial edition, built on top of Community, adding enterprise features (sub-domains, focus mode, advanced insights, custom roles, validation flows, and more) plus official support.
A full feature-by-feature comparison lives on the Community vs PRO page on the intuitem website — we keep the matrix there so it stays in sync with pricing and release cycles. This page is here to explain the commercial concepts that intersect with the platform itself: contributor seats and where PRO runs.
PRO is licensed by contributor seats — the number of users in your instance who can actually make changes.
A user counts as a contributor as soon as they have any create / edit / delete permission anywhere in the platform — that is, any role that grants add_…, change_…, or delete_… rights. Concretely, the typical contributor is someone with the Analyst, Domain Manager, or Administrator role (or a custom role that confers similar write rights). Read-only users — anyone whose role only grants view_… permissions — do not consume a seat.
Two narrow categories of users are explicitly excluded from the seat count, even though they may perform meaningful work in the platform:
Pure approvers — a user whose only write capability is signing off on a . In practice this is the built-in Approver role: its single write permission, change_validationflow, is registered as a non-seat permission. If the same user also holds any other write right (e.g. they're an Analyst who happens to be an approver too), the seat is still counted — being an approver doesn't subtract from the count, it just doesn't add one on its own.
External third-party representatives — vendor-side users who log into the to fill in entity assessments. They are flagged as external (is_third_party) and are systematically excluded from the count regardless of which write permissions their role grants.
The intent behind the two exceptions is narrow: the seat count tracks internal contributors authoring and maintaining your GRC content, while sparing two patterns where counting would feel punitive — pure sign-off workflows, and external vendors who don't belong to your organisation in the first place.
The instance compares the number of contributors against the seat allowance carried by your license. The current count is visible from About CISO Assistant, opened via the three-dot menu next to your name in the sidebar footer — so you can see at any time how many seats are used and how many are available.
PRO is available on both deployment models, and the feature set is identical between them:
On-premises — you host the platform on your own infrastructure (Linux VM, Kubernetes via the , or any of the deployment methods documented under ). Your data stays in your network. This is the right model when sovereignty, air-gapping, or strict residency requirements rule out a managed service.
SaaS — intuitem hosts and operates a managed instance for you. No infrastructure to run, automatic upgrades, backups handled. The right model when you'd rather focus on the GRC programme than on running a Django application.
You can move between the two models — there's no architectural difference, and the data formats (domain exports, library YAML, audit exports) are stable across deployments.
For organisations that don't want to track individual seats — typically large enterprises, MSSPs, public-sector deployments, or any environment where contributor headcount fluctuates often — both the on-premises and SaaS editions are available with an unlimited plan. Under an unlimited plan, the platform doesn't enforce a seat count and you don't need to manage role assignments around license limits.
A SecNumCloud version is also available, with dedicated hosting under the highest available cloud-security qualification. It is offered in unlimited mode. See the for details.
— current plan tiers and what each includes.
— the full feature comparison.
— the governance object whose approvers don't consume a seat.
— the auditee surface used by external representatives, who also don't consume a seat.
CISO Assistant is an open-source GRC (Governance, Risk and Compliance) platform — a different take on cybersecurity posture management, built on a few load-bearing ideas:
Explicitly decoupling compliance from security-operations implementation.
Providing simplified tools for decision-making.
Assessing a program, product, or whole organisation against standard frameworks.
Letting you bring your own framework via a simplified DSL.
Acting as a one-stop shop for the Governance, Risk, and Compliance layers.
Introduction — design philosophy and vocabulary.
Concepts — the central objects you work with: domains, perimeters, applied controls, assets, assessments, risks.
Installation — getting CISO Assistant running.
Configuration — organisation setup, IAM, SSO, and customisation.
— recorded demos and walkthroughs.
— subscription tiers and what's included.
— feature-by-feature comparison.
— partner programme for integrators and resellers.
Features — a catalogue of shipped capabilities.
Guides — task-oriented walkthroughs, both onboarding and ongoing operations.
AI and Integrations — the REST API, the MCP server, and third-party integrations.
Contributing — how to extend CISO Assistant and improve this documentation.
A library is a bundled set of catalog objects — frameworks, threats, risk matrices, reference controls, mappings, security advisories, CWE entries — distributed as a YAML file.
Libraries are how content gets into CISO Assistant. They make the platform extensible: anything from a regulator's framework to a vendor's threat feed to your organisation's internal control catalogue is just another library.
A library starts life as a stored record — its YAML is parsed and registered but contents stay inactive. Loading it materialises whatever catalog objects the YAML declares — any subset of framework, risk matrix, threats, reference controls, or mapping set (all dashed, all optional). A loaded library can also declare dependencies on other loaded libraries — for example, a framework library that ships its companion reference-control catalogue as a separate dependency.
A library can be in one of two states:
Stored — the library is known to the instance but its content hasn't been activated yet. It's visible in the libraries store, ready to be loaded on demand.
Loaded — the library is active. Its catalog objects show up across the platform: a loaded framework becomes available when creating an audit; a loaded threat appears in the threats list; loaded reference controls power autosuggestion.
A library typically contains a single primary object (for example, one framework) but may bundle related ones — a framework alongside its companion reference-control catalogue and its mapping to a sibling framework.
Library content is referenced by URN (Uniform Resource Name), an immutable identifier that survives renames and re-imports.
Built-in libraries ship with the platform — over 100 frameworks plus the standard threat, matrix, and reference-control catalogues.
Community libraries are contributed by the open-source community; see .
Custom libraries can be built locally and loaded without sharing them, useful for internal frameworks or control sets.
See for the Excel format and authoring workflow.
Libraries are versioned. When you update a library from the Catalog, its new content is applied to all linked objects in the instance, including existing audits. Inspect the updated content before applying it. See and .
Stored library
StoredLibrary
Inventory entry, contents inactive
Loaded library
LoadedLibrary
Activated; contents visible across the platform
Mapping set
RequirementMappingSet
Crosswalk between two frameworks

A framework is a normative body of requirements that audits are measured against — an industry standard (ISO/IEC 27001, NIST CSF, SOC 2), a regulation (NIS2, DORA, GDPR), a custom internal standard, or any other structured set of requirements.
In CISO Assistant, frameworks are shipped as YAML libraries and are the foundation of every audit.
A framework lives inside a loaded library and is read-only. It comprises a tree of requirement nodes — some assessable, some structural — linked parent-to-child via the self-loop. Creating an audit instantiates the framework: each assessable node becomes a requirement assessment inside the audit. Requirements can optionally suggest reference controls (templates for the applied controls that satisfy them), and a framework can be mapped to other frameworks for cross-walks.
A framework is a tree of requirement nodes. Most nodes are assessable — concrete requirements you evaluate one by one — while others act as section or chapter headings that organise the tree. Each assessable node becomes a inside an audit, carrying its own status, score, and evidence.
Frameworks can define a default scoring scale with a minimum score, a maximum score, and optional level descriptions. For example, a CMMI-style framework may use 0..5, while another framework may use 1..4 or 0..100.
An individual requirement node can override that default scale with its own min_score, max_score, and scores_definition_ref. These overrides are useful when a standard mixes different scoring shapes in the same tree: for example, a mostly maturity-based framework that also contains binary pass/fail requirements.
The override is resolved independently for each field:
If a requirement defines min_score, that value is used; otherwise the audit-level minimum is used.
If a requirement defines max_score, that value is used; otherwise the audit-level maximum is used.
If a requirement defines scores_definition_ref, those labels (resolved from the framework's alternatives registry) are used; otherwise the audit-level labels are used when they fit the requirement's effective range.
Per-requirement label overrides go through a named alternatives registry declared on the framework alongside its default scale. Each requirement references an entry by name, keeping shared scales DRY and avoiding duplication on the node:
One-off scales that only apply to a single requirement are added as a new entry in the framework's alternatives registry and referenced by name, just like shared scales. The node always carries a reference, never inlined labels.
The audit copies the framework's scores_definition (default scale + alternatives) at creation, so per-requirement references resolve against the audit's own copy. This keeps the audit self-contained: customising the audit's scale later doesn't break references on its requirements.
Roll-ups keep mixed scales comparable. Average-based aggregation normalises each requirement score against its effective range before computing the parent or global score, then displays the result on the audit scale. Sum-based aggregation remains a raw weighted sum, so each requirement contributes its own effective maximum.
CISO Assistant ships with 100+ built-in frameworks covering most international standards and regulations. When none of them fits your needs, you can build your own — see or follow the .
A mapping (or crosswalk) is a directed graph linking the requirements of one framework to those of another, using the convention. Once a mapping is loaded, an existing audit can be projected onto the target framework — reusing requirement assessments where the mapping is strong, surfacing gaps where it isn't.
Framework
Framework
Read-only catalog object
Requirement
RequirementNode
Tree node (assessable or section)
Library
LoadedLibrary
Active library bundle
Audit
ComplianceAssessment
One per (framework × domain × optional perimeter)
Reference control
ReferenceControl
Template for an applied control
framework:
scores_definition:
scale: # default scale, inherited by requirements that don't override
- score: 0
name: "N/A"
- score: 1
name: "Initial"
- score: 5
name: "Optimised"
alternatives:
binary: # named alternative shared by several requirements
- score: 0
name: "No"
- score: 1
name: "Yes"
requirement_nodes:
- urn: ...:r1
min_score: 0
max_score: 1
scores_definition_ref: binary # reference by name, DRY
- urn: ...:r2
min_score: 0
max_score: 1
scores_definition_ref: binary # same reference, same scaleHow IAM, the domain hierarchy, default roles, and cross-domain visibility shape what each user sees
This page is the mental model for how access and visibility work in CISO Assistant. The full configuration-side deep-dive (SAML / OIDC / MFA / PATs / accounting) lives in Understanding the IAM model; this page focuses on the three things every user needs internalised before they can predict what they will and won't see on screen:
Almost everything is bound to a domain.
Permissions are inherited down the domain tree.
Some permissions are granted by a domain default role.
The combination of these three is what produces the most common "why am I seeing items from another domain here?" moment, which the last two sections explain and resolve.
The platform's primary scoping unit is the domain (see ). Almost every operational object you create carries a domain — that's what drives who can see it and how it rolls up in reports.
The list is long on purpose — to make the model concrete:
Compliance: audits / compliance assessments, requirement assessments, evidences.
Risk: risk assessments, risk scenarios, quantitative risk studies, EBIOS RM studies, business impact analyses, security exceptions.
Operations: applied controls, policies, tasks, incidents, findings, findings binders, commitments.
Assets: assets, contracts, entities, solutions, representatives.
A small number of objects don't carry a domain because they're either system-wide (the user catalogue, the role catalogue, instance settings) or imported from a library catalogue. Everything else lives inside a domain.
The domain hierarchy is not just for organising the UI — it actively shapes access. A role granted on a domain applies to every domain beneath it. Give a user the Analyst role on a parent domain, and they get analyst-level access to every sub-domain underneath, without re-assigning them at each level.
This is why the tree shape matters as much as the names: putting "France" and "Germany" under a "EMEA" parent isn't decorative — it's the lever that lets EMEA-level managers see across both without granting them individual roles per country.
Permissions only flow downward: a role on a sub-domain does not grant any access to the parent. If you need a role-holder to see across siblings, the role goes on the shared ancestor.
A third-party workspace — the folder holding a vendor's questionnaire, an enclave internally — is the one place where the tree deliberately stops conducting. Access granted there does not reach up: a representative with view rights on their workspace sees their questionnaire and nothing the domains above it share. That containment is what makes it safe to give an external party an account on your instance at all.
Each third party gets one workspace per domain. See .
The same inheritance also drives reporting: most dashboards and analytics roll up across a domain and its descendants, so a leadership-level view on the parent domain is automatically the consolidated view across its sub-tree.
Every domain has members: the people its own and its sub-domains' IAM groups grant roles to. A domain can carry a default role — and the default role is what the domain grants its members, on the domain itself (and only there: the grant is never recursive).
For example: if the EMEA domain has the Baseline reader default role, then a user granted any role on EMEA or EMEA/France through the IAM groups is a member of EMEA, and can view EMEA's catalog objects. The grant never extends downward: EMEA's default role gives nothing on EMEA/France — what members see there comes from their own roles.
By default, the global domain (the root) carries the Baseline reader default role, which grants view access to catalog objects. Everyone in the organization is a member under the root, so everyone can read the root-level catalog.
Some objects exist to be shared. Frameworks, threats, risk matrices, reference controls, and other catalogue-style items wouldn't be useful if they were trapped in a single domain — every team needs to be able to pull from the same shared library. That's why catalog objects (usually stored in the root domain) are viewable by everyone in the organization.
Who is not a member, and why: third parties hold their grants inside third-party workspaces, which are not the domain's groups; service accounts hold direct assignments — no group grants them anything, so they read exactly what their own assignment names. Neither ever receives anything from a default role.
The default role is a dial, not a law. In the enterprise edition, each domain's default role can be tuned: replaced by a narrower view-only role (a custom role listing exactly the object types this domain shares), or removed entirely — a domain with no default role shares nothing ambiently, and its members see exactly what their own roles grant. Removing the root domain's default role goes all the way: the instance then runs an explicit-grant policy, where nothing is ambient — even the catalog is visible only to those whose roles name it. It's one field on the domain, so tightening takes effect immediately and is just as easy to revert.
Configuring default roles is an enterprise capability. In the community edition, the default role exists only on the root domain, fixed to Baseline reader — it cannot be changed, and no other domain carries one.
Assessments routinely compose objects across the tree. Risk assessments reference applied controls, threats, and assets; audits reference applied controls and evidences; findings binders reference applied controls and the requirement assessments they remediate.
When you're working inside one assessment, the platform's selectors and pickers don't just show you what's in the assessment's own domain — they show you everything you have access to. So a risk scenario authored inside the France domain can pull in:
A shared applied control attached to the EMEA parent domain (you can see it because of inheritance).
A threat from the global library (you can see it because the root domain usually has a "baseline reader" default role).
An asset attached to a sibling Germany domain (if your role gives you access there).
This is by design — composing across the organisation is the whole point of a centralised GRC platform — but it can be disorienting on day one. The rule is consistent: you see what you have access to, regardless of which domain you started on.
When you have access to many domains and you only want to think about one at a time, the platform exposes (PRO, default off). Focus mode scopes the entire application — every list, dashboard, count, and search — to a single domain and its sub-tree, hiding everything else for the rest of your session.
It does not grant or revoke permissions; it just filters the view. Use it when:
You operate across many client domains and want to work on one at a time.
Your organisation is large enough that the "all domains" view is overwhelming for day-to-day work.
You're running a demo or an onboarding and want the rest of the workspace out of the way.
When focus is engaged, all the cross-domain composition described above is suppressed — you'll only see assets, controls, and assessments from the focused sub-tree. Clearing the focus restores the full cross-domain view.
— domain hierarchy, IAM groups, restructuring, moving objects.
— who gets assigned what.
— full configuration-side deep dive (SSO, MFA, PATs, accounting).
— the PRO tool for scoping back the view.
Privacy: processings, personal-data inventories, right requests, data breaches.
Project management: projects, accreditations, responsibility matrices.
— how role assignments are actually stored and managed.
How CISO Assistant represents people, groups, and external parties for assignment and responsibility
Almost every object in CISO Assistant has an assignee — the applied control someone is responsible for, the audit a team is running, the contract a supplier signs. The platform represents all these counterparties through a single abstraction: the actor.
The actor is a one-to-one wrapper that always points at exactly one of three concrete records — a user, a team, or an entity (the three dashed edges are exclusive: a database check constraint enforces XOR). Every Assigned to / authorship / approver field on the platform (applied control assignee, audit author, task assignee, contract counterparty…) holds an actor, so a single code path resolves notifications and access regardless of the underlying type. Teams aggregate users via leader, deputies, and members — assigning to a team fans out to every user in it.
An actor is the unifying handle for anyone who can be assigned to work in CISO Assistant. Every actor wraps exactly one of three concrete underlying objects:
A user — a person with a platform account.
A team — a named grouping of users for collaborative responsibility.
An entity — an external party from the third-party register (typical for contracts and entity assessments).
The actor abstraction means the same Assigned to field on an applied control can hold a user, a team, or a supplier without the consuming code caring which it is. Notifications, emails, assignments, and reporting all go through the actor — they fan out to the right addresses regardless of the underlying type.
Actors are created automatically when their underlying object is created. You don't manage actors directly; you manage users, teams, and entities, and the actor records follow.
A team is a named grouping of users used for collaborative assignment — when the responsibility for something belongs to a working group rather than an individual.
A team has:
A leader — a single user accountable for the team.
Deputies — users who can act in the leader's place.
Members — the broader group.
An optional team email — used as the default notification address; if not set, emails fan out to the leader, deputies, and members individually.
Teams are first-class targets for assignments and notification routing. When work is assigned to a team, anyone with the appropriate role in the team's domain can act on it.
These two are easy to confuse:
A team is a collaborative group with members, a leader, and deputies. It's about who works together. Teams are voluntary, organisational, and can span any domain.
A user group is a (role, domain) pair on which users get placed. It's about what permissions someone has where. User groups are auto-created when a domain is created — one per role per domain.
You join a team for collaboration. You're placed in a user group for access. Both can carry users; they answer different questions.
Applied controls are assigned to one or more actors.
Requirement assessments can be assigned to an actor for completion.
Tasks are assigned to actors and notify them when due.
Risk scenarios are assigned to one or more actors.
Because all of these use the same actor handle, an audit log that says "assignee changed from team A to user B" is unambiguous, and the platform can resolve notifications the same way everywhere.
— where role-based access control happens
Entity assessments have a representative (an actor of type user, tied to an entity).
Validation flows route approvals through actors.
Actor
Actor
XOR(User / Team / Entity); auto-created with its target
User
iam.User
Platform account
Team
Team
Leader + deputies + members
Entity
tprm.Entity
Third-party party
A perimeter is an optional scope refinement an assessment or risk study can be attached to, inside a domain. Where a domain defines who owns the work and what they can see, a perimeter narrows the assessment to exactly what is being assessed — a product, a system, a process, a contract.
Perimeters are optional everywhere they appear: an assessment without a perimeter is scoped to its domain only. Use them when you need to track several distinct scopes inside the same domain, or to roll up assessments by scope rather than by domain.
The two concepts solve different problems — and the choice matters because it shapes both who can see what and how reports roll up.
Use a domain (or a sub-domain) when the boundary needs to enforce IAM scoping or anchor reporting. Domains are where roles are granted, where permissions stop, and where dashboards aggregate. Pick a domain when different teams must have different levels of access, or when leadership wants to see one number per business unit / subsidiary / regulated entity.
Use a perimeter when the boundary is purely logical, inside an already-scoped domain. Perimeters split the work into distinct named scopes — a product, a system, a process, a contract — so you can run separate audits or risk studies against each without spinning up extra IAM machinery. Everyone with access to the domain sees every perimeter inside it; there's no per-perimeter access control.
A useful mental check: "Do these two scopes need different people seeing them?" — if yes, they're domains (or sub-domains). "Do these two scopes need separate assessments but the same audience?" — they're perimeters.

Catalogued vulnerabilities, weaknesses, and the feeds that enrich them
The threat intelligence layer holds the catalogued knowledge CISO Assistant uses to prioritise vulnerabilities, qualify incidents, and connect operational findings to the wider security ecosystem. Today it covers security advisories, weakness catalogues, and a small set of public enrichment feeds; the surface is expected to grow significantly in upcoming releases.
A security advisory is a catalogued security warning published by a vendor, CERT, or standards body. CISO Assistant supports four sources today:
CVE — the MITRE/NIST CVE Program identifiers (CVE-YYYY-NNNN).
EUVD — the EU Vulnerability Database (post-NIS2 European equivalent).
GHSA — GitHub Security Advisories.
Other — for sources that don't fit the above.
Each advisory carries the usual identifying metadata (reference ID, published date, references) plus the bits that drive prioritisation:
CVSS base score and CVSS vector — severity per the standard scoring system.
EPSS score and EPSS percentile — probabilistic exploitation likelihood from FIRST.
Active exploitation flag and the KEV date when the advisory landed on CISA's Known Exploited Vulnerabilities list.
Aliases — cross-references to other identifiers when an advisory exists under multiple IDs.
Advisories are catalog objects: library-backed, root-folder-published, referenced from vulnerabilities and assets rather than re-created per scope.
The Common Weakness Enumeration is MITRE's catalogue of software-weakness categories — buffer overflows, missing authentication, improper input validation, and so on. Where a security advisory is "this specific vulnerability in this specific product", a CWE is "this class of flaw". CWEs are catalogued separately and tagged onto advisories and vulnerabilities to enable categorical analysis ("how many of our open vulns are credential-handling bugs?").
CWEs ship as their own catalog library; loading the CWE library makes the entries available across the platform.
A vulnerability in CISO Assistant is the organisation-specific record — "we have this exposed in our environment, here's the SLA". It links to:
One or more security advisories — the upstream finding(s) it corresponds to.
One or more CWEs — the weakness categories it belongs to.
This linkage is what lets enrichment feeds work end-to-end: advisories get scores from EPSS, exploitation status from KEV, and category mappings from NVD; vulnerabilities inherit that context via their advisory links and surface it in dashboards and SLA prioritisation. See .
Three public feeds enrich the catalogue when enabled — they keep the threat-intel layer current without manual data entry. Configuration is under :
KEV feed — CISA's Known Exploited Vulnerabilities; flags advisories under active exploitation.
EPSS feed — FIRST's Exploit Prediction Scoring System; attaches a probabilistic exploitation score.
NVD enrichment — pulls extra metadata from the NIST National Vulnerability Database (CWE mappings, affected configurations, references).
Each feed is a separate flag and can be toggled independently. The platform stores feed status, network timeout, and last-update timestamps centrally.
The threat-intelligence surface is being expanded to cover full STIX-style threat intelligence — threat actors, attack patterns, indicators of compromise, campaigns, sightings — along with STIX 2.1 / TAXII / MISP / OpenCTI interop and DORA cyber-threat notification workflows. The objects on this page remain the entry point; new SDOs will sit alongside them.
Directed graphs linking the requirements of one framework to those of another
A mapping (also called a crosswalk) describes how the requirements of one framework relate to those of another. Once a mapping is loaded, an audit performed against the source framework can be projected onto the target framework — reusing the existing requirement assessments where the mapping is strong, surfacing gaps where it isn't.
Mappings are catalog objects: defined once as a YAML library, loaded into the platform, and applied on demand.
A mapping set is the unit shipped by a library — it pins exactly one source framework and one target framework. Inside the set, each individual mapping connects one source requirement node to one target requirement node and carries a relationship type (equal / subset / superset / intersect / not_related). Applying the set to an audit projects the existing requirement assessments onto the target framework: full-coverage relationships (equal, superset) copy directly; partial-coverage (intersect, subset) require manual review.
Most organisations have to demonstrate compliance against multiple frameworks at once — ISO 27001 plus SOC 2 plus a sector-specific regulation, for instance. Without mappings, you re-assess the same control posture against every framework's requirement list, which is busywork. With mappings, you assess once and project.
A mapping is a directed graph linking assessable nodes of a source (SRC) framework to assessable nodes of a target (TGT) framework, using the convention from project.
Each relationship between a SRC node and a TGT node has a type, which is easiest to read as a set relation between what each requirement covers:
No relationship — the two requirements are disjoint; nothing carries over.
Equal — the two requirements are equivalent in scope and intent.
Subset — the SRC requirement is contained within (narrower than) the TGT requirement.
Superset — the SRC requirement contains (is broader than) the TGT requirement.
The directionality matters: a mapping from A → B does not automatically imply B → A. Reverse mappings can be generated, but the relationship type usually inverts (a SRC subset becomes a TGT superset).
Once a mapping library is loaded, it can be applied to an existing audit:
Open the source audit.
Click Apply mapping and pick the target framework.
The platform creates a new audit on the target framework and copies over the requirement assessments where the mapping is strong (typically equal), leaving the rest to be assessed.
The apply-mapping feature can also clone an audit onto the same framework — useful for creating a new revision while keeping the previous one for history.
You don't need a direct mapping between every pair of frameworks. If the platform holds a mapping from A → B and another from B → C, it can chain them automatically to project an A audit onto C, using B as a pivot — even though no one ever authored an A → C crosswalk.
The mapping engine treats the loaded mapping sets as a directed graph of frameworks and searches for a path between your audit's framework and the target you pick. When you open an audit and choose Apply mapping, the list of available targets already includes every framework reachable through the graph — both directly mapped ones and those reachable only through one or more pivots. You select the destination; the chaining happens behind the scenes.
How the chain behaves:
Coverage degrades to the weakest hop. Full-coverage relationships (equal, superset) chain cleanly. If any hop in the path is partial (subset, intersect), the projected result is marked partial and flagged for manual review — a chain is only as strong as its loosest link.
The best path wins. When several pivots connect A to C, the engine keeps the path that successfully maps the most requirements, and records which intermediate framework(s) it went through so the projection is auditable.
This is what makes a modest set of crosswalks go a long way: a hub framework such as ISO 27001 or NIST CSF that is mapped to many others effectively becomes a translation pivot between all of them.
Many cross-walks ship as built-in or community libraries (ISO 27001 ↔ NIST CSF, SOC 2 ↔ ISO 27002, and so on). When none of them fits, see for the Excel workflow using prepare_mapping_v2.py.
— the UI flow for applying a mapping
Intersect — the two overlap in part but neither contains the other.
Depth is bounded. Chaining is limited by the Mapping max depth setting in (default 3 nodes — i.e. up to one pivot, A → B → C). Raise it (up to 5) to allow longer chains (A → B → C → D…), at the cost of progressively weaker, more indirect inferences and slower computation.
Mapping set
RequirementMappingSet
One per (source, target) library entry
Mapping
RequirementMapping
Single SRC → TGT pair, typed
Requirement
RequirementNode
Read-only catalog entry from a framework

The lookup tables that derive a risk level from probability and impact
A risk matrix is a configurable lookup table that maps a (probability, impact) pair to a resulting risk level. It's what turns "likely × severe" into "critical" — the encoded judgement that lets a risk assessment be more than a free-form narrative.
Risk matrices are catalog objects: defined once, packaged into libraries, loaded into the platform, and reused across many risk assessments.
A risk matrix is a JSON definition (probability axis, impact axis, risk levels, and the grid linking them) bundled in a library. Once a risk assessment is created against a matrix the binding is permanent — the FK uses on_delete=PROTECT — because switching matrices mid-assessment would silently change every risk level, which is what auditors don't want. Each risk scenario in the assessment reads its inherent, current, and residual risk levels from the same matrix's grid using the (probability, impact) pair the assessor sets at each tier.
A matrix has four pieces:
Probability levels — the ordered scale used for likelihood (e.g. negligible, low, medium, high, very high).
Impact levels — the ordered scale used for severity (financial, reputational, operational, or whatever scale the organisation uses).
Risk levels — the resulting categories (e.g. low / medium / high / critical), usually colour-coded.
The grid — the lookup from each (probability × impact)
The grid is the substance of the matrix; the visual rendering (orientation, colours, layout) is handled by the UI based on the loaded matrix definition.
When a risk assessment is created, its risk matrix is captured and stays fixed for the lifetime of that assessment. Re-evaluating the same scenarios against a different matrix would silently change the risk levels under your feet, which is exactly what auditors don't want.
If you change matrices mid-programme, you create a new risk assessment against the new matrix and migrate the scenarios. The old assessment keeps its history; the new one starts clean against the new scale.
Each scenario in a risk assessment is evaluated three times against the chosen matrix:
Inherent risk — what the risk would be with no controls.
Current risk — what it is today given existing applied controls.
Residual risk — what it will be once planned applied controls are implemented.
The matrix is the same for all three tiers; what changes is the (probability, impact) pair the assessor sets at each tier. See for how the three tiers are used.
Matrices ship as YAML libraries — the same format as frameworks, with _meta and _content sheets defining the probability/impact/risk axes and the grid. They are typically authored in Excel using the templates under tools/excel/matrix/ and converted to YAML.
Designing a matrix correctly — particularly the grid — is non-trivial. Start from one of the existing examples and adapt the levels and grid logic rather than building from scratch. See for the workbook fields and an example.
Risk matrix
RiskMatrix
json_definition with probability / impact / risk / grid
Risk assessment
RiskAssessment
risk_matrix FK is fixed at creation (PROTECT)

create your metrics in one form, from a library definition or from scratch
record their values for each period, or compute them from other metrics
create dashboards with builtin and custom metrics
A metric definition is the catalog template — qualitative (level) or quantitative (number with unit) — and can be shipped via a library. Each instance scopes one definition to a domain and carries the operational fields: owner, target value, collection frequency (auto-stale logic kicks in when the latest sample exceeds the cadence + grace period). Samples are the values recorded against an instance; a metric with a collection frequency records one value per period (month, quarter, week…), and a new value for a period replaces the previous one. The definition also holds how a value reads: its direction (higher is better, lower is better, or none) and the decimals it is shown with. You rarely create a definition by hand: the creates it with the metric. Dashboards are independent of the metrology pipeline: they comprise widgets, and each widget either reads a custom metric instance, pulls from a builtin sample (a system-computed daily snapshot attached generically to any tracked object — assets, audits, projects, incidents, etc.), or just displays free text.
Consider metric definition as a template of your metric. It serves as the guideline of the actual metric instance that you will track accross your domains.
You will learn how to import off-the-shelf metrics definitions on CISO Assistant.
Click "Metric definitions".
Click here to proceed to the next menu.
You can also preview the content before importing it.
Click "Metric definitions" to revisit the main metrics page.
Click "Search..." to begin finding specific metrics.
You can now instantiate this metric as you see fit for your domains.
You can now instantiate this metric as needed across your domains. Keep in mind that you can also create your own metric definition directly without going through the library.
Metrics can be quantitative (number with unit) or qualitative (choices):
You can add your options during declaration or afterward:
The "higher is better" setting is used to indicate if the trend is a good thing or not.
The metric instance is the projection of the definition to a specific domain and it's what you'll be tracking.
Parameters:
Metric definition (it will inherit its settings)
Domain: the scope of this metric
Status: lifecycle of the metric. The stale is specially interesting as the application will auto-toggle it according to the data freshness
Collection frequency: expected collection frequency, on which we add a grace period before toggling the metric to stale status
This is the actual data of the metric instance on a given timestamp.
Keep in mind that you can add the data manually or through all the supported integrations (API, n8n, etc.). Note that data cannot be in the future.
A computed metric is an instance whose samples come from a formula rather than from people or integrations: a training coverage from the employees trained and the headcount, a click rate from the clicks and the people tested, a status level from a threshold on either. Nobody types its numbers in; the application computes them on the instance's collection frequency, and on demand with Refresh now.
Start from the metrics you track. Pick metric instances, write a formula, name the result: the application creates the instance and its definition. See , or follow .
The formula runs once per period. The instance's collection frequency cuts time into calendar periods, and each input contributes its latest sample in the period. The series is complete from the start and follows corrections to its inputs. See the .
The result is a number or a level. A level metric returns one of an ordered list of texts, such as On track and Behind.
All of it is described in .
Dashboards are the visual representation of the metrics and support:
custom metrics instance (multiple charts)
built-in metrics (multiple charts)
markdown text widget
In edit mode, you can add different widgets, place and resize them as you see fit:
Once done, you can go back to view mode to see the result:
In addition to the custom metrics for your internal KPI and KRI, you can also include some of the built-in metrics tracked by the platform:
Those are updated on each change of your data and tracked as daily metrics.
Assigned to: actor responsible for the metric instance and its updates.
The formula lives on the definition. The application creates the definition with the metric, or reuses an identical one, and the formula reads exactly the instances you picked. Editing it never changes another metric. See .
The domain holds what the formula reads. A computed metric lives in a domain holding every metric it reads, and its value is the same for everyone allowed to see it.
Metric definition
MetricDefinition
Catalog template; optional library FK
Metric instance
MetricInstance
One definition × one domain
Sample
CustomMetricSample
A recorded value on an instance, for a period when the instance has a frequency
Builtin sample
BuiltinMetricSample
System-computed; daily; ContentType GFK to any object
Dashboard
Dashboard
Container
Widget
DashboardWidget
KPI / donut / pie / bar / line / area / gauge / sparkline / table / text








A business impact analysis (BIA) measures the operational, financial, and reputational consequences of disrupting specific assets or business processes — the input to any serious resilience plan.
Where a risk assessment asks "what could go wrong?", a BIA asks "if it does, how bad is it, and how quickly does it become unacceptable?".
A BIA is an assessment scoped to a perimeter. For each asset in scope, the platform captures:
Asset assessment — the impact of disruption along one or more dimensions (financial, operational, regulatory, reputational).
Escalation thresholds — the durations after which disruption becomes severe, critical, or unacceptable — often expressed as RTO (Recovery Time Objective) levels.
The output is a prioritised view of which assets your continuity plan must protect first.
BIAs read from the asset inventory — assets are first-class objects, not BIA-private records.
The criticality output can drive risk scenario prioritisation for the same assets.
For organisations subject to DORA, BIA results feed into incident-reporting workflows.
A threat is a catalogued source of potential harm — a phenomenon, agent, or event that could compromise an asset, a business process, or a regulatory obligation.
Threats are reusable building blocks: a single threat (say, "Phishing") may appear in many risk scenarios, EBIOS RM operational scenarios, and applied control rationales without being duplicated.
Risk scenarios — most qualitative risk scenarios name the threat that drives them.
EBIOS RM operational scenarios — threats are mapped to attacker techniques in the kill chain.
Vulnerability tracking — threats can be linked to vulnerabilities to express what could exploit this weakness.
Applied controls — controls can declare which threats they address.
CISO Assistant ships with curated threat libraries based on common sources — ENISA, the ISO 27005 illustrative threat catalogue, sector-specific catalogues. You can also create your own threats inside a domain, or contribute a custom threat library.
Adversary techniques — how an attacker acts, as catalogued by MITRE ATT&CK and ATLAS — are a separate catalogue. See .
A risk assessment can be done without referencing threats — the platform doesn't force it. Naming the threat behind a scenario makes the analysis sharper, more reusable, and easier to map back to threat-intelligence feeds.
A shared taxonomy for classifying assets, extensible per organisation
An asset class answers "what kind of thing is this?" for an asset — a server, a business process, a set of personal data. Classes form a tree, so a class can be a refinement of a broader one, and an asset points at most one class.
CISO Assistant ships with a taxonomy derived from the CIS asset categories (Devices, Software, Data, Users, Network, Facilities, Documentation, Business Process, and their sub-classes). You can extend that tree with your own classes, and hide the shipped ones you don't use.
The class is deliberately separate from the domain an asset lives in and from its primary/supporting type. Those three answer different questions:
Keeping them apart means you can classify a supporting asset in one domain and a primary asset in another under the same class, and then read your whole estate by class regardless of who owns it.
A class refines a parent class, forming the taxonomy tree. An asset is classified by at most one class — the link is optional, so an unclassified asset is valid and stays fully usable. The domain still scopes the asset; classes are organisation-wide and carry no access control of their own.
The shipped classes are marked Builtin. They are re-created every time the platform starts, which is why they behave differently from classes you create:
They cannot be renamed or deleted — a rename would be undone and a deletion would silently reappear on the next restart. The edit form states this: "Built-in classes are provided with the product: they can be hidden, but not renamed or deleted."
They can be hidden. Clearing Visible removes a class from the picker used when classifying an asset, without touching any asset already classified under it. The form notes: "Hidden classes stay listed here but are no longer offered when classifying an asset."
Classes you create are yours entirely — rename, re-parent, hide, or delete them (see ). Creating a class under a built-in one is the intended way to extend the shipped taxonomy: the child is your own class, and the built-in parent stays untouched.
Hiding a class that still has visible sub-classes does not orphan them. The class remains in the picker as a non-selectable grouping row so its descendants keep a readable path.
In the sidebar under Extra → Asset classes.
The table lists Name, Parent class, Description, Translations, Visible and Builtin. Filters let you narrow to visible or built-in classes.
Use the add button on the Asset classes page. The form asks for:
Name — free text.
Description — optional.
Parent class — optional. Leave it empty to create a top-level class. The picker is a tree, and it excludes the class you are editing along with everything beneath it, so a class can never become its own ancestor.
Translations — see below.
To add a child directly under an existing class, open that class and use the add button on its children table: the Parent class is filled in for you.
Built-in class names are translated by the interface itself and follow the language you are using. Classes you create are free text, so they need their own translations.
The Translations editor on the class form takes a Name and a Description per language. When you are using a language that has no entry, the class falls back to the name you typed when you created it. That fallback is why a class can look "untranslated": it means no entry exists for your current language, not that translation is unavailable.
Deleting a class also deletes every class beneath it. The confirmation dialog lists exactly what will go, so check it before confirming.
Assets classified under a deleted class are never deleted — they simply become unclassified. They keep every other property, and you can re-classify them afterwards.
On the asset form, the Class field is a searchable tree. Only visible classes are offered. Type to search across the whole taxonomy, or expand the tree to browse it; the selected class shows its full path so you can tell two same-named classes apart.
The Assets by class page turns the taxonomy into a way to navigate your estate. Reach it from the Assets page using the tree icon.
Each class shows a count of the assets beneath it, rolled up through the tree, so a parent class with no assets of its own still shows what its children hold. Expanding a class lists the assets classified directly in it; classes with no assets at all are hidden unless you clear Hide empty classes.
Assets with no class appear in a separate Unclassified group at the bottom — a useful starting point when you are working through a backlog of unclassified assets.
Only assets you are allowed to see are counted or listed, so the totals on this page are yours, not the whole database's.
Asset exports include an asset_class column holding the class's full path, with each level separated by / — for example assetClassDevices/assetClassEnterpriseAssets/assetClassServers.
The path is used rather than a display name because it is unambiguous (two classes may share a name under different parents) and stable across instances and languages. Imports accept the same format in an asset_class column, and also accept a bare class name when only one class in the tree carries it.
If an import names a class that does not exist, the asset is still imported — it simply arrives unclassified, and the row is reported as a warning.
Domain exports carry asset classes the same way. When you import a domain into another instance, any of your own classes that the target does not have are re-created, while built-in classes bind to the ones already there.
— a different organisation-defined override
— confidentiality labelling, not typology
Visible — on by default.
Who owns and can see it?
Domain
Does it deliver value directly, or support something that does?
Type (primary / supporting)
What kind of thing is it?
Class
Asset class
AssetClass
Self-referencing tree via parent
Class (on an asset)
Asset.asset_class
Optional; a single class per asset
Built-in
builtin
Shipped with the product
Visible
is_visible
Controls whether the class is offered when classifying
Guided, step-by-step workflows that bootstrap a domain along a recognised path
A journey is a guided, step-by-step workflow that walks a domain through a recognised process — getting an organisation ready for ISO 27001 certification, running a DORA readiness assessment, building out a privacy register, and so on.
Journeys remove the "what do I do first?" problem. Instead of staring at an empty domain and assembling the right libraries, assessments, and tasks by hand, you pick a journey, apply it to a domain, and start working through the prescribed steps.
Journeys come in two flavours of object that mirror the Framework → Audit pattern elsewhere in the platform:
A preset is the template. It bundles a set of starter objects (typically an audit, a risk assessment, sometimes additional scaffolds) plus an ordered list of steps with descriptions and links into the platform. Presets are versioned and shipped via libraries; they can also be authored locally.
A journey is an instance of a preset, materialised in a specific domain. Applying a preset creates the scaffolded objects in the chosen domain and copies the step list onto the journey so that step statuses can be tracked independently from any other journey.
The same preset can be applied to multiple domains and yields a distinct journey each time.
Browse the catalogue. The presets page lists everything available on the instance — library-backed presets shipped with CISO Assistant (ISO 27001 starter, DORA readiness, NIS2, sector-specific bundles, etc.) and any presets authored locally. Filter by region tag to narrow the list.
Apply to a domain. Pick a target domain (or create one on the fly). Optionally let the journey create the scaffolded objects for you and apply any feature flags the preset suggests.
Work the steps. Each step has a title, description, and either a target object (e.g. "open the SOA review for the ISO audit") or a target URL inside the platform. Mark steps as in progress, done, or skipped as you advance, and add notes.
Scaffolded objects — the assessments and supporting records that get created when the preset is applied. Typical scaffolds include compliance assessments (with framework + implementation-group selection), risk assessments (with a chosen matrix), and occasionally task templates, perimeters, or entities.
Steps — an ordered list of actions to perform. Each step can point at a scaffolded object (via a named reference resolved at apply time) or at a generic internal route such as the SOA results page. Steps carry translations so the workflow speaks the user's language.
Feature flags — some presets enable optional features when applied (reports, sec-intel feeds, etc.), letting the journey come with a fully-configured environment.
Applying a preset requires permission to load libraries on the target domain. Viewing journeys and updating their step statuses follows the standard domain-scoped permission model — see .
Track progress. The recently-active journeys panel shows a progress ring per journey; the underlying step counts feed dashboards.
Upgrade when a newer preset ships. When a library-backed preset is updated, journeys derived from it flag an upgrade. Upgrading re-applies the newer template while preserving user-state (statuses and notes) on steps that survived the new version.
Adversary tactics and techniques catalogues, and the threat models built on them
A TTP catalog is a reference matrix of adversary behaviour: the tactics an attacker pursues (the why of a step, such as Initial Access or Exfiltration) and the techniques used to achieve them (the how, such as Phishing or Data Obfuscation). CISO Assistant ships MITRE ATT&CK and MITRE ATLAS as TTP catalogs.
A threat model is an attack sequence you build on top of one catalog. You pick the techniques that apply to your system, lay them out tactic by tactic, chain them into a graph, and link the result to the risk scenarios it justifies.
A threat and a technique answer different questions:
A threat is what could happen: a source of harm such as "Ransomware" or "Loss of power". It's what a risk scenario materialises.
A technique is how an adversary does it: a concrete, observed behaviour such as "Phishing" or "Exfiltration Over Web Service", catalogued with its tactics, platforms and mitigations.
Both can sit on the same EBIOS RM operational scenario: threats name the harm, techniques describe the attacker's operating mode.
A TTP catalog organises tactics in their published order. Each technique sits in one or more tactics; a sub-technique also points to its parent technique. A technique can list the reference controls (the catalog's mitigations) that address it. A threat model always draws on exactly one catalog, chosen at creation and locked afterwards; it optionally sequences that catalog's techniques into a graph, and optionally justifies one or more risk scenarios or quantitative risk scenarios. Separately, an EBIOS RM operational scenario can directly reference the techniques it uses, without going through a threat model.
Two control this area. Both are off by default.
TTP catalogs (ttps) — shows TTP catalogs under Catalog in the sidebar, technique pages, and the Techniques field on operational scenarios.
Threat modeling (threat_modeling) — shows Threat modeling under Risk in the sidebar and the Threat model field on risk scenarios and quantitative risk scenarios.
Threat models are built on a TTP catalog, so turn on both flags to use them.
TTP catalogs come from . Load one or more from the Libraries page:
Each library brings the catalog, its tactics, its techniques, and the mitigations as reference controls already linked to the techniques they address. When a newer version of a library drops a technique, the technique is marked deprecated rather than deleted, so existing links keep working; deprecated techniques are hidden from the matrix and from pickers.
Open TTP catalogs and pick a catalog to see it as a matrix: one column per tactic, in the publisher's order, with each technique listed under every tactic it belongs to. Techniques that have sub-techniques show a counter that expands them in place. Clicking a technique opens its detail page: description, tactics, parent technique, mitigations, and the groups (platforms, assets) it applies to.
Above the matrix, a search box filters by reference ID or name, and the catalog's filter chips, grouped by dimension (platform, asset, maturity), narrow the matrix further. Chips in the same dimension combine with OR; different dimensions combine with AND. A parent technique stays visible when only one of its sub-techniques matches. Clear filters resets everything.
With TTP catalogs enabled, the operational scenario form in workshop 4 gains a Techniques field next to Threats: "Adversary techniques (MITRE ATT&CK and other TTP catalogs) used in this operational scenario". The selected techniques are listed on the operational scenario page (or No technique when none is set) and in the EBIOS RM study report, each linking to its technique page.
Open Threat modeling in the sidebar and click Add threat model. Give it a name, choose its domain, and pick its TTP catalog. The catalog can't be changed later: "The catalog cannot be changed after creation."
The threat model page shows the usual details plus a read-only preview of the graph, with two actions: Select techniques and Edit graph. Both views share a Selection / Graph switch so you can move between them.
The Selection view is the catalog matrix in selection mode. Click the techniques that apply to highlight them, then Save; the header shows how many are selected. A selection is per cell: the same technique selected under two tactics is two separate steps, because a tactic changes what the technique means in the sequence.
The Graph view is an editor laid out in one lane per tactic, left to right in the catalog's order.
Add techniques — drag them from the palette into a lane. A technique only drops into the lanes of tactics it belongs to.
Connect steps — draw an edge from one node to another. Loops and backward edges are allowed, only self-loops and duplicates are refused.
AND / OR — when a node gets a second incoming edge, an operator node is inserted in front of it, set to OR. Use Toggle AND / OR to switch it: "AND requires every incoming step; OR accepts any of them."
Add a step — adds a custom node for a step the catalog doesn't cover.
Select a node to edit its context in the side panel: a Label, a Description, a Key step flag to highlight it, and links to the Assets it targets, the Applied controls that counter it, and the Vulnerabilities it exploits. Changes are kept locally until you click Save; Discard drops them.
With Threat modeling enabled, the risk scenario edit page has a Threat model field ("The attack sequence that justifies this scenario") and a button to create a new threat model on the spot. The risk scenario page then lists the linked threat model with a direct link to its graph. Quantitative risk scenarios have the same Threat model field.
Analysts and domain managers can create, edit and delete threat models in their domains; readers and approvers can view them. TTP catalogs, tactics and techniques are catalog objects: readable by every built-in role except third-party respondents, and loaded or updated through libraries.
Auto-link — "Draw an edge from every technique to every technique in the next occupied tactic. Existing edges are kept."
Hide empty tactics — collapses lanes with no node.
TTP catalog
TTPCatalog
One matrix per library, e.g. MITRE ATT&CK Enterprise Matrix
Tactic
Tactic
Matrix column; keeps the source order
Technique
Technique
Can sit in several tactics; sub-techniques have a parent
Mitre ATT&CK v19.1 - TTPs and Mitigations
MITRE ATT&CK Enterprise Matrix
Platform (Windows, Linux, macOS, IaaS, SaaS, Containers, …)
Mitre ATT&CK for ICS v19.2 - TTPs and Mitigations
MITRE ATT&CK for ICS Matrix
Asset (Control Server, Data Historian, …)
Mitre ATT&CK for Mobile v19.2 - TTPs and Mitigations
MITRE ATT&CK for Mobile Matrix
Platform (Android, iOS)
Reference control
ReferenceControl
The catalog's mitigations (e.g. ATT&CK M1031)
Threat model
ThreatModel
Lives in a domain; bound to one catalog
Graph node
ThreatModelNode
Technique, AND/OR operator, or custom step
Graph edge
ThreatModelEdge
Directed link between two nodes
Operational scenario
OperationalScenario
EBIOS RM workshop 4; has a Techniques field
Mitre ATLAS v2026.07 - TTPs and Mitigations
MITRE ATLAS Matrix
Platform (Predictive AI, Generative AI, Agentic AI, …) and maturity (Realized, Demonstrated, Feasible)
A commitment is the delivery date someone promises for a piece of remediation work — and the record of how that promise was arrived at, and what happened to it.
Every GRC tool has a due date field. What it doesn't have is the difference between a date the analyst typed in and a date the owner agreed to. Commitments make that difference explicit, and keep the history of the negotiation instead of overwriting it.
Commitments are gated by the commitment_management feature flag, off by default.
A commitment hangs off the object it is about — an applied control or a one-off task definition — and adds no fields to it. Each round of negotiation is one commitment row, so a renegotiated date does not erase the one it replaced. Two roles are in play: the owner, who is accountable for the object and therefore makes the promise, and the counterparty, who is anyone who isn't.
The steps between them, and who may take each one:
Two rules carry the intent of the model:
The owner cannot close their own commitment. Someone who both makes and confirms delivery of a promise is self-certifying. Fulfilment is a counterparty step, deliberately.
A third-party respondent is always the promising side. They can commit and decline; they can't sign off their own delivery.
When nothing is accountable for the object — no owner set — there are no sides, and anyone who can change the object can run the whole lifecycle. That's a deliberate escape hatch so an unassigned control doesn't deadlock.
Renegotiating doesn't overwrite the promised date. The current cycle is closed and a new one opens, carrying the previous committed date forward so a breach stays visible across the renegotiation. The panel shows Reopened n times and lists the closed cycles with the date each promised, who promised it, when, and the note that closed it.
This is the point of the whole feature: "the fix slipped three times and each time we heard about it after the date had passed" is a sentence a report can now support.
Neither is stored — both are read from the object every time it's displayed:
Slipped — the object's own ETA is now later than the date that was promised. The work has moved; the promise hasn't been renegotiated.
Breached — the promised date has passed and the commitment isn't fulfilled.
On an applied control or a one-off task definition, as a Commitment panel with the available steps and the history.
On the Commitments page under Governance — every promised date across controls and tasks in one table, filterable by state, domain, and who committed. Reading it needs the view commitment permission; taking a step needs Can take a commitment step.
Recurring task definitions have no commitment panel at all: a recurring definition is a template, not a single promise. Making a task recurrent while a commitment is still live is refused — resolve the commitment first.
The two look adjacent and aren't. A is a reviewer approving an object as it stands. A commitment is an owner promising a future date. One is a verdict on the present, the other a promise about the future, and an object can be subject to both.
Commitment
Commitment
One row per negotiation cycle
Owner
the object's accountable actor
owner on an applied control, assigned to on a task
Committed date
committed_eta
Frozen when the promise is made
Not started
Nothing has been promised yet.
In negotiation
A date is being discussed.
Committed
The owner has promised a date.
Declined
The owner refused, with a reason.
Fulfilled
The counterparty confirmed delivery.
Not started → Open a negotiation
anyone who can change the object
—
In negotiation → Commit to a date
the owner
a date
In negotiation → Decline
the owner
a note
Committed → Reopen the commitment
anyone
a note
Committed → Mark as fulfilled
the counterparty — not the owner
—
Declined → Reopen the commitment
anyone
—
An applied control is the main building block of the action plan: the actual action your team has implemented or will implement to address a security need. It can be technical, organisational, a process, a policy, a piece of documentation — anything that materially changes risk or compliance posture.
A single applied control can satisfy any number of requirements across any number of frameworks — it's where what the framework asks meets what the organisation actually does.
The applied control sits at the centre — anything that asks for action points to it; everything that proves the action took place hangs off it. Compliance work (requirement assessments), risk work (scenarios), follow-up work (findings), and operational maintenance (tasks — periodic reviews, evidence refresh, assignee rotation) all reference the same control, while evidences and protected assets accumulate on the other side. This is the decoupling principle made concrete: one applied control, many demand-side users.
Applied controls are fundamental for both compliance and remediation. They can derive from a reference control for consistency, or be created independently. They are always defined by the organisation and can be attached to the global domain or to a specific domain.
The status field is what turns an applied control from a static catalogue entry into a live, trackable piece of work. It's the single signal that drives roll-ups across audits, dashboards, action plans, and reporting — so it's worth understanding how it moves.
The target state of an applied control is Active, not Done. This is deliberate. A control is never finished in the way a project task is finished: a backup policy that's been written and approved still has to keep being followed, a firewall rule that's been deployed still has to keep being enforced, an access review that's been run still has to be run again next cycle. Reaching Active doesn't end the work — it starts the maintenance phase. The work shifts to keeping the control in Active, through periodic reviews, evidence refresh, and the that "maintain" it.
This is why the lifecycle has Active, Degraded, and Deprecated rather than Done. A control that's failing partially moves to Degraded (signal to act); a control that's no longer needed moves to Deprecated (retire with history intact). There's no terminal "done" state on purpose — the whole point of cybersecurity controls is that they're a continuous effort to stay in Active, not a checkbox you tick once.
The lifecycle runs through seven states:
Why the lifecycle matters across the platform:
Risk model — Current risk uses controls in active (and partly degraded); residual risk also factors in controls in to_do / in_progress (the planned ones). Moving a control from to_do to active is what closes the gap between residual and current.
Action plan — the Kanban view and the Control Plan grid both swimlane by status. A control sitting in
The transitions aren't enforced as a strict state machine — you can move a control between any two statuses — but staying within the lifecycle above makes audit trails and analytics meaningful.
Applied controls carry a structured cost field, so the financial weight of your security programme isn't a separate spreadsheet — it's attached to the controls themselves and rolled up wherever they appear (most notably on every ).
The platform models the build vs run distinction that controls actually carry in practice:
Each side accepts two inputs:
Fixed cost — a money amount (hardware purchase, licence fee, contractor invoice).
People days — a measure of internal effort. The platform converts this to money using the daily rate configured in General settings, so internal time is comparable with external spend.
From these inputs, the platform computes an annual cost for every control:
That single number is what feeds every roll-up — the action plan's budget overview, the per-assignee and per-domain breakdowns, the by-status / by-priority / by-CSF-function totals. Controls without a cost set contribute zero, so partial adoption of the financial tracking still works — you fill in numbers where you have them and the totals reflect what's known.
The annualised view matters because cybersecurity controls are continuous (see above). A capex-style "we paid €40k once" view doesn't compare well across controls; an "annual cost in steady state" view lets you put a single line per control on the budget and compare them like-for-like.
A reference control is a template for an applied control. Reference controls facilitate the creation of applied controls and help keep them consistent across the organisation.
They can be provided by security frameworks imported from a library, or you can create your own — in the global domain or in a specific domain. Reference controls are optional but recommended.
A policy is a specific type of applied control: a document describing what is expected from some part of your stakeholders. Putting your cybersecurity policies in CISO Assistant makes them readily available for audits, and lets you manage their lifecycle alongside the rest of your controls.
on_holdETA enforcement — when ETA passes and the status isn't active, the control is flagged overdue. Reaching active is what stops the clock.
Commitments — with enabled, a control carries the delivery date its owner has actually promised, separately from the ETA. The owner commits; someone else confirms delivery.
Framework / audit roll-ups — compliance percentages and analytics treat active as "in place" and degraded as a partial signal that wants follow-up. Other statuses don't contribute coverage.
Deprecation — preferred over deletion. A deprecated control keeps its history, evidence, and links to the requirements it once satisfied — useful for past-audit traceability — without polluting current views.
Applied control
AppliedControl
The action you take
Reference control
ReferenceControl
Optional library template; an applied control can also be created from scratch
Task
TaskTemplate (definition) / TaskNode (occurrence)
A task definition lists which controls it keeps alive; occurrences are the recurring instances
Evidence
Evidence
M2M; one evidence can prove several controls
Undefined
Default — status not yet set
No
To do
Planned but not started
No
In progress
Being implemented
No
Build
One-shot setup cost — buying hardware, integrating a tool, drafting and approving a policy, running a project to roll out the control
Amortised over the configured amortisation period (in years)
Run
Ongoing operational cost — licence renewals, the time spent every quarter on access reviews, the hours of the team that operates the control
Counted as an annual cost — applied every year for as long as the control runs
Bfixed, Bdays
Build fixed cost and people-days
Rfixed, Rdays
Run fixed cost and people-days
r
Configured daily rate (from General settings)
T
Amortisation period, in years
On hold
Started then paused — blocked, deprioritised, or waiting on a dependency
No
Active
Implemented and operating as intended
Yes
Degraded
Was active, now partially failing — gap detected at audit or in operations
Partial
Deprecated
Retired or superseded — no longer in scope
No

Recurring and one-off operational work tracked against assignees, schedules, and evidence
Tasks are how CISO Assistant tracks the operational work that keeps controls effective: the weekly access review, the monthly backup test, the quarterly policy refresh, the one-off offboarding checklist. They are deliberately separate from applied controls — a control says what is done; a task says that someone has actually done it on a given date.
The task template is the definition — assignee, recurrence rule, expected evidence — and the task occurrence is the actual unit of work scheduled from it. The schedule is a JSON field describing the cadence (DAILY / WEEKLY / MONTHLY / YEARLY with the usual iCalendar refinements), rendered in words wherever it's displayed — Monthly, Every 3 months — rather than as the raw rule. Templates can be wired to many other objects — applied controls being the canonical one (the "maintains" semantics: this work keeps that control healthy) — and when an occurrence is completed, the evidence revision it produces is back-linked through task_node so the audit trail closes the loop.
Tasks come in two layers, mirroring the template → instance pattern used elsewhere in the platform:
A task definition is the reusable spec — the title, description, assignee, expected evidence, and the recurrence rule (every Monday, the 1st of each month, every 90 days). It says what should happen and how often. Internally a TaskTemplate.
A task occurrence is a single scheduled run produced from a definition — the actual row with a due date, a status (pending → in progress → completed / cancelled), and the evidence collected when the work was done. Internally a TaskNode.
A one-off task is just a definition that produces a single occurrence.
With enabled, a one-off definition can also carry a commitment — the delivery date its assignee has actually promised, tracked separately from the task date. Recurring definitions cannot: a recurring definition is a template, not a single promise.
Define. Create a task definition with the assignee, the cadence, and what's expected when the task runs.
Schedule. Occurrences are materialised from the recurrence rule. The platform creates them as their due dates come into view, so the list of upcoming work is always visible.
Work the occurrence. When a due date arrives, the assignee opens the occurrence, records what they did, attaches evidence, and marks it completed.
Iterate. Edit the definition to adjust the cadence, assignee, or expected evidence — existing occurrences keep their state; future occurrences pick up the change.
The point of tracking tasks inside the platform — rather than in a calendar or ticketing system — is so that the evidence of execution lives next to the rest of your compliance record. An auditor asking "show me proof of monthly backup testing" can be answered by pointing at the task and walking through the completed occurrences with their attached evidence.
Task definition / template
TaskTemplate
One spec, one schedule
Task occurrence
TaskNode
One scheduled run
Schedule
schedule JSON field on the template
iCal-style recurrence rule; shown in words in the tasks table and the occurrences panel
An asset is anything of value worth protecting. Assets are first-class objects in CISO Assistant, decoupled from any specific risk study or audit, so the same asset can participate in many analyses without being duplicated.
Assets are always defined by the organisation and can be attached to the global domain or to a specific domain.
The asset is a hub other surfaces point at: risk scenarios impact it, vulnerabilities affect it, incidents affect it, and a Business Impact Analysis assesses it (through an intermediate AssetAssessment row, one per asset in the BIA). The supports self-loop captures the primary/support hierarchy — a support asset is recorded as a child of its primary parent through parent_assets.
Primary assets are core resources directly contributing to the organisation's main objectives — business processes, data, intellectual property.
Supporting assets indirectly aid primary functions — IT systems, services, locations, people.
The distinction matters for risk work: scenarios typically express what can happen to a primary asset via which supporting assets are involved.
Asset
Asset
First-class; primary vs support via type
Domain
Folder
Required; drives IAM scoping
Risk scenario
RiskScenario
Lives inside a RiskAssessment
Vulnerability
Vulnerability
First-class
Incident
Incident
First-class
BIA
BusinessImpactAnalysis
Bridges to assets via AssetAssessment

Security and operational events tracked from detection through resolution
An incident is a security or operational event being investigated or responded to. CISO Assistant treats incidents as first-class objects so that detection, response, evidence, and the controls that should prevent recurrence all live in one place.
Incidents are deliberately distinct from related concepts:
A risk scenario is a potential adverse event — the possibility.
A vulnerability is a weakness that could be exploited — the gap.
An incident is something that has actually happened and is being handled.
An incident lives in a domain and aggregates everything about the event: the assets it affected, the threats it's attributed to, the actors handling it, the applied controls invoked during response, and any task templates set up for follow-up work (post-mortem, control review). Timeline entries are the append-only log of what happened and when — detection, mitigation, observation, severity / status changes.
Identifiers and classification — a name, an optional reference ID, the severity (critical / major / moderate / minor / low / unknown), and the status through its lifecycle.
Timing — when it occurred, when it was reported, when it was resolved.
Detection — internal vs external, optionally with a link to the source signal.
Scope — the affected assets, the threats believed to be in play, the entities (third parties) involved.
Incidents follow a five-state lifecycle:
new → ongoing → resolved → closed (or dismissed at any point if the event turns out not to be a real incident)
State transitions are recorded in the incident timeline, an append-only log of significant moments: detection, mitigation, free-form observation, severity changes, status changes. The timeline is what an auditor or a post-mortem author will read to reconstruct what happened.
For regulated tenants, CISO Assistant ships a DORA incident report — a structured form aligned with the Digital Operational Resilience Act notification requirements (initial, intermediate, and final reports). It draws from the underlying incident but adds the regulatory fields and timing that DORA prescribes.
Assignees — the actors handling the response.
Response and qualifications — qualifying terminology, BCP-activation flag, resolution notes.
Linked controls and tasks — the applied controls invoked during response, plus the task definitions that should run as follow-up (e.g. a post-mortem, a control review).
Incident
Incident
First-class operational event
Threat
Threat
Library catalog object
Actor
Actor
XOR(User / Team / Entity)
Task template
TaskTemplate
Spawns occurrences
Timeline entry
TimelineEntry
Append-only response log
A policy is a specific type of applied control: a document describing what is expected from some part of your stakeholders — an acceptable-use policy, a password policy, a data-classification policy, an incident-response procedure, anything that defines how things should be done.
Because policies are applied controls under the hood, they inherit the full applied-control machinery: they live in a domain, have an assignee and a status, link to the requirements they satisfy, and carry evidence.
Policies are central to most compliance frameworks — almost every requirement expects a documented policy as part of the substantiating artifacts. Pulling policies onto a dedicated page lets you:
Maintain the catalogue of published documents independently of the broader action plan.
Track policy review and approval cycles separately from operational controls.
Surface policies by domain when responding to audit requests or external reviews.
The Policies page in the platform is a filtered view of applied controls where the type is policy; everything you can do to an applied control, you can do to a policy.
Policies can come from either side of the divide:
Author in CISO Assistant. Each policy can carry one or more managed documents — versioned documents tracked in-platform through a draft → in-review → validated → published → deprecated lifecycle. Useful when you want the policy text to live where the rest of the GRC programme lives, with revision history and approval workflow attached.
Bring an existing document. If a policy already exists as a file, or lives in Confluence, SharePoint, or a DMS, its managed document can be uploaded (the file itself) or linked (a pointer to where it lives) — the same lifecycle, versioning, and links apply, without duplicating the source of truth.
Both paths are first-class — you can mix them across an organisation, or even across policies in the same domain. The applied-control machinery (assignee, status, linked requirements, evidence) is the same either way.
For policies authored in CISO Assistant, every change produces a new revision of the managed document rather than overwriting the previous text. Each revision carries:
A monotonically increasing version number (v1, v2, …).
A revision status (draft / in review / change requested / validated / published / deprecated).
A timestamp, the actor who edited it, and the content at that point in time.
The full revision list is reachable from the version history sidebar on the document page — you can switch between revisions to read any past version exactly as it was published.
Two diff views help reviewers and approvers see what actually changed:
Diff between two revisions — pick a "from" revision and a "to" revision in the history sidebar and the platform renders the textual differences between them. Useful for periodic reviews ("what changed between v3 and v6?") and for the approval workflow ("show me the delta the requester is asking me to validate").
Edit diff (within a revision) — while a draft is being worked on, the platform also tracks the diff between the last loaded state and the current edits in the editor. This lets the author see exactly what they're about to commit before they save the increment.
The diff is computed on the document content itself — it's not a binary file diff, so it works well for the plain-text or Markdown policies typically authored in-platform. For policies attached as external files (PDF, DOCX, etc.), version history is preserved through evidence revisions, but the inline diff view is not available.
Policies have their own lifecycle — drafting, review, approval, publication, periodic review, retirement. The platform tracks these states through the standard applied-control status field and the supporting evidence on each policy entry. When authoring in-platform, the managed-document revision states (draft, in review, validated, published, deprecated) provide a finer-grained workflow on top.
Standalone forms filled outside an audit, and the requests they raise — intake, pre-qualification and light questionnaires with a decision at the end
A quick form is a short, standalone questionnaire that someone fills in without an audit behind it: a derogation request, a vendor review request, a pre-qualification, an intake form. A request is one filled copy of that form, submitted to someone who decides on it.
It answers "how does someone outside the security team ask us for something, and how do we decide and keep the record?" — the question that otherwise gets answered by email, a spreadsheet, or a ticketing tool that knows nothing about your controls.
The distinction from an audit matters. An audit measures an organisation against a framework, and every question hangs off a requirement. A quick form has no requirements, no assessable nodes and no compliance result — it has pages, questions, and an outcome.
A quick form arrives from a library, like a framework or a risk matrix does. On its own it cannot be filled — it must first be published, which is what names the audience allowed to file it and the domain their requests land in. A publication is then surfaced to people through a portal tile, which is where they click to start. Someone in that audience files a request, answers it, and submits. The answers compute a score and fire outcomes, which classify the request. A reviewer decides. An accepted request can then produce a real governed object, with the link back to the request that caused it.
A form about an object, such as a vendor tiering form, takes a shorter route: it is started from that object, with no publication or portal, and when the form has an On accept setting, an accepted request writes its result back onto it.
Three places in the sidebar, and they are deliberately different audiences:
My requests is the only route back to an unfinished draft, and it is scoped by who you are rather than by domain permissions — a requester holds no role on the domain their request lands in.
A quick form sitting in the catalog cannot be filled by anyone. Publishing it is a separate, deliberate step, because the form says what is asked and the publication says who may ask and where it lands:
The same form can be published more than once — to different audiences, landing in different domains, with different reviewers. That is how one derogation form serves several business units without being copied.
Publishing is necessary but not sufficient: a publication becomes reachable when a portal tile points at it. My requests lists the portals you can ask from, not the publications themselves.
Audience membership is the authorisation here. It is not folder permissions: someone in the audience can file a request into a domain they otherwise cannot see, and they will still not see anything else in it.
A closed request carries a Resolution: Accepted, Rejected, Dropped (the requester withdrew it), or Closed automatically. Reject is the reviewer's act and drop is the requester's; neither substitutes for the other.
A reviewer can also send a request back to Draft, which returns it to the requester to change and resubmit.
Whoever submitted a request may not decide it, whatever permissions they hold. An analyst who files a derogation cannot approve their own derogation.
Two things follow:
Deciding needs a specific permission, not merely write access to the domain. A reviewer who can edit everything in a domain still cannot decide a request they filed.
Organisations too small to separate the two can turn on self-validation in the settings, which lifts the restriction deliberately rather than by accident.
The rule is anchored on who submitted the request, not who created the record — cloning and reassignment move authorship, and the check follows it.
Scores and outcomes are what separate a quick form from a web form that emails someone.
Each question can carry a score, and each form can carry outcome rules — expressions over the answers that classify the request when it is evaluated. All matching rules fire; a request can carry several outcomes at once.
Two ways to write a rule, both useful:
On a specific answer — "personal data was ticked", "no compensating control is in place". Precise, and reads like the question it depends on.
On a score — "anything above 100 needs a full assessment". One threshold to tune, instead of an enumeration that grows each time a choice is added. The score can be the form's, or a page's when each page measures something different (business impact on one, data exposure on another).
A rule can also compute a number instead of firing: an average, the higher of two page scores, a formula such as the EBIOS RM threat level. The value shows on the request, and later rules read it. That is how a form turns answers into one figure a decision can rest on.
Outcomes are what the rest of the platform reacts to. They drive which supervised actions a reviewer is offered, and they are the hook for turning an accepted request into a governed object or a change to an existing one.
Some forms rate the same thing several times: a DPIA rates the severity and the likelihood of each of its three risks. A scale names those ordered levels once, for example Negligible, Limited, Significant, Maximum, each with an optional description and number. A choice question on a scale gets the scale's levels as its choices, so every question measuring severity uses the same ones.
A rating is the third kind of outcome rule, next to Yes / no and Number. It classifies the answers to its scales, its dimensions, into named levels of its own:
With two dimensions, a grid crosses them: one cell per pair of levels, like a risk matrix.
With any number of dimensions, rules do: the first rule that holds gives the level, for example dims.severity.value * dims.likelihood.value >= 8.0.
Each page that answers every dimension is a point, rated on its own: a DPIA rates each of its risk pages. A question can also belong to a second stage, such as residual for the level once planned measures are in place, which gives a second point on the same page. Other rules read the highest level among the current points as values.RATING_ID, and the residual one as values.RATING_ID_residual.
In the library builder, scales are edited under Scales, and a question uses one by picking it in Choices. A rating is created by choosing Rating as an outcome rule's Result. It starts from the form's first two scales, which the builder adds when the form has fewer, and a grid of three levels, Low, Medium and High, to repaint. A dimension no question measures yet is flagged.
Some forms are about an object: a vendor tiering form is about a vendor, a DPIA about a processing. The form names that object in a subject question, and the request is then linked to it.
Such a form can say what an accepted request writes onto its subject, in its On accept setting:
A page of such a form can also show what the subject already records, read-only: a DPIA's data page shows the processing's personal data, data subjects and recipients. While the request is a draft, these reflect the object as it is; once submitted, as it stood then. The form then asks about what the record does not hold, instead of copying it.
Three things follow:
It starts from the object. Assess tier on a vendor, or Run DPIA screening and Start DPIA on a processing, opens a request with the object filled in and locked. No publication or portal is needed.
The reviewer sees the write before deciding. The request shows On accept, this will write and the value. The reviewer can override it, with a justification. If the vendor's tier came from another form, the request warns that accepting replaces it.
No review when there is nothing to grant. When the submitter could make the change themselves and every scored question is answered, submitting applies the result at once and closes the request as accepted. A publication can require a review anyway, with Always require review.
When the result cannot be written — no subject, a tier missing from the scale, a permission missing, an object the form no longer applies to — nothing is written and the request goes to review with the reason.
and walk through the whole loop.
A ticketing tool stops at "approved". The point of filing a derogation request here is that an accepted one can become a security exception — a governed object with an expiry, an approver and a place in the register.
That conversion is done by a supervised action: a workflow the reviewer runs deliberately on a request they have read, rather than something that fires on its own. The outcomes decide which actions are offered; the reviewer chooses whether to run one.
Whatever is produced keeps a link back to the request that caused it, visible from both ends — the request shows what it produced, and the produced object shows where it came from.
Questions can ask for a file. Uploads are held on the answer, and a reviewer who wants to keep one can promote it to an evidence in the domain — so a document that mattered to a decision stops being an attachment on a request and becomes part of the record.
Quick forms are library content. There are two ways to get one:
Import a library that contains one, from Libraries — the same route as a framework or a risk matrix.
Build your own in the library builder: New quick form creates the draft and opens the editor. Pages, questions, choices, scores, conditions and outcome rules are all edited there, and the result is a library you can export and re-import elsewhere.
Because a form is library content, it is versioned like one. Re-importing a newer version updates the form in place — except for questions already answered by a request that has left draft. Those are kept deliberately: deleting them would rewrite the record a decision was made on.
Automation that reacts to what happens in the platform, on a schedule, on an event or on a webhook, and notifies, reads, creates or updates objects on your behalf
Workflows automate what happens in CISO Assistant. A workflow reacts to something (a schedule, an object changing, an external system calling in, or you pressing a button), then runs a series of steps: read objects, create or update them, branch on a value, loop over a list, send an email, call an external system.
You build workflows visually, on a canvas. No code is involved, and everything a workflow does is bounded by the permissions of the person who published it.
QuickFormResponse
One filed copy, with its own reference and lifecycle
Outcome
QuickFormOutcome
One row per rule that fired on the answers
Requester
respondents / submitted_by
Whoever is on the asking side
Reviewer
reviewers / assignee
Whoever may decide
Quick form
QuickForm
Library content; lives in the catalog, not in a domain
Page
QuickFormPage
Ordered; can be hidden by a condition on earlier answers
Published form
QuickFormPublication
The offer: which audience, which domain, which reviewers
Quick forms (Catalog)
Anyone building the catalog
The form templates themselves, imported from libraries
My requests
Everyone
The requests you filed, including unfinished drafts
Requests (Governance)
Reviewers
The queue of requests waiting on a decision
Audience
Groups that may file this request. Empty means every user
Submission domain
Where responses land. The publication's own domain when left empty
Default reviewers
Who is put on each request as reviewer
Allow multiple drafts
Off: a requester with an unfinished draft is handed it back instead of starting a new one. Submitted requests are never limited
Draft
Being filled. Answers save as you go; only the requester can change them
Submitted
Waiting for a reviewer. Content is frozen
In review
A reviewer has taken it. Claiming is optional — a quick decision does not require it first
Closed
Decided, with a resolution
Vendor
Its criticality tier: bands on a score or a number rule, and knock-out rules that force at least a given tier. When several apply, the highest tier wins
Processing
Whether it needs a DPIA, from the outcomes that fired (screening)
Processing
The request itself, as the processing's DPIA of record (DPIA)
Processing
The applied controls the form picked, added to the processing's own
Request
Reactions
When a finding is created at high severity, open a remediation control and link it
Sweeps
Every night, mark lapsed security exceptions as expired
Intake
A scanner posts vulnerabilities to a URL and each one is recorded
Collection
Every night, pull an export from a tool, file it as evidence and record what it measured
Triage
Ask a model to classify an incoming finding, then branch on its answer
Outbound
When a control goes live, post to your ITSM or chat tool
Provisioning
Create a domain, its groups and its first user in one run
Thirty-four ready-made workflows ship as templates. Most people start by installing one and adapting it.
A workflow is a set of steps connected by wires. A run starts at a trigger step and follows the wires. Where a step has several outgoing wires, all of them run, in parallel. Where you want only one path, you use a Condition step, which picks exactly one of its branches.
A path ends when it reaches a step with no outgoing wire, or a Stop run step. The difference matters: an unconnected step ends that path only, while Stop run ends the whole run, including paths still executing elsewhere.
A workflow has versions. At any time it has at most one draft and at most one published version. Older published versions are archived.
Draft
Yes
No
Yes, as you
Published
No. Editing it creates a new draft
Yes, when its triggers are enabled
Yes, as its publisher
Publishing is the moment a graph becomes real. It runs a set of checks, freezes the graph, registers its triggers and stamps the publisher as the version's run identity. The previously published version is archived. Runs that were already in flight keep executing their own frozen version.
You can always edit a published workflow. The first change you make silently creates a new draft. The published version keeps running until you publish the draft, and you can discard the draft at any time to go back.
A run is one execution of one version. It has a status:
Active
Still executing, or waiting for an email to be delivered
Completed
Every path finished, or a Stop run step was reached with no failure elsewhere
Failed
A step failed, or the run hit its time limit
Each run keeps its variables, the payload that started it, the output of every step, and a log of everything that happened. You browse all of it from the Runs panel, replay it on the canvas, and pin a run as reference data so the builder can show you real values while you edit.
Steps exchange data through expressions: {{today}}, {{payload.object_repr}}, {{nodes.fetch_late.count}}, {{item.name}}. A step's settings are text with expressions inside. When the step runs, the expressions are replaced with values from the run.
Variables are named values declared on the workflow with a type and a default. A trigger can fill them from incoming data, a Set variables step can assign them, and conditions compare against them.
Secrets are named credentials for HTTP calls. They are write-only and scoped to one workflow.
A workflow lives in a domain. Its runs can only see and change objects in that domain and its sub-domains. Every published version runs as the person who published it, with their permissions checked live at every step. Publishing grants nothing: you can automate exactly what you could do by hand, in the domain where the workflow lives.
Read Permissions and security for the full model.
Step
A node on the canvas: trigger, Condition, Action, Loop or Stop run
Wire
A connection between two steps
Trigger
The step a run starts at
Branch
One output of a Condition step, with its own conditions
Ref
Building your first workflow: enable the feature and build one end to end.
Workflow builder: every part of the canvas, then triggers, steps, expressions, variables, runs, sharing and troubleshooting.
Cheat sheet: every rule, limit and subtlety on one page.
Template catalog: the ready-made workflows and which one to start from.
: who a run acts as and what it can reach.
Digests
Every Monday, email the controls past their ETA

[Monday 08:30] ──▶ [Read overdue controls] ──▶ [Loop] ──each──▶ [Log one line] ──┐
▲ │
└────────────────────────────────┘
└──done──▶ [Email the digest] ──▶ [Stop run]Archived
No. Can be restored as a new draft
No
No
A step's identifier in expressions, derived from its label
Run
One execution of a version
Run identity
The user whose permissions a published version's runs use
Reference run
The run pinned in the builder to show real data while editing
Template
A ready-made workflow shipped as a library

Document management lets you author, version, and publish documents — policies, procedures, charters, records, meeting minutes — directly inside CISO Assistant, so the text of your governance programme lives where the rest of your GRC data lives, with revision history and an approval workflow attached.
Documents can be authored in-platform (a Markdown editor with a draft → published lifecycle), uploaded (an existing PDF or file carried through the same lifecycle), or linked (a pointer to a document that lives in another system). Whatever the source, a document can be classified, linked to the objects it governs, and referenced by other documents.
A document container is the language-independent identity of a document: it holds the document's type, its domain, its classification, and its links. Under it sit one managed document per language, and each managed document is a chain of document revisions — one per saved increment. A new document can be seeded from a template, classified with a level, reference other containers, and be linked to the objects it governs. Solid edges are always present; dashed edges are optional.
Every document has a Document type, used to group the reading catalogue and to scope the template picker. The built-in types are Policy, Procedure, Charter, Record, Meeting minutes, and Other.
A document's content comes from one of three sources, chosen when you create it:
Authored — written in the in-platform Markdown editor (formatting toolbar, live preview, insert links to other documents). The published version is rendered to a PDF snapshot.
Uploaded — an existing file (e.g. a signed PDF) attached to the document. It runs through the same lifecycle and version history, but is served as-is rather than rendered from Markdown.
Linked — a pointer to a document that lives in another system (Confluence, SharePoint, a DMS, …). The document carries the URL through the same lifecycle; readers open it in place rather than reading content inside CISO Assistant.
All three share the same versioning, lifecycle, languages, classification, and links — they differ only in where the content lives.
Each language version moves through its own lifecycle — Draft → In review → Change requested → Validated → Published → Deprecated — and every saved change produces a new revision (v1, v2, …) rather than overwriting the previous text. The Version history sidebar lets you read any past revision and diff two of them.
The published revision stays in force until a newer one is published, so the document list keeps reporting it: a document whose v1 is published and whose v2 is still in review shows Published at 100%. A secondary badge next to the status (v2 · In review) flags the revision still in the approval loop, and the document header shows both — the revision in force and the one being worked on. The badge disappears once the two are the same again.
The lifecycle, revision history, and diff mechanics are shared with policy documents and are described in detail under .
A single document can carry a version per language. One language is the default (used for the catalogue title and status); the others are translations. Each language version has its own lifecycle, so a French translation can still be in draft while the English original is published.
When you link one document to another from inside the editor (Link to document), CISO Assistant records the edge automatically. Each document then shows:
References — the documents this one points to.
Referenced by — the documents that point at this one.
These are computed from the content — there is no separate list to maintain, and they stay accurate as the text changes.
A document can be linked to one or more Policies, Applied controls, Task definitions, or Processings. The link is associative — it never changes the document's domain or publication state — and it is bi-directional: the linked object shows a Documents panel listing the documents attached to it.
This is how a policy or a control points at the document that describes it without duplicating the text. See .
A document can carry a Classification — a level from an scheme such as TLP. Once set, the level shows as a coloured badge on the reading catalogue, in the reader, and in the documents table, and it is stamped on every page of the document's exported PDF (for example TLP:AMBER). Classification is optional and independent of the document's type, domain, and lifecycle.
New authored documents can start from a document template — a reusable Markdown body, chosen in the editor's template picker. The picker only offers templates matching the document's type and language. See the .
The Documents page is a read-oriented catalogue: published documents shown as tiles, grouped by type, with search and type filters. It is the place to browse and read the published corpus, separate from the authoring workflow.
Formal sign-off workflow that routes assessments, policies, evidence, and other artefacts through one or more approvers
A validation flow (labelled Validations in the sidebar) is the platform's structured sign-off workflow. It takes one or more objects — an audit, a risk assessment, a policy, a security exception, a contract, an accreditation — bundles them into a single request, and routes that request to a designated approver for a formal decision. The decision and its context are preserved as an audit trail.
It's the answer to "who approved this, when, and on what evidence?" — the question that comes up in every certification audit, every internal review, and every regulator visit.
A requester submits a validation flow for one or more objects in their domain. The flow carries optional request notes, a validation deadline, and is routed to a designated approver. The approver moves the flow through a status machine ending in one of: Accepted, Rejected, Change requested, Revoked, Expired, or Dropped. The decision is recorded on the validation flow itself, with the link back to whatever was up for approval. Two things also change on the linked objects: accepting a flow locks its audits, risk assessments, business impact analyses and findings binders, and security exceptions follow the decision (see below).
There are two ways to start a flow, and they cover slightly different objects:
From the Validations list — click +, then expand the More section to attach objects. The picker offers Audits, Risk assessments, Business impact analyses, Findings binders, Security exceptions, Processings (privacy), Accreditations, and Contracts (third-party). You can mix several types in one flow when they belong to the same approval decision (e.g. "approve this audit and the related exceptions").
From an object's own page — objects that carry a validation section let you submit the open object directly. This is the only way to route a Policy through a flow, and it's also available on audits, risk assessments, BIAs, findings binders, exceptions, processings, accreditations, and contracts.
So the full set of objects that can go through a validation flow is: audits, risk assessments, business impact analyses, findings binders, policies, security exceptions, processings, accreditations, and contracts.
The flow moves through a small state machine:
The decision also carries onto linked security exceptions: Accepted moves those in Draft or In review to Approved, Rejected moves them to Rejected, and Revoked sends Approved ones back to In review. This only applies to exceptions the requester is allowed to edit, and an exception stays Approved while another accepted flow still covers it. Exceptions in any other status are left alone, and their owners are notified of the change.
The status is the audit-trail field that gets cited in evidence reports — "policy v3 was Accepted by Jane Doe on 2026-03-12" is what makes the decision provable later.
Most ownership/assignment fields across the platform hold an actor (which can resolve to a user, a team, or an entity). The validation-flow approver is the deliberate exception: it's a foreign key to a User directly. Approval is a personal accountability act — "a named individual signed off" — and a team can't approve in the same way it can co-own work.
When the page surfaces validation flows, it wraps the user-only approver field so the team-broadening toggle still works: switching to "include team assignments" adds every team member's user ID into the approver filter, giving you a per-team queue without changing the underlying "one named user approves" semantics.
Each validation flow carries an optional, unique reference ID — useful when you need to cite the approval in external systems (a change-management ticket, a regulator submission, an internal governance record). The platform enforces uniqueness across the instance.
The pattern fits anywhere a decision needs formal, traceable sign-off:
A policy moving from validated to published — the approver records that the document is fit for release.
A security exception being granted — naming the risk-owner who accepted the residual risk.
An audit's results being signed off before publication to stakeholders.
A risk assessment's residual-risk acceptance — the formal capture required by ISO 27005 and most internal risk frameworks.
It is not the right tool for casual review or peer feedback — for those, use comments, the requirement-assessment review status, or a . Validation flows are explicitly heavy: they exist so that the approval is preserved as evidence.
— typical objects routed through validation flows for publication
— lighter-weight review/remediation tracking
— formal risk acceptance is one of the most common reasons a validation flow is submitted
— where approvers see flows queued for them
Document
DocumentContainer
Language version
ManagedDocument
Revision
DocumentRevision
Document template
DocumentTemplate
A third-party contract going through procurement sign-off.
— explains why approver is a user, not an actor
Validation flow
ValidationFlow
Lives in a domain; carries the request, the bundled objects, the deadline, and the decision
Validations (sidebar)
validationFlows i18n key
The list view label
Requester
requester FK to User
The platform account that submitted the flow
Approver
Submitted
Sent to the approver; awaiting decision
Change requested
Approver wants edits before deciding; the requester reworks and resubmits
Accepted
Approved — decision and timestamp recorded
Rejected
Declined — decision and rationale recorded
Revoked
Approval was rescinded after the fact (e.g. underlying conditions changed)
approver FK to User
Foreign key to a User (not an actor) — approval is always personal accountability, even if assignment elsewhere can fan out to a team
Request notes
request_notes
Free-text explanation of what's being asked for
Validation deadline
validation_deadline
Optional date used for expiry and dashboards
Expired
Validation deadline passed without a decision
Dropped
Requester withdrew the flow before a decision was reached
A findings binder collects the issues raised by one review and drives their remediation through to closure. The issues themselves are findings, and they can come from a CISO Assistant audit, a penetration test, a technical posture scan, an external assessor's report, a responsible disclosure, or someone simply noticing something.
It's the place where the action plan meets reality: each non-compliance, observation, or recommendation gets an owner, a due date, and a status, and is followed all the way to closed.
A findings binder is an assessment, in the same family as audits, risk assessments, and business impact analyses: it lives in a domain, optionally narrows to a perimeter, carries authors, reviewers, a status and a version, and can be locked. Inside it sit the individual findings, each remediated by one or more applied controls and backed by evidences. A binder can point back at the audit whose findings it captures, which is what the findings-from-requirements flow uses.
Beyond the assessment basics (name, version, status, authors, reviewers, domain, perimeter):
Category — Pentest, Threat hunting, Red teaming, Audit, Self-identified, Posture follow-up, or Responsible disclosure. This is what lets dashboards separate "what the pentester found" from "what the auditor found".
Audit — the audit whose findings this binder captures, when there is one.
Reference ID and Reference link — the assessor's own identifier and a URL to the report, ticket, or external tracker.
Reported at and Start date
Locking a binder freezes it: its findings can no longer be modified, which is how a signed-off pentest report stays the record of what was found.
A finding carries a severity (from informational to critical), a status (undefined → identified → confirmed → assigned → in progress → mitigated → resolved → closed, plus dismissed and deprecated), an optional priority (P1–P4), an owner, and an ETA / due date.
Two text fields separate the observation from the answer to it:
Observation — what was found.
Recommendation — what should be done to close it.
A finding can also point at the things it concerns: an asset, a requirement and the requirement assessment it was raised from, threats, vulnerabilities, reference controls, and the applied controls that remediate it. The applied-control link is many-to-many: one control can close several findings, and one finding can need several controls.
A finding does not have to belong to a binder. Leave the binder empty and it stands on its own — appropriate for something spotted in passing that doesn't warrant a whole engagement record. Standalone and bound findings appear together in the Findings list under Governance, so nothing is lost by not opening a binder.
A bound finding takes its domain from its binder; detach it to move it elsewhere.
The same model serves every source, which is what makes cross-source reporting possible — all open critical findings due this quarter is one question, not one per review type:
Audits — non-compliances raised while assessing requirements, especially with enabled.
Technical postures — failing checks routed into a binder with the Posture follow-up category. See .
External assessments — pentest reports, third-party security reviews, regulator inspections. These are typically bulk-loaded through the , which has a dedicated findings-binder importer.
Internal reviews and self-identified issues
With the findings_from_requirements on, a requirement assessment gains a Findings tab. Raise a finding records the issue without leaving the requirement, and the picker binds an existing finding from any binder to the requirement. A finding is bound to one requirement assessment at a time: the picker skips findings bound elsewhere, and moving one is done from the finding. A locked binder keeps its findings where they are, and a locked audit refuses new ones.
The first finding raised on an audit creates that audit's binder automatically — named after the audit, in the audit's domain and perimeter, with the Audit category — and every later finding on the same audit joins it. Creating it needs permission to add a findings binder in the audit's domain. If the audit already has binders, the oldest one is used.
Objectives — what this campaign of findings sets out to achieve.
Budget and Expenses — what the engagement was allotted, and what it has consumed so far.
Evidences — the report itself, the scope letter, the rules of engagement.
Responsible disclosure — issues reported from outside the organisation.
Findings binder
FindingsAssessment
Subclass of the shared Assessment base
Finding
Finding
Can stand alone — a binder is optional
Domain
Folder
Required; drives IAM scoping
Perimeter
Perimeter
Optional
Audit
ComplianceAssessment
Optional back-link, set by the findings tab
Documented, time-bound deviations from a requirement, control, or risk treatment, with owners, an expiration date, and a review trail
A security exception records a deliberate, documented deviation from what you would normally do: a requirement you can't meet yet, a control you've decided not to apply on a legacy system, a vulnerability you won't patch before the next maintenance window. Instead of leaving the gap silent, you write it down, say who owns it, how severe it is, and when it stops being acceptable.
The key word is time-bound. An exception carries an expiration date, and the platform reminds its owners before that date and keeps flagging it afterwards until someone closes it out. It's how a temporary waiver stays temporary.
Exceptions are listed under Governance › Exceptions in the sidebar.
A security exception always lives in a domain, which decides who can see and edit it. From there it points at whatever it deviates from: the requirement assessments of an audit it waives, the applied controls it suspends or weakens, and the assets, vulnerabilities, or risk scenarios it concerns. All of these links are optional and many-to-many: one exception can cover several objects, and one object can carry several exceptions. Evidences hold the supporting material (the signed waiver, the compensating-measure description, the vendor's end-of-life notice). Formal sign-off goes through a validation flow, which records who approved the exception and when.
Identification — a Name, an optional ID, a Description, and the Domain it belongs to.
Severity — the same scale as vulnerabilities and findings: low, medium, high, critical, plus an informational level, or left undefined.
Status — where the exception stands in its life (see below). New exceptions start as Draft.
The Approver (Deprecated) field is still shown on exceptions that had one, but it can no longer be set. Its help text says it plainly: "This field is deprecated. Use validation flows to request approval of a security exception."
Status is set by hand. Passing the expiration date doesn't switch an exception to Expired on its own. Instead the owners get an expiry alert (see ) so a person can decide what happens next: renew it with a new date, resolve it, or mark it expired.
Rejected, Resolved, Expired, and Deprecated are closing statuses: an exception in any of them gets no more expiry reminders.
You can link an exception from either side.
From the exception's own form, you pick Assets, Applied controls, and Evidences.
From the other object's form, an Exceptions field is available on:
Assets
Applied controls and policies, under Relationships
Vulnerabilities
Risk scenarios, on the scenario's edit page
On an audit, open a requirement to find an Exceptions tab alongside the other tabs (applied controls, evidences, findings, and so on). From there you can Add exception to create one already linked to that requirement, or attach existing ones. The tab is part of the auditor's view; respondents answering through an don't see it.
The risk scenario detail page lists its exceptions in its own Exceptions section.
The exception's detail page shows the reverse view, with one tab per linked type: Evidences, Applied controls, Assets, Vulnerabilities, Requirement assessments, and Risk scenarios. Only the Evidences tab lets you create or attach from there. The others are read-only summaries, because those links are managed from the exception's form or from the other object.
Exceptions take evidences like most operational objects. Attach existing ones from the form or from the Evidences tab, or create a new evidence from that tab, which links it to the exception automatically. The evidence's own page then lists the exceptions it supports.
When the Validations feature is enabled, an exception's page shows a Request validation button. It opens a validation flow with the exception already attached; you pick the approver and add your notes. The flow's history (requests, decisions, and notes) then shows up on the exception page.
You can also bundle exceptions with other objects in one request from the Validations list, for example an audit together with the exceptions raised against it. See for how approvers, deadlines, and decisions work.
The decision updates the exception's Status: accepting the flow moves a Draft or In review exception to Approved, rejecting it moves it to Rejected, and revoking an accepted flow sends an Approved exception back to In review. This only happens when the requester is allowed to edit the exception, and the exception's owners are notified of the change.
Notifications go to everyone in Assigned to. For a team, that means its team email, leader, deputies, and members.
The two deadline notifications are skipped for exceptions in a closing status (Rejected, Resolved, Expired, Deprecated), and they need an Expiration date to fire. Administrators can switch each of these notifications on or off per channel (in-app or email) for the whole instance. See .
Exceptions you co-own also appear under Exceptions on your page, and on the Calendar on their expiration date.
If your organisation tracks attributes of its own on exceptions, like a waiver reference from your risk committee or a business sponsor, you can define for them. They appear at the bottom of the exception form and can be used to filter and search the list. Custom fields are a PRO capability.
The list shows ID, Name, Severity, Status, Expiration date, Domain, Associated objects (how many requirement assessments, controls, assets, vulnerabilities, risk scenarios, and evidences the exception is linked to), and Created at.
Filters — Domain, Severity, Status, Expiration date, Created at, Updated at.
Batch actions — select rows to Change status, Change domain, or Delete in bulk.
Export — download the list as CSV or Excel.
For a portfolio view, the Governance tab of Analytics has an Exceptions breakdown chart that shows how exceptions spread across severities and statuses. Each audit's has an Exceptions Overview for the exceptions raised against its requirements.
Exceptions follow the usual : you see the exceptions of the domains you have access to. With the built-in roles, analysts, domain managers, and administrators can create, edit, and delete them, while readers and approvers can only view them.
The whole module can be hidden with the exceptions . It's on by default.
Both formalise a decision to live with something, but they answer different questions. A risk acceptance works at the level of a risk scenario: it says the residual risk is tolerated. An exception works at the level of a rule: it says this requirement or control won't be applied as written, for this scope, until this date. Exceptions can still be linked to risk scenarios when a deviation is part of a risk's story.
Expiration date — when the exception stops applying. The form warns you if you enter a date in the past, but doesn't block it, so you can record exceptions that have already lapsed.
Observation — free-form notes in markdown: the justification, the compensating measures, the conditions under which the exception holds.
Link — a URL to an external tracker (an ITSM ticket, a Jira issue, a SharePoint page).
Assets, Applied controls, and Evidences — the objects the exception concerns and the material that supports it, all selectable from the form. When editing an existing exception, a + next to Applied controls lets you create a new control already linked to it (for example, the compensating measure).
Validations, to bundle exceptions into an approval request
Import — exceptions can be loaded in bulk through .
Exception
SecurityException
Domain-scoped; listed as Exceptions
Domain
Folder
Required; drives IAM scoping
Assigned to
owners → Actor
A user, a team, or an entity
Draft
Being written up; not yet submitted. The default.
In review
Submitted and awaiting a decision.
Approved
Granted. The deviation is accepted until the expiration date.
Rejected
Refused. The underlying gap has to be treated normally.
Resolved
No longer needed: the requirement is now met or the control is in place.
They are added to an exception
An assignment notification
The exception's status changes
A status-change notification naming the new status
The expiration date is 30, 7, and 1 day(s) away
An "expiring soon" reminder
The expiration date has passed
An "expired" alert, until the exception reaches a closing status
Validation
ValidationFlow
The approval workflow
Approver (Deprecated)
approver → User
Read-only; kept for existing records
Evidence
Evidence
Many-to-many
Expired
The expiration date has passed and the exception no longer applies.
Deprecated
Superseded or no longer relevant, kept for the record.
A risk assessment (also called a risk study) is a scenario-based evaluation of risk over a perimeter. CISO Assistant supports qualitative approaches (configurable risk matrices), quantitative approaches (Monte Carlo over loss distributions), and the structured EBIOS RM methodology.
The platform follows the ISO 27005 risk-management workflow.
A risk assessment always lives inside a domain (the mandatory IAM scope) and is bound to one risk matrix that supplies the probability × impact scale (the matrix can be swapped later; existing scores are clamped to the new scale). A perimeter can optionally narrow the assessment to a specific service or process inside the domain. The assessment is composed of risk scenarios; each scenario links to the assets it impacts, the threats it materialises, and the applied controls that mitigate it (split between existing and planned to drive the three-tier risk model below).
A risk assessment encompasses three steps:
Risk identification — defining the risk scenarios.
Risk analysis — assessing probability, impact, and strength of knowledge for each scenario.
Risk evaluation — done automatically based on the selected risk matrix.
In CISO Assistant, risk treatment is combined with the risk assessment rather than tracked as a separate phase.
Scenarios can be defined directly from the risk-assessment view or separately via the scenarios view. The same scenario can be reused across multiple studies.
On the assessment page, the Selection bar above the scenario table narrows the assessment to the scenarios you choose, for example those that are not within tolerance. The matrices, the action plan and the exports then follow the same selection. See .
CISO Assistant tracks three risk levels for each scenario, reflecting where the organisation stands along the treatment journey:
Inherent risk — the natural level of the scenario without any controls in place. The starting point. Surfaced in the UI when the inherent_risk feature flag is on.
Current risk — the level given the applied controls already in place. The state of risk today.
Residual risk — the level expected once all planned applied controls have been implemented. The target state, and the figure used in risk-acceptance decisions.
Each level has its own probability, impact, and overall level fields. The assessment's consistency check flags a scenario whose residual risk exceeds its current risk (on level, probability, or impact), and also flags a residual lowered below current when no applied control justifies the reduction.
To watch scenarios move through these three levels on the matrix, and project when each will reach its residual level from the ETAs of its extra controls, open from the assessment.
Risk acceptance is when an organisation or individual decides to tolerate a certain level of risk without taking further action to reduce it. CISO Assistant provides a workflow to capture formal approval of risk acceptances by management — the approver must hold the Approver role.
For the formal definition, see .
Risk levels are calculated as a function of the probability and impact of a scenario, using a configurable risk matrix. Matrices are imported from libraries — pick one of the built-in matrices or define your own via a custom library.
Most organisations define an official matrix to be used for all risk assessments, but CISO Assistant lets you choose a different matrix per assessment when needed. The matrix can be changed after the assessment has been created. When you do, each scenario's existing probability and impact values are clamped to the new scale's bounds — a score that falls outside the new range is clipped to the nearest valid value, and unrated scenarios stay unrated. There is no proportional rescaling, so review the scenarios afterwards and correct any score that no longer reflects your intent.
Risk assessment
RiskAssessment
Also called "Risk study" in the UI
Risk scenario
RiskScenario
A row inside the assessment
Risk matrix
RiskMatrix
Can be changed; existing scenario scores are clamped to the new scale's bounds
Domain
Folder
Required; drives IAM scoping
Threat
Threat
Catalog entry from a library
A quantitative risk study evaluates risk in monetary terms — the expected annualised loss from each scenario — using probabilistic methods rather than a qualitative matrix.
It's the sibling of qualitative risk assessment and EBIOS RM: same problem (what could go wrong, how bad would it be), different lens (statistics rather than categories).
A study is the container for one quantitative analysis. It comprises scenarios — each one a discrete risk being modelled — and each scenario comprises one or more hypotheses, typically one per risk stage (inherent / current / residual). The hypothesis carries the probability and impact distributions plus the applied controls it assumes are in place — split into existing / added / removed sets so the delta between stages is explicit. Scenarios reference the assets they impact and the vulnerabilities they exploit, mirroring the qualitative side of the platform.
Each scenario in the study is parametrised by one or more hypotheses:
A loss-event frequency distribution — how often the bad thing happens per year, expressed as a distribution rather than a point estimate.
A loss magnitude distribution — how much it costs when it happens, also as a distribution.
The platform runs Monte-Carlo simulation over those distributions and derives the loss exceedance curve (LEC) plus aggregate metrics: expected loss, value-at-risk, tail loss.
You need to compare risk against budget — "should we spend €X on control Y?" becomes tractable when both sides are in euros.
You need to talk to the board or finance about risk in the language they speak.
You have enough data — or enough informed judgement — to bound the loss distributions.
Qualitative methods stay useful for everything else.
— click walkthrough for running a study.
— the math behind the LEC, VaR, expected shortfall, ROSI, and tolerance overlay.
Study
QuantitativeRiskStudy
Container; carries risk tolerance + loss threshold
Scenario
QuantitativeRiskScenario
One row of risk
Hypothesis
QuantitativeRiskHypothesis
Parameter set + Monte-Carlo simulation cache
Catalogued weaknesses tracked through detection, triage, and remediation
A vulnerability is a weakness in a system, process, or product that could be exploited to compromise confidentiality, integrity, or availability. CISO Assistant tracks vulnerabilities as first-class objects, separately from the incidents they may cause and the risk scenarios they feed into.
The vulnerability surface answers two operational questions: what's exposed and are we treating it fast enough?
A vulnerability sits inside a domain and points outward at what it affects (assets) and what's treating it (applied controls). Threat-intel feeds enrich it asynchronously — KEV/NVD/EUVD feeds attach security advisories, NVD enrichment tags it with CWEs. The vulnerability SLA policy is a single platform-wide setting that maps severity to a deadline: when severity changes and no explicit due date has been set, the platform recomputes the due date from the policy.
Identification — a name, an optional reference ID (typically a CVE), a description.
Severity — the shared scale (undefined / info / low / medium / high / critical).
Status — undefined, potential, exploitable, mitigated, fixed, not exploitable, unaffected.
Affected scope — the assets exposed, and the entities (third parties) involved when relevant.
Vulnerabilities are unusual in that the platform sets a due date automatically based on severity, via the . The flow:
A vulnerability is created (or its severity changes).
If no explicit due date is set, the SLA policy is applied — high severity gets a tighter deadline than medium, and so on.
The bulk "refresh due dates" action re-applies the policy across existing rows when the SLA configuration changes.
Explicit due dates the user (or an import) sets are preserved — the policy only fills in the blank.
Vulnerabilities don't live in isolation. CISO Assistant can pull from external feeds to enrich them automatically — see :
KEV feed flags vulnerabilities confirmed exploited in the wild.
EPSS feed attaches a probabilistic exploitation score.
NVD enrichment pulls CWE mappings, affected configurations, and references.
These enrichments link a vulnerability to its security advisory and CWE entries, making severity triage less guesswork.
The status field captures where each vulnerability sits in that flow; the linked applied controls and security exceptions explain how it's being treated.
Treatment — linked applied controls (the remediations), (formal deviations), CWE entries, and one or more security advisories.
Timing — detection date, publication date, ETA, and the SLA due date computed from the severity-driven policy.
Vulnerability
Vulnerability
First-class object
Security advisory
sec_intel.SecurityAdvisory
Ingested from KEV / NVD / EUVD
CWE
sec_intel.CWE
Common Weakness Enumeration
SLA policy
GlobalSettings("vulnerability-sla")
Severity → days mapping
detected → triaged → treated (mitigated / fixed / exception / accepted) → closedEBIOS RM (Expression des Besoins et Identification des Objectifs de Sécurité — Risk Manager) is the structured risk-management method published by the French national cybersecurity agency, ANSSI.
CISO Assistant supports EBIOS RM natively, with a dedicated object graph rather than forcing the method into a generic risk-assessment shape.
EBIOS RM organises a study around five workshops:
Scope and security baseline — define the studied system, its mission, and the regulations it must comply with.
Risk origins and target objectives — identify who might attack and what they want (the RO/TO couples).
Strategic scenarios — model attack paths through stakeholders to reach target objectives.
Operational scenarios — drop into technical detail: kill chains, attacker techniques, supporting assets touched.
Risk treatment — score residual risk and plan the action plan.
An EBIOS RM study lives in a domain (no perimeter — the study itself is the scope envelope). It unfolds through five workshops that produce, in order: feared events (undesirable outcomes on primary assets) and stakeholders in workshop 1; RO/TO couples (Risk Origin × Target Objective) in workshop 2; strategic scenarios showing high-level attack paths through stakeholders in workshop 3; and operational scenarios drilling into kill chains, operating modes, and elementary actions in workshop 4. Workshop 5 reuses the platform's standard objects — applied controls, evidence, residual risk — to treat both strategic and operational scenarios.
At workshop 4, an operational scenario can also reference the adversary techniques it relies on, picked from a loaded TTP catalog such as MITRE ATT&CK. See .
EBIOS RM scenarios sit alongside qualitative risk scenarios in the same risk register: both contribute to the residual-risk picture for a perimeter, and both can be treated with the same applied controls.
EBIOS RM study
EbiosRMStudy
Container for the five workshops
Feared event
FearedEvent
Workshop 1 outcome on a primary asset
Stakeholder
Stakeholder
Workshop 1 — internal/external party with trust + dependency scores
RO/TO couple
RoTo
Workshop 2 — Risk Origin × Target Objective
Strategic scenario
StrategicScenario
Workshop 3 — uses an attack path through stakeholders
Operational scenario
OperationalScenario
Workshop 4 — composed of kill chains, operating modes, elementary actions
Applied control
AppliedControl
Shared with the rest of the platform; used at workshop 5
An audit is the evaluation of a perimeter against a framework. It produces a per-requirement view of status, score, evidence, and the applied controls that substantiate each requirement.
Because applied controls are decoupled from compliance requirements, a single set of controls can be evaluated against many frameworks in parallel without re-doing the work.
An audit always lives inside a domain (the mandatory IAM scope) and is assessed against one framework. A perimeter can optionally narrow the audit further — e.g. to a specific service or process inside the domain. On creation the platform spawns one requirement assessment per requirement in the framework — those rows are where status, score, and the supporting applied controls and evidences live.
The fundamental input to an audit is a framework — a published standard such as ISO/IEC 27001:2022 or NIST CSF. Frameworks ship as YAML libraries. If you can't find one that fits your needs, you can build your own and import it.
An audit assesses compliance against the chosen framework. The evaluation of a single requirement inside an audit is called a requirement assessment.
A requirement assessment is not a single value — it captures several dimensions at once, separating the compliance result from how the work got done and from the depth of the implementation. The point is that the same row tracks the auditor's view, the analyst's progress, and the maturity of the underlying implementation without conflating them.
Every audit in the audit tables (and on dashboards and campaigns) shows a Progress percentage. It answers a single question: how much of the audit has been assessed?
What counts as assessed depends on which fields the audit exposes to the auditor. A single rule drives every surface (audit tables, the audit page, journeys, and My assignments), so the number never diverges between views:
Status mode — the default, and active whenever the field is visible to the auditor. A requirement counts as assessed only once its status is Done. Nothing else moves the needle: a requirement can already be Compliant or scored and still not count until it is explicitly marked Done. This makes progress the analyst's own "I'm finished with this one" signal.
Content mode — active when the status field is hidden. A requirement counts as assessed when:
for questionnaire requirements, the questionnaire is fully answered (every question answered), or it already carries a result or a score;
Enabling scoring pre-fills every requirement at the scale minimum, so a bare minimum score is treated as "not touched yet" and does not count. Only a score genuinely moved above the minimum counts as assessed, which stops empty maturity- or scoring-only audits from showing 100%.
In other words, progress is an auditing-activity signal, not a compliance signal. An audit can be 100% in progress and still be largely non-compliant — the column tells you the team has gone through every requirement and reached a verdict, not that the verdicts are good. The actual compliance picture lives in the donut and score read-outs computed from the below.
Only assessable requirements count toward the denominator — section titles and headings from the framework are skipped. When the audit is scoped to specific , only the requirements inside those groups count, so progress reflects the work you actually committed to rather than the framework's full catalogue.
Several frameworks ship with their requirements pre-tagged into implementation groups (IG) — labels that act as filters over the requirement catalogue. They serve two recurring purposes, depending on how the framework's authors used them:
Maturity tiers — basic / intermediate / advanced (CIS Controls' IG1 / IG2 / IG3, CyFun's Basic / Important / Essential, FedRAMP's Low / Moderate / High). Picking IG1 narrows the audit to the minimum baseline; IG2 adds the next tier; IG3 expands to the full catalogue. This is the "how mature do we want to be?" axis.
Scope or applicability — slices like physical security, SaaS, cloud, or framework-specific selectors like ISO 27001's SoA group. Picking these narrows the audit to the requirements that actually apply to your context, regardless of maturity.
The two patterns are not exclusive — a single framework can mix them, and several IGs can be combined on the same audit. If no IG is selected the audit covers the full catalogue, and you can change the selection later (adding or removing IGs) without losing the work already done on requirements that stay in scope.
Where IGs show up in the platform:
Audit scope — selected at creation, editable afterwards. Anything outside the selection isn't dropped, it's simply hidden from the audit's assessable count.
Progress, score, donut, and analytics — all roll-ups respect the IG selection. The widget in audit analytics shows progress per IG when you want to compare tiers.
Framework report — the report filter lets you re-slice the audit by IG after the fact, so the same audit can produce an IG1-only view and a full-catalogue view without re-running the assessment.
See for the mechanic of selecting IGs at audit creation, and the framework library's own documentation for which IG taxonomy a given standard ships with.
The headline dimension — the actual answer to "does this requirement hold?". Each requirement carries one of:
Compliant
Partially compliant
Non compliant
Not applicable
This is the field that feeds the framework's compliance percentages, the report, and the cross-framework roll-ups.
For questionnaire-driven frameworks (whether authored in the or imported from an — same vocabulary on both paths), the result is computed from the compute_result tag carried by each question choice and aggregated worst-wins across the requirement's questions, with not_applicable neutral.
Text type questions are not taken into account for the result computation: leaving one empty does not hold the requirement at Not assessed.
If you maintain a tenant whose audits were produced under the older boolean-collapse logic, see for the realignment procedure.
Independently of the compliance result, each requirement assessment captures who is working on it and where they are in their process:
An assignee (an actor — user, team, or entity) — who is responsible for assessing this requirement.
A workflow status — To do / In progress / In review / Done. This is the analyst's status, not the auditor's verdict.
The two layers exist because the same requirement can be Compliant but still In review (the analyst has reached a conclusion, but a peer hasn't validated it yet) — and an In progress requirement obviously doesn't have a final compliance result yet. Splitting analyst progress from compliance result lets dashboards and the campaign view show meaningful "still to do" counts without polluting the compliance percentage.
When you enable on the audit, each non-compliant or partially-compliant requirement can carry an additional qualification on a specific scale:
Major nonconformity
Minor nonconformity
Observation
Opportunity for improvement
This is the auditor's grading language, useful when the framework requires distinguishing major from minor non-conformities (ISO certification audits being the canonical case). It's an extra layer attached to the result — not a replacement for it.
Beyond the binary compliance result, each requirement assessment can carry a score on the audit's scale. Scoring captures how mature or deep an implementation is, not just whether it exists. There are two ways to score, depending on what the audit needs:
Maturity score (single layer) — one score per requirement, typically used for CMMI-style or NIST-CSF-style maturity assessments.
Implementation + Documentation scores (two layers) — toggle on documentation score to split scoring into is this implemented? and is the implementation documented?. The platform computes the maturity score as the average of the enabled layers.
Each requirement assessment uses an effective scoring scale, resolved in this order:
Requirement scale — a requirement can carry its own min_score, max_score, and level labels, set by the framework author.
Audit scale — otherwise, the audit's own scoring scale (ComplianceAssessment).
The audit scale is chosen when the audit is created and stored on the audit. The form proposes the baseline audit's scale for a copy, otherwise the framework's scale when the framework declares one, otherwise the organisation default set in (0–5 out of the box); a preset can be picked instead. Changing the organisation default later leaves existing audits alone. The audit scale can also be changed after creation, with existing scores converted proportionally — unless the framework's questionnaire computes the scores or some requirements carry their own scale, in which case the framework's scale is fixed. See .
The scoring UI, documentation score, exports, and tree views use that effective scale for the requirement.
When an audit contains mixed scales, average-based roll-ups normalise each requirement against its effective range before aggregating, then display the result on the audit scale. Sum-based roll-ups stay raw: they add score x weight, and their maximum is the sum of each requirement's effective maximum times its weight.
If anchor N/A to target is enabled, not-applicable requirements contribute the audit target projected onto their own effective range. If no target is configured, they contribute their effective maximum.
Together with the compliance result, the analyst dimension, the extended results, and the scoring layers, a single requirement assessment can record: what's the compliance state, who is working on it and where they are, how severe any non-conformity is, and how mature the implementation is — all without conflating them.
Each requirement assessment can carry a thread of comments — short, dated, author-attributed notes used for in-context conversation during the audit. They sit alongside the formal fields and don't change the compliance result or the score; they're where the back-and-forth between the analyst, the reviewer, and the auditee happens (clarifications, follow-up questions, agreed-upon next steps).
Each comment has a body, an author, a creation timestamp, and an active / processed toggle so resolved threads can be filtered out of the default view without losing the history. Comments can be edited (the platform records the edited state), preserving who said what and when. The panel is collapsed by default and shows the comment count, so it stays out of the way until you open it.
Comments are not exclusive to requirement assessments — the same thread is available on risk scenarios, applied controls, and findings.
On audits, Comments is governed by two controls: the comments (the platform-wide master switch, default on) and the per-audit , which lets you make the thread visible to respondents, auditor-only, or hidden. So you can keep comments enabled everywhere while still hiding the discussion from third-party respondents on a sensitive audit. Authors are also masked for third-party participants who can't see other users.
See for the full feature reference — processed state, edit history, permissions, and author privacy.
Evidence justifies the status of a compliance requirement or proves that an applied control has been implemented. It can be a description, a link, or an uploaded file, and it can be attached to any number of applied controls or requirement assessments.
With the findings_from_requirements on, a requirement assessment gains a Findings tab. Raise a finding records a non-compliance without leaving the requirement; the picker next to it binds an existing finding, so an issue raised elsewhere (a pentest binder, say) is tied to the requirement it violates. The audit's own findings collect in a created on first use.
A finding belongs to one requirement assessment at a time. The picker only offers findings not bound to another requirement; to move one, edit the finding itself. Binding and unbinding are refused while the finding's binder is locked, and a locked audit does not take new findings. Raising needs permission to add findings, picking needs permission to change them.
otherwise, when the result is visible, as soon as it leaves the default Not assessed state (Compliant, Partially compliant, Non compliant, or Not applicable);
otherwise, for scoring-only audits, when the score is strictly above the applicable minimum of the scale.
Audit
ComplianceAssessment
One audit = one framework × one domain (× optional perimeter)
Requirement assessment
RequirementAssessment
Per-requirement row inside the audit
Requirement
RequirementNode
Read-only catalog entry from the framework library
Domain
Folder
Required; drives IAM scoping
Framework
Framework
Read-only library import




Third-party risk management (TPRM) is the discipline of evaluating the security and compliance posture of the vendors, suppliers, and service providers your organisation depends on.
CISO Assistant treats third parties as a first-class concern with their own object graph, separate from internal compliance work.
Four interlocking objects model the third-party landscape:
Entity — a vendor, supplier, or service provider. The unit of organisational identity.
Solution — a specific product or service provided by an entity. An entity can have many solutions.
Contract — the formal agreement covering one or more solutions, with dates, renewal terms, and obligations.
Representative — the person on the entity side who answers questionnaires and signs off on assessments.
The actual review of a third party is an entity assessment. It can:
Use a questionnaire — custom, or imported from a library (CAIQ, SIG, …).
Trigger an audit that lives in the entity's third-party workspace, so the third party can fill it in directly.
Capture the residual risk you accept by working with this entity.
Carry labels, to sort and filter assessments in the list. The list offers labels as an opt-in column and a filter, labels can be added or removed in bulk, and the CSV/Excel export of the list includes them. Only labels you are allowed to see appear, on screen and in the export.
The questionnaire behind an entity assessment is a real audit, and it lives in a folder of its own — a third-party workspace (an enclave, internally). The workspace is what makes the vendor's access safe: view rights granted there never reach up into the parent domain, so a representative sees their questionnaire and nothing else.
Each third party gets one workspace per domain, named after the entity. Everything the vendor relationship accumulates — evidences, tasks, the respondent group, the representatives' access — sits in that one folder and carries across assessment rounds.
Reassessing a vendor is a New revision, from the entity assessment's page. It creates a fresh assessment for the same third party in the same workspace, with a questionnaire that carries over the previous round's answers, results and evidences — so the vendor confirms or corrects rather than starting from a blank form. Solutions, reviewers, authors, representatives, labels and custom field values come across too.
Two things deliberately do not: the criticality rating and its inputs, which are a judgement to be made again each round, and the source assessment itself, which is left untouched as the record of the previous round.
Because name and version identify an assessment within a domain, a revision needs a different name or a different version from the one it came from.
The entity list carries a Last assessment column — the most recent assessment for each third party, with its date and status — so the register answers "who is overdue for a review?" without opening anything. Third parties that have never been assessed are shown as Never assessed, and can be filtered on that.
An entity's tier is its criticality on your organisation's scale, managed under Third parties > Criticality. The default scale has four tiers: Critical, Important, Standard and Low impact. Solutions are ranked on the same scale.
The tier and the entity assessment answer different questions. The tier is a qualification: how much the vendor matters to you, and so how closely to follow it. The assessment is the vendor's posture: how well it protects what you entrust to it. A critical vendor can have an excellent posture.
A tier is set in one of two ways:
Assessed. A tiering form is filled for the entity and accepted. The form computes a value from the answers, and its bands and knock-out rules turn that value into a tier. Two forms ship with the platform, one of them built on the EBIOS RM stakeholder criteria.
Forced. Someone who can edit the entity picks a tier directly, with an optional reason.
The latest decision wins, whichever way it was made. Each change is kept in the entity's Tier history: the tier before and after, who made the change, when, and which response it came from.
See for the walkthrough and how to adapt the forms to your method.
Many organisations buy a continuous outside-in rating of their vendors. With the external_ratings on, entities gain an External ratings tab where those readings are recorded:
Rating provider — SecurityScorecard, Bitsight, CyberVadis, and so on. The list is yours to define through .
Score and Scale maximum — the value as the provider publishes it, plus the top of that provider's scale (100 for SecurityScorecard, 900 for Bitsight). Recording the scale is what lets scores from different providers be compared: a normalised 0–100 value is derived from the two.
Grade — the letter rating, when the provider publishes one.
As of
Readings accumulate as history rather than overwriting each other — one per provider per day — so a vendor's trajectory is visible, not just its current number. Ratings sit alongside assessments rather than replacing them: an outside-in score is evidence, not an answer to a questionnaire.
CISO Assistant supports two modes for getting the actual answers back from the vendor, depending on whether the third party is allowed to access your instance.
This is the default workflow. You create a representative on the entity side, the platform issues credentials, and the representative logs into CISO Assistant directly to fill in the questionnaire. Their access is scoped: they only see the entity assessment(s) attached to their entity, and they land on the respondent dashboard inside a simplified layout: a header with your logo, their questionnaires, a language picker and their profile. There is no sidebar, search, command palette or chat assistant, so they never see the rest of your workspace.
This surface is gated by the auditee_mode , which has to be on for the third-party login flow to be available.
Use this mode when:
Your instance is reachable from the internet (or from the vendor's network).
You want live progress visibility — answers land in the database as they're typed, so the dashboard updates in real time.
You want comments, validation flows, and the full audit trail to apply to the vendor's answers automatically.
Don't confuse this with the feature — that one is for internal users splitting one audit across teammates using the
respondentrole within your own organisation. The third-party flow described here is for external vendors answering through the auditee surface.
Many organisations don't want to expose their CISO Assistant instance to third parties — for security, network, or contractual reasons. In that case, the platform supports a fully file-based round trip:
Export the questionnaire from the framework page. On the framework backing the entity assessment, use the Export to Excel action. You get an .xlsx file containing every requirement of the framework, structured so the vendor can fill in the answers, observations, and supporting evidence references in dedicated columns.
Send the file to the vendor. Email, secure file share, sneakernet — whatever channel your procurement and security policies allow. The vendor opens the spreadsheet in any tool that can edit Excel.
Receive the filled-in file back. The vendor returns the spreadsheet with their answers populated. No platform access was needed at any point.
Use this mode when:
Your instance can't be (or shouldn't be) exposed to the vendor's environment.
The vendor refuses to log into a third-party platform.
You want a tangible artefact — the signed-off Excel — as part of the audit record.
Both modes produce the same internal state at the end: a populated entity assessment with per-requirement answers and observations. You can mix them within an organisation (online for one vendor, offline for another) or switch a single vendor from one mode to the other mid-cycle if the situation changes.
Treating third parties as a parallel surface — rather than just "another perimeter" — matters because:
Permissions differ: third-party representatives need restricted access, not full role-assignment privileges in your domain.
Lifecycle differs: contracts have renewal dates, vendors come and go, but your internal frameworks stay stable.
Reporting differs: TPRM dashboards aggregate across many entities, not down inside one.
Carry , when the feature is enabled.
Link and Observation — where the report lives, and what you make of it.
Re-import via the data import wizard. Use the to upload the filled-in spreadsheet. The wizard maps the rows back to the requirement assessments on the existing entity assessment, populating answers, observations, and any other captured columns.
Minor nonconformity, Major nonconformity, etc.
This tutorial guides you through managing minor nonconformities and major audit results within your audits
You will learn how to add extra attribute to your audit through extended result.
Click "ISO 27002 SOA" to access the relevant audit for managing audit results.
Click "Edit" to enable modifications on the selected audit.
Click "More" to reveal additional settings and options for the assessment.
Click "on" to activate the desired feature or option within the assessment settings.
Click "Save" to apply and store the changes made to the audit.
Click "4.1 - Understanding the organization and its context" to examine specific requirements.
Select the appropriate category such as major nonconformity, minor nonconformity, observation, opportunity for improvement, or good practice.
Click the note stating "Major and minor nonconformities are only applicable when result is non-compliant or partially compliant." to understand criteria.
Click "Save" to store the additional nonconformity or observation details.
Click "Save" to finalize and save the observation or nonconformity information entered.
You have successfully managed minor nonconformities and major audit results by editing, documenting, and saving relevant audit details. This ensures accurate tracking and supports effective audit management for your organization.
A technical posture measures a fleet of assets against a technical baseline — a CIS Benchmark, a hardening guide, or any check-shaped framework — and keeps the measurements coming. Where an audit captures a considered, point-in-time evaluation, a technical posture is a living scoreboard: scanners, scripts, and operators keep pushing results, and the platform always shows the latest state of every check on every asset.
A technical posture always lives inside a domain and is measured against one framework, whose assessable requirements act as the check list. It monitors a set of assets, and every ingestion of results — a manual entry session, an API push, a file import — is recorded as a run carrying one check result per (check, asset) it touched. An optional findings binder is where remediation of failing checks is tracked.
Technical posture
PostureAssessment
One framework × one domain × a set of assets
Run
PostureRun
One ingestion event; keeps the tool name and start time
Check result
PostureResult
One measurement of one check on one asset
Check
RequirementNode
Assessable requirement from the framework library
Findings binder
FindingsAssessment
Findings binder with the "Posture follow-up" category
Domain
Folder
Think of a technical posture as a three-dimensional cube of measurements:
Checks — the assessable requirements of the chosen framework. If the framework defines implementation groups (for example Automated vs Manual recommendations in a CIS Benchmark), the Selected implementation groups field narrows the check list to the slice you committed to.
Assets — the fleet under measurement. Assets can be enrolled explicitly, or automatically the first time results arrive for them.
History — for each (check, asset) cell, the platform keeps the last N results, where N is the History depth of the assessment (10 by default). Older results are pruned as new ones arrive.
The cube is sparse on purpose: nothing is created upfront. A cell only exists once something has measured it, and the Measured checks read-out tells you how much of the cube is filled. The current posture — what the overview, the score, and the action plan are computed from — is simply the latest result in each cell.
Each result carries one of five values: Pass, Fail, Not applicable, Error, or Not checked, along with the observed and expected values and a message when the source provides them. The Pass rate score is computed over Pass and Fail only — errors and unchecked cells don't dilute it.
Technical postures deliberately do less than audits:
Question answered
"Are we compliant, and can we prove it?"
"What is passing right now, and what changed?"
Rows
One requirement assessment per requirement, created upfront
Sparse — only measured cells exist
Result
Compliance result, score, maturity — set by a person
Pass/fail vocabulary — set by whatever measured it
The two share the same framework catalogue, so a benchmark imported once can back both a formal audit and a running posture. Use an audit when a human needs to weigh evidence and sign off; use a technical posture when a tool can answer the question and you want it answered often.
Every way of getting results in produces a run:
Manual runs — the "New manual run" flow on the Runs tab, or per-asset editing in the tree view, for checks a human performs.
API — scanners push JSON to the assessment's upload-results endpoint; re-using the returned run identifier accumulates or patches the same run. The API tab shows a ready-to-copy example.
File import — the "Import results" button on the Runs tab accepts CSV and XLSX files as well as OCSF Compliance Finding JSON. A mapping dialog lets you bind arbitrary scanner CSV columns and result values, choose how colliding rows aggregate, and target one or several of the scoped assets.
A run only records the checks present in what was sent. A check missing from a file or a push is never assumed to pass: it keeps its previous result.
Runs are the ingestion log of the assessment: the Runs tab lists them with their tool, check counts, and pass/fail breakdown, and a run's detail page shows exactly what it recorded per asset. The trend chart on the Overview tab plots the pass rate across runs, which is how posture drift becomes visible.
The matching cloud benchmark (for example a CIS Benchmark)
The compliance CSV Prowler writes for that framework
Pick the Prowler compliance preset in the mapping dialog
Plumber CI/CD Security Checks
plumber analyze --ocsf results.json
Direct, no mapping
Plumber scans GitHub Actions and GitLab CI pipelines. Its OCSF output carries one finding per control, passes included, with the control key in the finding's control field. Each Plumber control is a check of the Plumber CI/CD Security Checks framework, matched on that key; the per-job findings land in the check's message. Controls disabled in your .plumber.yaml come in as Not checked.
To set it up, create a technical posture on the Plumber CI/CD Security Checks framework, select the GitHub Actions or GitLab CI implementation group together with Cross-provider, and add one asset per repository. Then, after each scan, use Import results on the Runs tab, choose the file, and tick the repository under Apply results to.
Prowler's own OCSF export is not used: it is made of detection findings, which only report what failed, so a passing check would be indistinguishable from one that was never evaluated. Its compliance CSV reports every requirement.
The Action plan tab lists every check whose current result is anything other than Pass — failures first, then errors, then unchecked and not-applicable cells — so the operational team can decide what to investigate. From there, each line can be turned into a finding with one click.
Findings do not live on the technical posture itself. They are created in the findings binder linked to the posture (the creation form offers to create one automatically via Create a related findings assessment). This separation is deliberate:
Measurements are ephemeral; remediation is not. A check result is overwritten by the next run and eventually pruned by the history depth. A finding needs a stable life of its own — status, owner, ETA, severity, linked applied controls — that survives every re-measurement. Pinning remediation to a measurement row would lose it on the next scan.
Remediation is a shared discipline. Findings binders are the platform's common remediation ledger — the same object used for pentest reports and internal reviews. Routing posture failures into one means they benefit from the full findings workflow (action plans, applied controls, reporting) instead of a parallel, posture-only mechanism.
The posture stays a pure measurement surface. The cube tells you what is true right now; the binder tells you what you are doing about it. Either can be read — or rebuilt — without touching the other.
The link is kept visible in both directions: the action plan shows the finding attached to each failing check with its status and ETA, and re-measuring a remediated check back to Pass is how the fix is verified — on the next run, not by declaration.
Required; drives IAM scoping
Substantiation
Evidences and applied controls per requirement
Observed vs expected values from the measurement
Cadence
Campaign or review cycle
Continuous — every scan is a new run
Evidence is anything that substantiates a claim in CISO Assistant — that a control has been implemented, that a requirement is met, or that a process is being followed as described.
Evidence is the connective tissue between what the audit asks and what the organisation actually does.
Evidence is the shared substantiation surface — the same record can back an applied control, a requirement assessment, and a finding at the same time, which is why a single proof can satisfy many frameworks at once. Each evidence comprises a chain of revisions; the latest revision holds the current attachment (with its SHA-256 hash) or external link. When a recurring task produces evidence, the corresponding task occurrence is recorded on the revision so the audit trail goes both ways.
An uploaded file — PDF, screenshot, configuration export, signed approval, exported policy.
A link to an external system — a Jira ticket, a Confluence page, a Git commit, a monitoring dashboard, a signed agreement.
A free-form description, when the proof is the assertion itself (rare but allowed).
Evidence attaches to two places:
Applied controls — proof that the control is in place and working.
Requirement assessments — proof that a specific compliance requirement is met.
Because the same applied control can satisfy many requirements across many frameworks, a single piece of evidence often substantiates compliance against several requirements at once — without duplication.
Evidence isn't attached once and forgotten. Each piece carries metadata — description, timestamp, expiry, assignee — and a status. Auditors regularly refresh evidence: a yearly penetration-test report needs to be re-uploaded each year; an exported configuration needs to be re-pulled after each significant change.
Each piece of evidence keeps a versioned history through evidence revisions. Replacing an attachment doesn't overwrite the previous file — it creates a new revision under the same evidence object. Every revision carries:
A monotonically increasing version number.
The attachment (file) or link for this revision.
An SHA-256 hash of the attachment, computed automatically and used for integrity checks.
An observation field for notes about what changed.
The current attachment shown on the evidence page is always the latest revision; older revisions remain accessible for audit. This matters when an assessor asks "what proof did you have on date X?" — the historical revision is still there.
An optional link to the task occurrence that produced it — when an evidence file is generated by completing a recurring task, the task occurrence is recorded on the revision so the audit trail goes both ways.
Evidence
Evidence
Logical record (stable identity)
Evidence revision
EvidenceRevision
Versioned payload (file or link)
Task occurrence
TaskNode
Optional producer back-link
A privacy register is the catalogue of personal-data processing activities the organisation carries out — required by GDPR and equivalent regulations as the foundation of accountable data handling.
CISO Assistant models the register as a graph of typed objects so it stays queryable, auditable, and reusable across audits.
A processing lives in a domain and comprises the records that describe it: its purposes, the personal data it handles, the data subjects, recipients, contractors and transfers. When you carry it out as a processor, or share it as a joint controller, it names the clients involved. Its security measures are applied controls. A controller processing can be screened, and assessed by a DPIA when it needs one.
GDPR asks for two registers. A controller records the processings it decides on (Art. 30(1)). A processor records the processings it carries out on behalf of its clients (Art. 30(2)). Many organisations are both: a payroll provider is the controller of its own HR processing and a processor for its customers' payroll.
CISO Assistant keeps one list of processings and sets each one's GDPR role:
Clients are , picked from the entities you can see.
On the Processings list, two quick filters switch between the registers: Controller register and Processor register. The list shows each processing's GDPR role, and its Clients (controllers or processors) among the optional columns. A processing added while Processor register is selected starts as a processor one.
A processor processing has no DPIA fields: the DPIA is the controller's duty. See .
Each controller processing records whether it needs a data protection impact assessment, with the reference of its DPIA. When the DPIAs feature flag is on, the processing's detail page gains a DPIA screening card: it runs a screening to decide, sets the verdict by hand, starts the DPIA, and keeps the history of every decision. Privacy → DPIAs follows the whole register.
See , and the .
Privacy operations also need to record events as they happen:
Right requests — data-subject requests under GDPR (access, rectification, erasure, portability) and the organisation's response.
Data breaches — incidents affecting personal data, with the notification clock and authority correspondence. A breach can point at an . In the Incident field, typing a name that matches no incident offers ✨ Create incident "…": saving the breach creates that incident in the breach's domain, or reuses one with the same name there. Saving it needs the right to add incidents in that domain.
A privacy register lives in a domain with its own RBAC scoping — typically a DPO-led folder.
Findings from GDPR audits surface in the register as actions on processings.
Data contractors and clients cross-reference TPRM entities, so a sub-processor's security review feeds both worlds.
A processing's nature and its personal-data categories draw from the layer, so you can align them to your own internal vocabulary.
— customise processing nature and personal-data category labels
A processing can be exported as a multi-sheet Excel workbook from its detail page and re-imported — through the data import wizard or the CLI — to recreate it with all its sub-objects. See .
Processing
An activity that operates on personal data (collect, store, transfer, delete). The central object
Purpose
The lawful reason a processing exists, with its legal basis. Each processing has at least one purpose
Personal data
What is being processed (name, email, location, health record, biometric, …), with categories that map to GDPR sensitivity classes
Data subject
The kind of individual the data refers to (employee, customer, prospect, …)
Data recipient
The internal teams or external parties that receive the data
Data contractor
Third parties that process data on your behalf — a TPRM entity surfaced here with a privacy-specific lens
Data transfer
Flows of data to entities outside the original jurisdiction
GDPR role
Register
Clients (controllers or processors)
Controller (default)
Controller register, Art. 30(1)
Not asked
Joint Controller
Controller register, Art. 30(1)
The Co-controllers you share the processing with
Data Processor
Processor register, Art. 30(2)
The clients on whose behalf you carry it out. When you are a sub-processor, the processor that engaged you

The project-management module brings PMBOK-style planning into CISO Assistant: a structured way to organise complex, multi-stakeholder initiatives — go-lives, accreditations, transformation programmes — alongside the compliance and risk work they drive.
It's the newest concept in the platform, and the object graph will continue to evolve.
A project sits in a domain and can stack as a portfolio → programme → project hierarchy (the same model with three values of kind). On creation it's auto-paired with a generic collection — the flexible bag where the audits, risk studies, findings, evidences, and policies tied to the project accumulate. A responsibility matrix can be attached to one or more projects, encoding RACI / RASCI / RAPID assignments of actors to activities (each activity in turn references the work objects it covers). An accreditation is the formal authorisation event: it links its decision evidence and its compliance-assessment checklist back to the project's collection.
Project objects don't replace — they sit alongside. Use a perimeter to define the scope of assessment; use a project to plan the work needed to bring that scope into compliance or through an accreditation.
A single project typically references many perimeters, audits, applied controls, and findings binders — it's the cross-cutting view the security organisation works against day-to-day.
Every project carries a Budget and an Actual cost field (with a currency that defaults to the instance setting), so leadership has a place to capture the financial envelope an initiative is operating under and what's been consumed against it.
This is deliberately the high-level view only. The project's budget number is not computed from anywhere — it's the planned envelope a sponsor signed off on, and the actual cost is whatever spend you record against it. Both are decimal money values, not roll-ups.
The detailed financial picture — line items, build vs run split, amortisation, who's spending — lives where the work actually happens: on the referenced by the audits, risk studies, findings binders, and other objects sitting in the project's . Each applied control carries its own structured cost; the of every assessment in the collection rolls those costs up into a budget overview.
The link between the two layers is indirect: applied controls aren't attached to a project directly. They're attached to the work objects inside the project's collection (a control satisfies an audit's requirement, mitigates a risk scenario, remediates a finding), and through that chain they become part of the project's financial picture.
When the project budget and the rolled-up applied-control costs diverge, that gap is itself a signal — you've either committed more spend than the project envelope foresaw, or your action-plan controls are missing cost data. The platform doesn't flag the gap automatically; the numbers are surfaced on both sides for the project manager to reconcile.
— the detailed build / run cost model that drives the bottom-up view.
— where the rolled-up cost of an assessment's controls is shown.
Project
Project
kind enum (Portfolio / Program / Project); self-FK parent_project
Collection
GenericCollection
Polymorphic bag (audits, risk / EBIOS / CRQ studies, findings, evidence, policies, exceptions)
Responsibility matrix
ResponsibilityMatrix
RACI / RASCI / RAPID / Custom; M2M to Project
Activity
ResponsibilityMatrixActivity
Row of a matrix; many M2Ms to work objects
Assignment
ResponsibilityAssignment
RACI cell — (activity, actor, role) unique
Accreditation
Accreditation
Linked to a Collection; checklist FK to ComplianceAssessment







Organisation-defined sensitivity schemes — like TLP — with ordered, coloured levels
An object classification is a scheme that marks how sensitive an object is — think TLP (Traffic Light Protocol), or a custom scale aligned to a customer's or an authority's own tiers. Each scheme is an ordered set of levels, and any object that supports classification can be tagged with one of them.
CISO Assistant ships with TLP built in, and lets you tune it or define your own schemes alongside it.
Organisations exchange documents with customers, partners, and authorities that each impose their own sensitivity tiers. One party works in TLP; another in a national CONFIDENTIAL / SECRET scale; a customer in its own bespoke levels. Object classifications let you define those schemes once and apply them consistently, with colour coding people already recognise.
A scheme comprises an ordered set of levels. Each level carries a rank (its position in the order, from least to most sensitive), a colour, an abbreviation, and a translatable name and description. A level can then classify an object — today, a document — which surfaces it as a coloured badge and, on published PDFs, a page marking. The dashed edge is deliberate: classifying an object is always optional.
A scheme is a thin container with a name and an ID (TLP, for example). Its meaning lives in its levels, which are ordered by rank:
Rank sets the order and carries semantic weight — a higher rank is more sensitive. TLP runs CLEAR < GREEN < AMBER < AMBER+STRICT < RED.
Colour is the swatch shown wherever the level appears; the platform picks readable text automatically over it.
Abbreviation is the short token on the badge (AMBER, RED).
The built-in TLP scheme follows the FIRST TLP 2.0 standard — five levels with their official colours. Its structure is protected: you can hide levels you don't use, but you can't delete or reorder the canonical set.
Because levels are ranked rather than just listed, the order is data, not decoration — "at least AMBER" is a well-defined comparison. Today a classification is a visual marking; the ordinal rank is the foundation for comparing and, in future, gating access by level.
Open a scheme to reach its level editor, where you can:
Add a custom level with Add level — set its abbreviation, name, and colour; it slots in at the end of the order.
Reorder levels with the up/down controls (rank follows the order).
Hide a level with its visibility toggle — it stops being offered without being destroyed.
Edit or delete custom levels.
Built-in levels (and the built-in TLP scheme itself) can only be hidden, never deleted or restructured — so the canonical TLP always stays intact, and a hidden level stays hidden across restarts.
When you create or edit a document, the Classification field lets you pick a level. Once set, the level shows as a coloured badge on the document catalog, in the reader, and in the documents table, and it is stamped on every page of the document's exported PDF (for example TLP:AMBER, in the level's colour). Deleting a level a document points at simply clears the field — the document is never removed.
Documents are the first object type to carry a classification; the same mechanism extends to other objects over time.
Schemes and their levels live in the global (root) folder — they apply organisation-wide. Multiple schemes can coexist, so TLP and a customer-specific scheme are both available at once.
In the sidebar under Extra → Object classifications. The list shows every scheme; open one to manage its levels. Access is governed by the standard permissions — viewing is available to every role, while creating and editing schemes and levels is reserved for administrators.
— the first object type that carries a classification
— a related organisation-defined override layer
Name and description are translatable, so a level reads in the user's language.
Decide which processings need a data protection impact assessment, carry it out on the processing, and keep the record of every decision
A data protection impact assessment (DPIA) is the analysis GDPR Article 35 requires before a processing likely to result in a high risk to people's rights starts. A screening, or pre-DPIA, is the short check that decides whether a processing needs one.
CISO Assistant runs both on the privacy register. It answers three questions a DPO is asked:
Which processings need a DPIA, and why?
Which of those have one, under way or accepted?
What did each DPIA conclude, and which measures came out of it?
A processing is screened by a screening, a filled quick form. Once accepted, the screening sets the processing's DPIA required flag, and every change of that flag lands in the requirement history. A processing that requires one is then assessed by a DPIA, another quick form filled about the same processing. Once accepted, the DPIA becomes the processing's DPIA of record, and the measures it picked are added to the processing's applied controls.
A DPIA is the controller's duty (Art. 35). A processor only assists the controller with it (Art. 28(3)(f)).
So screenings and DPIAs apply to processings whose GDPR role is Controller or Joint Controller. A processing in the has no DPIA card, does not appear on the DPIAs page, and cannot be screened. If a processing becomes a processor one while a screening or DPIA is open, accepting it writes nothing: the request says it Does not apply to this object any more, so nothing will be written.
The Pre-DPIA screening library ships a screening form built on the nine WP29/EDPB criteria, as the CNIL applies them. It has three pages:
Processing identification: the processing, your role, the stage, the planned start date.
WP29 criteria: which of the nine criteria apply, a short justification, and an optional Supporting document (an email or a PDF backing the decision, such as the DPO's opinion).
Exemptions: only shown when two or more criteria are met. An exemption applies, for example, when an equivalent DPIA already exists.
Each criterion ticked scores one point, and the score decides the outcome:
The form's On accept setting maps each outcome to a verdict. Your own screening form can do the same: any quick form whose On accept writes DPIA required is a screening form, and it shows on the DPIAs page under Screening forms. Such a mapping can also leave chosen outcomes to a reviewer.
The reviewer decides instead of the mapping when:
an outcome left to review fired (An exemption was claimed: the reviewer decides);
no mapped outcome fired (No rule gave a verdict: the reviewer decides);
fired outcomes give different verdicts (Rules disagree on the verdict: the reviewer decides).
In every case the reviewer can override the verdict at acceptance, with a justification.
A DPO may decide without a screening, or against one. Set manually on the processing's DPIA screening card, or on a row of the processings list, sets DPIA required directly, with an optional reason kept in the history.
The card always says where the current verdict comes from:
The processing's DPIA requirement history tab lists every change: when, the new and previous verdict, the source, who made it, and the note. The history cannot be edited.
The Data protection impact assessment (DPIA) library ships a DPIA form following the structure of the CNIL PIA method:
A DPIA is about one processing, and some of its pages show what the processing already records, read-only: personal data, data subjects and recipients on the data page, purposes on the proportionality page, contractors and transfers on the rights page, applied controls on the measures page. While the DPIA is a draft, they reflect the processing as it is; once submitted, as it stood then. The questions ask about what the register does not hold, rather than copying what it does.
Each risk page measures Severity and Likelihood on four levels, from Negligible to Maximum. The form's Privacy risk rating crosses the two in a grid, so each risk gets a level: Acceptable, To reduce or Unacceptable. The levels show for the current risk and, when the residual questions are answered, for the residual risk once the planned measures are in place.
When a residual risk stays Unacceptable, the DPIA fires Residual risk unacceptable: consult the supervisory authority, as Article 36 requires.
Ratings are a quick form feature: see to adapt the grid or build your own.
A DPIA's On accept writes two things onto the processing:
DPIA: the accepted response becomes the processing's DPIA of record.
Associated controls: the measures the DPIA picked are added to the processing. Measures picked on a page that is hidden are left out, and so are measures from a domain the processing cannot reach. Accepting only adds: a control already on the processing stays.
A rejected DPIA is no DPIA: the card offers to start another one.
Privacy → DPIAs shows where every controller processing you can see stands:
Click a state to filter the list, and each row offers the next step: Run DPIA screening, Start DPIA, Continue an open DPIA, or Review a screening waiting for a decision. The side panel lists the Screening forms loaded.
: the step-by-step guide
: review, separation of duties, writing back onto the subject
DpiaRequirementChange
Append-only, one row per change, whatever the path
DPIA of record
Processing.dpia
The last accepted DPIA
Two or more criteria, and an exemption applies
No DPIA required
Screening
QuickFormResponse
A response to a form whose On accept writes processing.dpia_required
DPIA
QuickFormResponse
A response to a form whose On accept writes processing.dpia
DPIA required
Processing.dpia_required
With who or what set it last (dpia_required_source) and when
DPIA required
Two or more criteria, no exemption
DPIA required
DPIA recommended
One criterion
DPIA required, shown with a Recommended badge: the form errs on the safe side
No DPIA required
No criterion, all answered
No DPIA required
An accepted screening
Screened on the date, with a link to the screening
A reviewer's override
Adjusted at acceptance on the date, with a link to the screening
A manual change
Set manually on the date
Nothing yet
Not screened yet
Context (overview; data, processes and supports)
Benefits and context, applicable standards, who accesses the data, its lifecycle, what it relies on
Core principles (proportionality and necessity; data subjects' rights)
Legitimacy of purposes, minimisation, accuracy, retention, information, consent, rights, contracts with processors, transfers
Risks (measures; illegitimate access; unwanted modification; disappearance; by-design risks)
For each of the three feared events: impacts, threats, risk sources, the measures treating it, severity and likelihood with a justification, and the residual severity and likelihood once planned measures are in place
Validation
The DPO's opinion and how it was applied, the data subjects' views, the decision and its conditions
DPIA required, none started
The gap to close first
DPIA required, under way
A DPIA exists and is not accepted yet
DPIA required, DPIA accepted
Done
No DPIA required
Screened or set as not needing one
Not screened yet
No verdict at all



DPIA requirement history
Exemption claimed — no DPIA required
CISO Assistant is delivered as a set of containers — frontend, backend, database, and reverse proxy — that can run locally via Docker, on a VPS, or on Kubernetes via the Helm chart. The diagram below sketches how the pieces fit together.
This section is being expanded.
Material to come: prerequisites, local Docker setup, virtualisation and remote deployment, deploying on a VPS, the Helm chart, S3 storage, mailer, custom certificates, secrets management, and instance upgrades.
Docker Compose or Helm for Kubernetes
Customise the local deployment according to your needs:
Make sure Docker and Docker Compose are installed on your system.
clone the repo: git clone https://github.com/intuitem/ciso-assistant-community.git
run the preparation script and follow the instructions: ./docker-compose.sh
You can also find other variants for different setups as a starting point for your specific needs:
Make sure the Helm binary is installed and switch to your cluster context. The chart is published to a GitHub OCI registry — no helm repo add needed.
Pull the default values:
Edit custom.yaml for your environment. At minimum, look at:
global.domain — the hostname your instance will serve on.
global.tls
See for the full procedure (image tag pinning, values reference, and operational notes).
ingress.tls.*backend.config.djangoSecretKey — rotate from the default.
backend.config.databaseType (sqlite, pgsql, or externalPgsql) and the matching postgresql.* / externalPgsql.* block.
Create a namespace: kubectl create ns ciso-assistant
Install: helm install ciso-assistant-release oci://ghcr.io/intuitem/helm-charts/ce/ciso-assistant -f custom.yaml -n ciso-assistant
helm show values oci://ghcr.io/intuitem/helm-charts/ce/ciso-assistant > custom.yamlHardware Requirements:
CPU: 4 cores
RAM: Minimum 16 GB
Storage: Minimum 10 GB (consider more for evidences)
You can start with lower specs of course for testing.
Software Requirements:
Ubuntu/Debian, CentOS, RHEL: LTS versions recommended when applicable*
Docker 27 or up, with Docker compose, or Kubernetes Cluster 1.31 or up
Postgres 16 or up if you are choosing this variant
*most Linux distributions supporting Docker should be compatible but have not been tested. Some distributions are not using the official repositories so make sure to follow the instructions from docker page.
Any SMTP compatible Mailer
Organisation-defined labels that override the platform's defaults
CISO Assistant ships with a default vocabulary for the values that appear in dropdowns and badges across the UI — risk origin types in EBIOS RM, project status values, qualifications on incidents, accreditation status labels, metric units, and so on. The terminology layer lets each organisation override those defaults to match its own internal vocabulary, without changing the platform's data model.
Two organisations can use CISO Assistant against the same framework and the same methodology, but speak entirely different internal languages. One team's "Risk Origin: state-actor" is another team's "Adversary: nation-state". The terminology surface lets you reshape the labels without forking the platform.
A terminology entry binds a label to a field path — the specific UI surface where the label appears. The shipped field paths today cover:
ro_to.risk_origin — Risk Origin types on RO/TO couples in EBIOS RM.
qualifications — qualification tags on incidents, risk scenarios, and BIA escalation thresholds. The word qualification itself can be swapped for risk category across the interface via .
accreditation.status and accreditation.category — labels on accreditation records.
entity.relationship — third-party relationship types.
metric_definition.unit — units shown alongside metric values.
project.status and project.health — workflow labels on projects.
processing.nature — the nature of a processing activity (collection, storage, disclosure, erasure, …) in the privacy register.
personal_data.category — categories of personal data (name, email, health data, …) in the privacy register.
For each field path, the platform ships a built-in set of entries (state, organised crime, terrorist, activist, … for risk origins, for example). You can:
Hide built-in entries you don't want to surface — the is_visible flag controls dropdown inclusion.
Add organisation-specific entries alongside the built-ins.
Translate entries through the standard library translation mechanism.
The platform falls back to the built-in default whenever an entry is missing or hidden — terminology is additive on top of a working set, not a replacement.
Terminology entries live in the root folder by default, meaning they apply organisation-wide. Built-in entries cannot be deleted; they can only be hidden.
In the sidebar under Extra → Terminologies. The feature is gated by the terminologies feature flag, which is on by default.
— a different kind of organisation-defined override
Deployment documentation for rootless containers
Since V3.16, CISO Assistant now runs as non-root user 1001:1001 by default across all compose templates.
If you are new from Ciso-Assistant, you will automatically be deployed with a rootless Docker configuration by running pre-built images or local image build
You already have a self-hosted Ciso Assistant (Community version or On-Premise Pro version) and you want to know all the options you have:
In the case you do not want to have rootless Docker
If you want to verify if you run a rootless Docker container, do:
It should say :
./docker-compose.sh./docker-compose-build.shdocker exec -it backend iduid=1001 gid=1001 groups=1001git pull
docker compose down
sudo chown -R 1001:1001 ./dbdocker compose up -dgit pull
docker compose down
sudo chown -R 1001:1001 ./db
docker compose -f docker-compose-build.yml up -ddocker compose downimage: ghcr.io/intuitem/ciso-assistant-community/frontend:v3.15.2
# replace with (for example)
image: ghcr.io/intuitem/ciso-assistant-community/frontend:v3.16docker compose up -dVirtual Private Server - Remote internet-facing VM
This setup aims to expose CISO Assistant on a VPS while using automated Let's Encrypt for certificates management.
provision your VPS and make sure it has a public reachable IP - make sure to have the Prerequisites mentioned on that page.
Setup your DNS zone to point to the IP of your VPS (A record). Give it sometime to propagate (depends on the registrar). It's better to start with this once you get the IP to give it enough time for propagation.
on the following I'm using ubuntu 24.04. So adjust the packages installation according to your OS
ssh to your server and perform the following commands:
Follow the instructions and make sure to do the following:
select VM/Remote
Internet facing and ACME ready - yes
Provide the FQDN you've set on your registrar
Port to use: 443
It should look like something like this:
Keep track of the URL mentioned at the end of the config generator. You can review the generated yml file and adapt it if needed.
Wait for the app to initialize and you will get a prompt to enter the first admin user and the password.
You can go back and update the docker-compose.yml according to your needs or restart the interactive guide to create a new one.
You can choose Traefik or BunkerWeb instead of Caddy using the config builder. Please note that BunkerWeb deployment is still experimental at the moment.
The generated file in the config directory will be named docker-compose-custom.yml For subsequent operations with compose, you'll need to specify it with -f
If you're running docker compose without the -f, it could conflict with the default one on the repository root directory.
If you're starting a production environment:
have your docker-compose-custom renamed and stored out of the repo,
have your db folder outside of the repo.
#update ubuntu repository and OS
sudo apt update
sudo apt upgrade
# install docker
sudo snap install docker
#install python
sudo apt install python3-pip python3.14-venv
#clone the repo
git clone https://github.com/intuitem/ciso-assistant-community.git
#go to the config generator
cd ciso-assistant-community
cd config
# setting up the python project and dependencies
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
# run the interactive config generator
python make_config.py# switch to sudo. This can be avoided depending on your docker setup
sudo su
./docker-compose.shcd config
# stop and remove containers
docker compose -f docker-compose-custom.yml rm -fs
# delete the db and proxy config
git clean -fdx .
Experimenting CISO Assistant through remote server or hypervisor
To get started with the config builder, make sure you have python and docker installed. Here is an example on ubuntu:
#update ubuntu repository and OS
sudo apt update
sudo apt upgrade
# install docker
sudo snap install docker
#install python
sudo apt install python3-pip python3.14-venv
#clone the repo
git clone https://github.com/intuitem/ciso-assistant-community.git
#go to the config generator
cd ciso-assistant-community
cd config
# setting up the python project and dependencies
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
# run the interactive config generator
python make_config.pyIf you aim to expose the VM to internet, use this dedicated guide:
If you aim to connect from the VM
If you aim to connect to the VM from your network
This means that you will be using a browser from within the VM so localhost settings are applicable. You can simply use the default ./docker-compose.sh at the root of the repository or trigger the config builder with the following settings:
run ./docker-compose.sh and connect from within the VM using https://localhost:8443
setup a FQDN for your VM and make sure it's known by the host you are connecting from. This will vary depending on your OS. For instance, for linux/mac, you can add a line to your /etc/hosts file such as:
192.168.1.87 ca.homelab.local
in this example, the first part is your VM's ip and the second one will be the FQDN you'll be providing to the config builder and that you will use to connect later on.
Run the config builder and provide the following settings:
run ./docker-compose.sh and connect from your host this time using https://ca.homelab.local:8443
Notes:
If you don't want to have a specific port, use the port 443 during the settings, given it's not used by another application on your system.
In the remote setup, if you also want to connect from within the VM, you can add your custom FQDN to the /etc/hosts of your VM but mapped to 127.0.0.1
---
Let's say that you want to setup or experiment with CISO Assistant on a Network or Virtualized environment (eg. Hypervisor) on a remote host, for instance, to use with multiple users:
Install a recent version of Docker on your remote server
Given that we are using TLS with Caddy, we need to have DNS entries and not IPs
The workstations need to be able to reach the remote using an FQDN (DNS entry). If not you can add an entry on your /etc/hosts. Keep track of the remote server DNS as you'll put it on the next step, let's say the remote is cool-vm for instance
Five lines need to be edited. Save the file and move to the next step
If you're getting SSL_ERROR_INTERNAL ERROR_ALERT (Can be different on other browsers) blocking you from continuing, make sure that you've made the 5 changes above.
The tls internal (equivalent to -i in CLI mode) parameter of Caddy can present some security issues and is not recommended for production and internet exposure. You should consider proper certificates for that.
You're all set, and you can simply run:
Your CISO Assistant can be reached now from https://cool-vm:8443, and you can skip the SSL warning for the self-signed certificate.
docker-compose.yml file as follows:
(red is for deletion and green for addition); your diff should look like:./docker-compose.sh



This guide covers how to keep sensitive configuration (database credentials, mailer passwords, API keys) out of your docker-compose.yml.
.env FileDocker Compose automatically loads variables from a .env file located next to docker-compose.yml. This is the recommended approach for all secrets.
.env file# .env
# ── Postgres ───────────────────────────
POSTGRES_NAME=ciso_assistant
POSTGRES_USER=ciso_assistant
POSTGRES_PASSWORD=change-me-to-something-strong
# ── Django / Backend ───────────────────
DJANGO_DEBUG=False
CISO_ASSISTANT_URL=https://localhost:8443
ALLOWED_HOSTS=backend,localhost
CISO_SUPERUSER_EMAIL=admin@example.com
# ── Mailer ─────────────────────────────
EMAIL_HOST=smtp.example.com
EMAIL_PORT=587
EMAIL_USE_TLS=True
EMAIL_HOST_USER=notifications@example.com
EMAIL_HOST_PASSWORD=smtp-secret-password
DEFAULT_FROM_EMAIL=ciso-assistant@example.com
# ── Rescue Mailer (optional) ──────────
# EMAIL_HOST_RESCUE=smtp2.example.com
# EMAIL_PORT_RESCUE=587
# EMAIL_HOST_USER_RESCUE=rescue@example.com
# EMAIL_HOST_PASSWORD_RESCUE=rescue-secret
# EMAIL_USE_TLS_RESCUE=True
# ── S3 Storage (optional) ─────────────
# USE_S3=True
# AWS_ACCESS_KEY_ID=AKIA...
# AWS_SECRET_ACCESS_KEY=wJal...
# AWS_STORAGE_BUCKET_NAME=my-bucket
# AWS_S3_ENDPOINT_URL=https://s3.eu-west-1.amazonaws.comdocker-compose.ymlReplace every hardcoded value with a ${VARIABLE} reference:
services:
backend:
container_name: backend
build:
context: ./backend
dockerfile: Dockerfile
restart: always
depends_on:
- postgres
environment:
- ALLOWED_HOSTS=${ALLOWED_HOSTS}
- CISO_ASSISTANT_URL=${CISO_ASSISTANT_URL}
- DJANGO_DEBUG=${DJANGO_DEBUG}
- POSTGRES_NAME=${POSTGRES_NAME}
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- DB_HOST=postgres
- EMAIL_HOST=${EMAIL_HOST}
- EMAIL_PORT=${EMAIL_PORT}
- EMAIL_USE_TLS=${EMAIL_USE_TLS}
- EMAIL_HOST_USER=${EMAIL_HOST_USER}
- EMAIL_HOST_PASSWORD=${EMAIL_HOST_PASSWORD}
- DEFAULT_FROM_EMAIL=${DEFAULT_FROM_EMAIL}
- CISO_SUPERUSER_EMAIL=${CISO_SUPERUSER_EMAIL}
volumes:
- ./db:/code/db
huey:
container_name: huey
build:
context: ./backend
dockerfile: Dockerfile
depends_on:
- backend
restart: always
environment:
- ALLOWED_HOSTS=${ALLOWED_HOSTS}
- CISO_ASSISTANT_URL=${CISO_ASSISTANT_URL}
- DJANGO_DEBUG=False
- POSTGRES_NAME=${POSTGRES_NAME}
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- DB_HOST=postgres
volumes:
- ./db:/code/db
entrypoint:
- /bin/sh
- -c
- |
uv run python manage.py run_huey -w 2 --scheduler-interval 60
frontend:
container_name: frontend
environment:
- PUBLIC_BACKEND_API_URL=http://backend:8000/api
- PROTOCOL_HEADER=x-forwarded-proto
- HOST_HEADER=x-forwarded-host
- ADDRESS_HEADER=x-forwarded-for
- XFF_DEPTH=1
build:
context: ./frontend
dockerfile: Dockerfile
depends_on:
- backend
postgres:
container_name: postgres
image: postgres:16
restart: always
environment:
POSTGRES_DB: ${POSTGRES_NAME}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- ./db/pg:/var/lib/postgresql/data
caddy:
container_name: caddy
image: caddy:2.11.4
restart: unless-stopped
ports:
- 8443:8443
command:
- caddy
- reverse-proxy
- --from
- https://localhost:8443
- --to
- frontend:3000
volumes:
- ./db:/dataTip — DRY with YAML anchors: Since
backendandhueyshare most variables, you can use extension fields to avoid repetition:x-common-env: &common-env ALLOWED_HOSTS: ${ALLOWED_HOSTS} CISO_ASSISTANT_URL: ${CISO_ASSISTANT_URL} POSTGRES_NAME: ${POSTGRES_NAME} POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} DB_HOST: postgres services: backend: environment: <<: *common-env DJANGO_DEBUG: ${DJANGO_DEBUG} EMAIL_HOST: ${EMAIL_HOST} # ... other backend-specific vars huey: environment: <<: *common-env DJANGO_DEBUG: "False"
chmod 600 .envUse override files to separate dev and production configurations without touching the base file:
docker-compose.yml # base (references ${VARIABLES}, no secrets)
docker-compose.override.yml # local dev defaults (loaded automatically)
docker-compose.prod.yml # production overrides (git-ignored)# Dev — override is loaded automatically
docker compose up
# Production
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -dEach environment can point to its own .env file:
This lets you commit safe dev defaults while keeping production secrets in a separate file.
docker compose --env-file .env.prod -f docker-compose.yml -f docker-compose.prod.yml up -dKubernetes installation with the official Helm chart
The chart lives at oci://ghcr.io/intuitem/helm-charts/ce/ciso-assistant. Source: charts/ciso-assistant-next/ in the repo.
A Kubernetes cluster you have admin access to.
helm 3.8+ (OCI registries require 3.8 or newer).
kubectl configured for the target cluster.
An ingress controller if you want to expose the app externally.
A persistent storage class (defaults work on most managed clusters).
Pull the default values into a working file:
Customise custom.yaml. The most important settings:
global.domain — the hostname your instance will serve on.
The backend pod runs migrations on startup; allow it ~30s before checking the frontend.
The in-product AI assistant needs two things wired in the chart: the ENABLE_CHAT flag on the backend and a reachable vector database for retrieval-augmented generation. Setting qdrant.enabled: true deploys the official as a dependency (a StatefulSet with persistent storage and health probes) and injects QDRANT_URL automatically.
Notes:
Any key under qdrant: is passed through to the Qdrant subchart (e.g. qdrant.persistence.size, qdrant.resources). Persistence is on by default; see the for all options.
The Qdrant subchart is bundled inside the published chart, so installing needs no access to the Qdrant Helm repo. Air-gapped clusters still pull the Qdrant image at runtime: mirror it and override qdrant.image.repository (same as for the backend/frontend images).
If your pods need to trust an internal CA (for SMTP, SSO, or any outbound TLS service signed by a private authority), provide just your CA in a secret. Trust is added on top of the default roots, not replaced, so public CAs keep working:
Backend and Huey (Python): an init container concatenates the system CA bundle with your CA into a shared bundle, and SSL_CERT_FILE / REQUESTS_CA_BUNDLE point at it.
Frontend (Node): NODE_EXTRA_CA_CERTS points at your CA, which Node adds to its built-in roots.
The backend init container writes the merged bundle to an emptyDir. If you also harden the pod to run as a non-root user (global.securityContext.runAsNonRoot / per-component runAsUser), set an fsGroup so that user can write the volume:
To restrict pod traffic, enable the bundled NetworkPolicy and pass your own ingress/egress rules. The policy selects all pods of the release by default, so your rules must also allow the internal flows the app relies on: the frontend BFF calls the backend (PUBLIC_BACKEND_API_URL) server-side, and the backend reaches Qdrant. Forgetting these blocks the app even though the ingress controller can reach it.
Replace ciso-assistant-release below with your Helm release name.
Chart upgrades are normally additive: new capabilities ship as new values keys, and existing keys are not removed or renamed within a major chart version, so your current custom.yaml keeps working as-is. New features are opt-in and off by default — if you don't set their keys, nothing changes in that area.
When a breaking change to the values shape is unavoidable, it is released under a new major chart version and the required migration steps are documented in the release notes. Read them before upgrading across a major version.
The main thing to manage on upgrade is the application version. If you did not pin global.image.tag, the images move to the chart's appVersion on the next helm upgrade. Pin the tag to the version you tested and review the for the range you're crossing. Database migrations run automatically on backend startup, so no manual schema step is needed.
PostgreSQL persistent volumes are not deleted automatically when using the bundled subchart. Inspect with kubectl get pvc -n ciso-assistant and remove manually if you want a clean slate.
Note: The old Helm repository (
intuitem.github.io/ca-helm-chart) is no longer maintained. The values shape changed substantially between the legacycharts/ciso-assistant/chart and the currentcharts/ciso-assistant-next/chart (frontendOrigin→global.domain, noclientName/clusterDomainshorthand, separate ingress block). New installs should use the OCI registry above.
global.tls — set to true if you're serving over HTTPS.ingress.enabled and ingress.tls.* — enable + configure the ingress.
backend.config.djangoSecretKey — rotate from the default changeme.
backend.config.databaseType — sqlite (default, single-pod), pgsql (bundled PostgreSQL via Bitnami subchart), or externalPgsql.
postgresql.* — when using the bundled PostgreSQL.
externalPgsql.* — when pointing at your own PostgreSQL instance.
backend.config.smtp.* — outgoing email configuration.
The chart README in the repository carries the full values table (charts/ciso-assistant-next/README.md) — refer to it for every key with its default and description.
Pin the appVersion to a published release if you want predictable upgrades. Set global.image.tag in custom.yaml to the version you tested against (e.g. v3.18.2). Leaving it empty pins to the chart's appVersion, which moves with the chart.
Create a namespace and install:
qdrant.enabled: false and set QDRANT_URL through backend.env and backend.huey.env.The LLM provider (Ollama or any OpenAI-compatible endpoint, model, base URL) is configured from the in-app Settings → Chat/AI section, not from the chart. LLM inference is heavy; point it at a GPU-backed endpoint.
The Qdrant collection and the indexes are not created automatically. After the pods are up, run the indexing commands once from the backend pod (init_qdrant creates the collection, index_objects indexes your existing risk/control/asset records, index_libraries indexes the framework libraries):
helm show values oci://ghcr.io/intuitem/helm-charts/ce/ciso-assistant > custom.yamlkubectl get pods -n ciso-assistant
kubectl get ingress -n ciso-assistantbackend:
config:
chat:
enabled: true # sets ENABLE_CHAT on the backend and Huey worker
qdrant:
enabled: true # deploys the Qdrant subchart and injects QDRANT_URLkubectl create secret generic my-ca -n ciso-assistant --from-file=ca.crt=./internal-ca.crtglobal:
extraCerts:
enabled: true
secretName: my-ca
fileName: ca.crt
mountPath: /etc/ssl/extra-certsglobal:
securityContext:
fsGroup: 1001networkPolicy:
enabled: true
policyTypes:
- Ingress
ingress:
# internal traffic between CISO Assistant pods (frontend -> backend, backend -> qdrant)
- from:
- podSelector:
matchLabels:
app.kubernetes.io/instance: ciso-assistant-release
# the ingress controller reaching the frontend and backend
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ingress-nginxhelm upgrade ciso-assistant-release oci://ghcr.io/intuitem/helm-charts/ce/ciso-assistant \
-f custom.yaml \
-n ciso-assistanthelm uninstall ciso-assistant-release -n ciso-assistantkubectl create ns ciso-assistant
helm install ciso-assistant-release oci://ghcr.io/intuitem/helm-charts/ce/ciso-assistant \
-f custom.yaml \
-n ciso-assistantPOD="deploy/ciso-assistant-release-backend"
kubectl exec -n ciso-assistant $POD -c backend -- uv run python manage.py init_qdrant
kubectl exec -n ciso-assistant $POD -c backend -- uv run python manage.py index_objects
kubectl exec -n ciso-assistant $POD -c backend -- uv run python manage.py index_libraries --syncBasic setup for local deployment and experimentation
The recommended pattern for local deployment is to use Docker Compose. Check the Readme file on the CISO Assistant repo for the latest instructions.
The compose file will manage three containers and set the required variables:
Front
Back
Caddy (proxy)
Make sure to have a recent version of Docker installed
On a Linux distro with a server flavor, make sure to remove older versions and install the latest one using the proper Docker repos to avoid twisted setups. Check out the instructions at
On Windows, Docker Desktop+WSL is recommended
On MacOS, Docker Desktop covers the requirements
Run:
It will clean up previous images and get the latest stable release.
Once the images are downloaded and migration triggered, you should see a prompt asking you to set the first superuser. Follow the instructions to set it, and you should be ready.
In case you are running on an unsupported architecture, you can open a GitHub issue so that we add its support or use the next steps to build the images locally.
Alternatively, if the previous configuration didn't succeed, run:
Given that Caddy is using a self-signed certificate, your browser will mention a warning that you can accept and continue.
How to add custom certificates for your remote installation
You can configure your own Certificate by replacing the line tls internal in the docker-compose.yml by tls <cert_file> <key_file>. Here is Caddy documentation on this https://caddyserver.com/docs/caddyfile/directives/tls
Before doing this, there is just one step, you need to add the cert_file and the key_file inside caddy container.
You have basically two ways to do it:
Adding the two files inside caddy_data directory, as it is already mounted by default in the volumes, and specify the path to the files:
If you don’t have this volume or you want to add another, create a repository at the same level of your docker compose file for example /certs, add the files inside and moun it:
caddy:
container_name: caddy
image: caddy:2.11.4
...
volumes:
- ./caddy_data:/data
command: |
sh -c 'echo $$CISO_ASSISTANT_URL "{
reverse_proxy /api/* backend:8000
reverse_proxy /* frontend:3000
tls /data/<path>/cert_file /data/<path>/key_file
}" > Caddyfile && caddy run'./docker-compose.sh./docker-compose-build.shcaddy:
container_name: caddy
image: caddy:2.11.4
...
volumes:
- ./caddy_data:/data
- ./certs:/certs
command: |
sh -c 'echo $$CISO_ASSISTANT_URL "{
reverse_proxy /api/* backend:8000
reverse_proxy /* frontend:3000
tls /certs/cert_file /certs/key_file
}" > Caddyfile && caddy run'How to connect your S3 block storage for your installation
By default, CISO Assistant stores attachments on the local filesystem. You can configure it to use an S3-compatible object storage (AWS S3, MinIO, etc.).
A running S3-compatible storage
An existing bucket (must be created before starting CISO Assistant)
Valid access credentials (Access Key / Secret Key)
Set the following environment variables in the backend environment:
export USE_S3=True
export AWS_ACCESS_KEY_ID=<your-access-key>
export AWS_SECRET_ACCESS_KEY=<your-secret-key>
export AWS_STORAGE_BUCKET_NAME=<your-bucket-name>
export AWS_S3_ENDPOINT_URL=<your-s3-endpoint>That's it ! You can now launch your backend and your attachments will be sent to your S3 🔥
You can test S3 support using MinIO:
Then go on , enter your minio root user/password and create a bucket with the name 'my-ciso-bucket'.
The backend environment variables will be:
You can now see your attachments on the MinIO console after importing them in ciso-assistant.
export USE_S3=True
export AWS_ACCESS_KEY_ID=ciso-assistant-admin
export AWS_SECRET_ACCESS_KEY=not_secure_password
export AWS_STORAGE_BUCKET_NAME=my-ciso-bucket
export AWS_S3_ENDPOINT_URL=http://localhost:9000docker run -d \
--name minio \
-p 9000:9000 \
-p 9001:9001 \
-e MINIO_ROOT_USER=ciso-assistant-admin \
-e MINIO_ROOT_PASSWORD=not_secure_password \
-v minio_data:/data \
minio/minio server /data --console-address ":9001"CISO Assistant sends transactional emails (password reset, superuser creation, notifications) over SMTP, SMTP with OAuth 2.0 or Microsoft Graph. This page covers configuration and the TLS specifics in
EMAIL_HOST=smtp.example.com
EMAIL_PORT=465
EMAIL_HOST_USER=noreply@example.com
EMAIL_HOST_PASSWORD=<secret>
DEFAULT_FROM_EMAIL=noreply@example.com
# Pick ONE of the two (not both):
EMAIL_USE_SSL=True # SMTPS (typically port 465)
EMAIL_USE_TLS=False
# or
EMAIL_USE_SSL=False
EMAIL_USE_TLS=True # STARTTLS (typically port 587)When EMAIL_PORT is unset, the port is 465 with EMAIL_USE_SSL and 25 otherwise, STARTTLS included. Set EMAIL_PORT=587 explicitly for submission over STARTTLS.
For a relay that requires a client certificate, set EMAIL_SSL_CERTFILE (and EMAIL_SSL_KEYFILE when the key is in a separate file) to paths inside the container. EMAIL_FORCE_TLS_1_2 applies on top of it.
For local development you can run Mailpit and point EMAIL_HOST at it with both flags set to False.
With ENABLE_CUSTOM_MAILER=True in the environment, administrators get a Mailer tab under Settings where they configure a primary mailer, an optional second mailer and the sender address themselves. When that configuration is active it replaces the environment mailers entirely; when it is parked ("Use these mailers" off) or empty, the environment applies. The switch is off by default and is meant for hosted instances; on-prem operators set it if they want the panel.
Rules enforced by the panel:
A blank port means the standard port for the TLS mode: 465 with SSL, 25 otherwise. Enter 587 explicitly for submission over STARTTLS.
The mail server host, and the token URL for OAuth, must resolve to a public address, like the other URLs an administrator can configure (LLM, integrations, audit sinks). For a relay or identity provider on a private network, start the backend with ALLOW_PRIVATE_NETWORK_REQUESTS=True. A Microsoft Graph mailer has no address of the administrator's to check: its endpoints are Microsoft's.
Every mailer must pass a test before the configuration can be saved: the test sends one message through each mailer, on its own, to the administrator's own address, and reports per mailer. The test uses the values in the form, not the stored ones. An override exists for servers that only accept mail for their own domain.
The Microsoft 365 and Google Workspace buttons fill in the SMTP with OAuth values from the sections below; the tenant-side steps are the same. Microsoft Graph is the third entry of the transport list, not a button: the Microsoft 365 button configures SMTP with OAuth, not Graph. Choosing Graph replaces the encryption, host and port fields with the tenant ID; the mailbox goes in the username field, then the client ID and client secret. The tenant-side steps are those of the . A failed test shows the token endpoint's answer, for example token endpoint answered 400: unauthorized_client ... for a wrong client ID, or Graph's reply, for example Graph replied 403 ErrorAccessDenied when the application may not send as the mailbox.
Send a test email through the configured mailers and see which one delivered:
With Docker Compose: docker compose exec backend python manage.py send_test_email you@example.com. The command prints the mailers it found, warns about any it had to skip, and exits non-zero with the reason when nothing was sent. It works for every transport, and so does the test button of the Mailer tab; the SMTP snippet further down is only for isolating a TLS trust problem.
This has two consequences:
The old recipe of running update-ca-certificates from command: in docker-compose.yaml no longer works — it requires root and write access to /etc/ssl/certs/, both denied by the new container. See the deprecated section below if you are still on an older image.
Every certificate in the chain presented by your SMTP server must satisfy the strict checks. A single non-compliant intermediate will fail the whole verification.
Your SMTP server certificate and every certificate in its chain must include:
BasicConstraints — CA:FALSE on the leaf, CA:TRUE on intermediates and root
KeyUsage — at minimum digitalSignature, keyEncipherment on the leaf
Authority Key Identifier (AKI) — on every cert in the chain, not only the leaf
If you get an error like:
regenerate the offending certificate with the missing extension. Inspect a cert with:
You cannot modify the system trust store at runtime anymore. Instead, mount a PEM file containing the CA(s) to trust and point OpenSSL at it via SSL_CERT_FILE:
How it works:
The SMTP transports go through smtplib, which builds its TLS context with ssl.create_default_context().
That function asks OpenSSL to load its default trust store.
When SSL_CERT_FILE is set, OpenSSL uses that file instead of the system bundle (/etc/ssl/certs/ca-certificates.crt).
Requirements for the bundle file:
A single PEM file, concatenating your root CA and any intermediates needed to validate the SMTP server's chain.
Each cert in the bundle must itself satisfy the strict X.509 checks listed above (BasicConstraints, KeyUsage, AKI).
From the backend container:
A clean (250, b'2.0.0 OK') means TLS and trust are correctly configured. Any SSL: CERTIFICATE_VERIFY_FAILED will name the missing extension or the failing cert.
This snippet checks SMTP only. For Microsoft Graph, send_test_email (or the test button) is the check: a certificate the bundle does not trust reads TLS handshake failed, a proxy that will not connect reads the HTTP proxy refused the connection.
You can also trigger a password reset from the UI and watch the backend logs: the mail service logs every mailer it had to skip with the reason, and the Mailer tab's health table keeps the last outcome and error per mailer.
The EMAIL_*_RESCUE variables define a second mailer, with its own EMAIL_TRANSPORT_RESCUE, tried only when the first cannot be reached: connection refused, login refused, token refused. Once a server has accepted the message, a failure is final and the second mailer is not tried, so a message is never sent twice. Any transport works in either position; the Mailer tab offers the same second mailer.
Older versions ran the container as root with a writable filesystem, which allowed mounting a CA at runtime and refreshing the trust store from command::
When upgrading to 3.16+, remove the command: override and switch to the CA bundle method above.
Microsoft 365 and Google Workspace no longer accept a mailbox password for SMTP. CISO Assistant can authenticate with an OAuth 2.0 token instead. The session is still SMTP: same host, same port, same firewall rules. Set EMAIL_TRANSPORT=smtp-oauth2, then give the token endpoint, the grant and the credentials. The same variables exist with a _RESCUE suffix for the second mailer.
EMAIL_HOST_USER is the mailbox mail is sent as. EMAIL_HOST_PASSWORD is not used. EMAIL_USE_TLS or EMAIL_USE_SSL is required: the token is a bearer credential and is never sent over a plaintext session. EMAIL_TIMEOUT (5 seconds by default) also bounds the token request.
There are no provider presets in the application. The blocks below are the values for the two common providers; any provider that follows the OAuth 2.0 standards works the same way.
On the tenant side:
Register an application in Entra ID and create a client secret.
Do not grant SMTP.SendAsApp tenant-wide in Entra. Grant it through Exchange RBAC for Applications with a management scope limited to the sending mailbox, so a leaked secret cannot send as anyone else. Grants from Entra and Exchange add up, so an Entra grant would undo the scope.
Enable SMTP AUTH on that one mailbox (Set-CASMailbox -SmtpClientAuthenticationDisabled $false). OAuth does not bypass the SMTP AUTH switch.
On the Workspace side, create a service account, download its JSON key, and grant it domain-wide delegation for the scope https://mail.google.com/.
Use the same variables with the values from the provider's documentation. refresh_token additionally needs EMAIL_OAUTH2_REFRESH_TOKEN; EMAIL_OAUTH2_CLIENT_SECRET is optional for it, so public clients work. The application keeps using the refresh token from the environment. That works with providers that hand out a new refresh token on each refresh while the old one stays valid (Microsoft, Keycloak by default), but not when the provider revokes a refresh token once it has been used (Keycloak "Revoke Refresh Token", rotation with reuse detection in Okta or Auth0): turn that off for this client. When the token is refused, the error says so. jwt_bearer needs either EMAIL_OAUTH2_SERVICE_ACCOUNT_FILE or EMAIL_OAUTH2_ISSUER plus EMAIL_OAUTH2_PRIVATE_KEY, and optionally EMAIL_OAUTH2_AUDIENCE when it differs from the token URL. The token URL must use https. It may point at a private identity provider.
Tokens are cached until shortly before they expire. A token the server rejects is dropped from the cache and the login is retried once with a new token, on a new connection when the server took longer than EMAIL_TIMEOUT to answer the failed login. A second rejection counts as an unreachable mailer and the next configured mailer, if any, is tried.
Two situations rule out SMTP, with or without OAuth: a tenant whose policy disables SMTP AUTH for every mailbox, and a network whose only way out is an HTTP proxy, which SMTP cannot cross. Microsoft Graph sends mail over HTTPS and covers both. Set EMAIL_TRANSPORT=graph; there is no host, port or TLS setting.
EMAIL_HOST_USER is the mailbox mail is sent as. The same variables exist with a _RESCUE suffix, so Graph can also be the second mailer behind an SMTP relay. EMAIL_TIMEOUT bounds each request, the token request included; it is 15 seconds by default for this transport, since both endpoints are reached across the internet and often through a proxy. In the Mailer tab, the same four values go under the Microsoft Graph transport.
On the tenant side:
Register an application in Entra ID and create a client secret.
Do not grant Mail.Send in Entra: that application permission is tenant-wide, so a leaked secret could send as any mailbox. Grant it through Exchange RBAC for Applications with a management scope limited to the sending mailbox. Grants from Entra and Exchange add up, so an Entra grant would undo the scope. In Exchange Online PowerShell, with the object ID from the Enterprise applications page (not App registrations):
Check it with Test-ServicePrincipalAuthorization -Identity <client id> -Resource noreply@example.com, which must report InScope true. The assignment can take up to two hours to apply to a running application. SMTP AUTH can stay disabled.
Outbound HTTPS follows the standard proxy variables, HTTPS_PROXY and NO_PROXY, like every other HTTPS call the backend makes; an intercepting proxy's CA goes in SSL_CERT_FILE as described above. Token endpoint failures, and a Graph endpoint the network cannot reach (proxy refusal, timeout), count as an unreachable mailer and the next configured mailer, if any, is tried, even while a token is cached. A rejected send is final and the error shows Graph's own answer, for example Graph replied 403 ErrorAccessDenied when the application may not send as the mailbox. Attachments are not supported by this transport; the application does not send any.
Secrets are write-only. Leaving a secret field blank on an existing mailer keeps the stored value.
Every save notifies the other administrators by email, through the mailer that was active before the change, because whoever controls the mail server receives every password reset link.
The tab shows delivery health for every configured mailer: the last attempt, its outcome and whether failover reached it, or "never attempted". Testing the saved values refreshes it. The same rows are printed by send_test_email.
a complete fullchain (leaf + intermediates), served in order by the SMTP server
The OAuth token request and the Microsoft Graph transport go through httpx, which reads the same variable. One bundle covers all of them, including an intercepting proxy's CA.
python manage.py send_test_email you@example.com[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed:
Missing Authority Key Identifier (_ssl.c:1081)openssl x509 -in cert.pem -noout -text | grep -A1 "Authority Key Identifier\|Basic Constraints\|Key Usage"services:
backend:
image: ghcr.io/intuitem/ciso-assistant-community/backend:3.16.1
environment:
- EMAIL_HOST=smtp.example.com
- EMAIL_PORT=465
- EMAIL_USE_SSL=True
- EMAIL_USE_TLS=False
- SSL_CERT_FILE=/certs/ca-bundle.pem
volumes:
- ./ca-bundle.pem:/certs/ca-bundle.pem:ropython -c "import smtplib, ssl; \
ctx = ssl.create_default_context(); \
s = smtplib.SMTP_SSL('smtp.example.com', 465, context=ctx); \
print(s.noop()); s.quit()"services:
backend:
image: ghcr.io/intuitem/ciso-assistant-community/backend:<old-tag>
environment:
- EMAIL_HOST=smtp.example.com
- EMAIL_PORT=465
- EMAIL_USE_SSL=True
- SSL_CERT_FILE=/etc/ssl/certs/smtp.example.com.pem
volumes:
- ./smtp-fullchain.crt:/etc/ssl/certs/smtp.example.com.pem:ro
command: |
sh -c 'update-ca-certificates && <original entrypoint>'EMAIL_TRANSPORT=smtp-oauth2
EMAIL_HOST=smtp.office365.com
EMAIL_PORT=587
EMAIL_USE_TLS=True
EMAIL_HOST_USER=noreply@example.com
DEFAULT_FROM_EMAIL=noreply@example.com
EMAIL_OAUTH2_GRANT_TYPE=client_credentials
EMAIL_OAUTH2_TOKEN_URL=https://login.microsoftonline.com/<tenant id>/oauth2/v2.0/token
EMAIL_OAUTH2_SCOPE=https://outlook.office365.com/.default
EMAIL_OAUTH2_CLIENT_ID=<application (client) id>
EMAIL_OAUTH2_CLIENT_SECRET=<client secret>EMAIL_TRANSPORT=smtp-oauth2
EMAIL_HOST=smtp.gmail.com
EMAIL_PORT=587
EMAIL_USE_TLS=True
EMAIL_HOST_USER=noreply@example.com
DEFAULT_FROM_EMAIL=noreply@example.com
EMAIL_OAUTH2_GRANT_TYPE=jwt_bearer
EMAIL_OAUTH2_TOKEN_URL=https://oauth2.googleapis.com/token
EMAIL_OAUTH2_SCOPE=https://mail.google.com/
EMAIL_OAUTH2_SERVICE_ACCOUNT_FILE=/run/secrets/mailer-service-account.jsonEMAIL_TRANSPORT=graph
EMAIL_GRAPH_TENANT_ID=<tenant id or verified domain>
EMAIL_HOST_USER=noreply@example.com
DEFAULT_FROM_EMAIL=noreply@example.com
EMAIL_OAUTH2_CLIENT_ID=<application (client) id>
EMAIL_OAUTH2_CLIENT_SECRET=<client secret>New-ServicePrincipal -AppId <client id> -ObjectId <enterprise application object id> -DisplayName "CISO Assistant mail"
New-ManagementScope -Name "CISO Assistant mailbox" -RecipientRestrictionFilter "PrimarySmtpAddress -eq 'noreply@example.com'"
New-ManagementRoleAssignment -App <client id> -Role "Application Mail.Send" -CustomResourceScope "CISO Assistant mailbox"CISO Assistant exposes a /metrics endpoint in the Prometheus exposition format. This page explains how to enable it and how to access it safely from Prometheus without exposing it publicly.
When enabled, the backend exposes a /metrics endpoint that returns instance-level gauges in the Prometheus exposition format:
User and editor counts
Number of loaded libraries, domains, perimeters, assets, threats
Number of compliance assessments, risk assessments, risk scenarios, risk acceptances
Number of applied controls and evidences
License expiration date, instance creation date, last login date
Build information (version, commit, schema)
Set the following environment variable on the backend container:
The endpoint is disabled by default (EXPOSE_METRICS=False).
If your prometheus runs in local you can for example check the /metrics endpoint with this config
If you have multiple instances on different machines, add them to targets:
Start Prometheus with:
The Prometheus UI is available at http://localhost:9090. Search for ciso_assistant_nb_users to confirm that scraping is working.
From a machine that has access to the backend (not from the public internet):
You should see output similar to:
If the endpoint returns a 404, check that EXPOSE_METRICS=True is set and that the backend has restarted.
EXPOSE_METRICS=Trueglobal:
scrape_interval: 30s
scrape_configs:
- job_name: ciso-assistant
static_configs:
- targets:
- localhost:8000
metrics_path: /metrics/ static_configs:
- targets:
- 192.168.1.10:8000
- 192.168.1.11:8000prometheus --config.file=prometheus.ymlcurl http://localhost:8000/metrics# HELP ciso_assistant_nb_users Number of users in the CISO Assistant instance
# TYPE ciso_assistant_nb_users gauge
ciso_assistant_nb_users 3.0
...You think it is time to change or try a new database on CISO Assistant? You were using SQLite and now want to switch to PostgreSQL, or the other way around? This guide is for you.
To stay safe, we strongly recommend creating a backup of the volume, disk, or db/ folder used to host your CISO Assistant instance.
We also recommend testing your backup procedure by deploying another CISO Assistant instance and restoring the backup into it. This will help verify that your backups work correctly before you start switching database engines.
Here is a complete guide of all the steps you should perform (after backing up your volume).
To facilitate the backup and restore workflow, we designed a backup-restore page with a button to back up the database.
Go in Extra > Backup & restore, then click on Export database. You will get a .bak file.
docker compose downYou can install whichever PostgreSQL setup you prefer, as you only need the environment variables afterwards to connect it to CISO Assistant. Here are the two main ways:
Depending on your OS, install PostgreSQL — here on Ubuntu:
sudo apt install postgresqlConfigure your PostgreSQL instance with at minimum:
Also check the port on which PostgreSQL is running (normally 5432).
In your docker-compose.yml, add a service like this:
Define these environment variables in your docker-compose.yml (or via export if running without containers):
If PostgreSQL is running on your host (not in a container), set DB_HOST=host.docker.internal and add the following to your backend service:
Bring the stack back up — if you are using Docker, migrations run automatically at startup:
Since the PostgreSQL database is brand new and empty, you also need to create a temporary superuser:
Connect with your new temporary superuser, go to the Backup & restore section, and click the restore button. Select the .bak file you downloaded in step 1.
Your database will be fully restored. Reconnect with one of your original users.
The .bak only contains database rows. The actual evidence files (PDFs, images, …) live on the filesystem under db/attachments/, independent of which database engine you use. Database rows reference them by path, so if the files aren't where the rows expect them, evidence records will appear to exist but their attachments will be broken.
You have two ways to keep them:
Option A — Same host, same volume (typical case). If you're switching the database engine in place on the same machine and you kept the db/ volume mounted on the new stack, you don't need to do anything: db/attachments/ is already there and the restored records will resolve to the existing files.
Option B — Cross-host migration, or you want one atomic snapshot. Use the clica CLI shipped under instead of the UI export. It packages the database and every attachment in a single backup, then restores both in one atomic call:
Requires a Personal Access Token with the backup permission set in .clica.env. The CLI verifies SHA-256 hashes and is resumable. See cli/README.md for setup details.
The logic is the same in reverse:
Export the database from the Extra > Backup & restore page on your PostgreSQL-backed instance (or run clica backup-full if you want attachments included).
Stop the stack (docker compose down).
Remove the PostgreSQL environment variables (POSTGRES_NAME, POSTGRES_USER, POSTGRES_PASSWORD
The same version-match rule applies: the SQLite-backed instance must run the exact same CISO Assistant version as the PostgreSQL-backed source.
DB_HOSTDB_PORTdbdocker-compose.ymlPOSTGRES_NAMEdb/ciso-assistant.sqlite3Bring the stack back up, create a temporary superuser, and restore the .bak file (or run clica restore-full).
Keep db/attachments/ in place — the same caveat from step 7 above applies.
POSTGRES_DB=ciso-assistant
POSTGRES_USER=ciso-assistantuser
POSTGRES_PASSWORD=<choose a password>db:
container_name: db
image: postgres:16
restart: always
environment:
- POSTGRES_DB=ciso-assistant
- POSTGRES_USER=ciso-assistantuser
- POSTGRES_PASSWORD=<your pg password>
volumes:
- ./pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ciso-assistantuser -d ciso-assistant"]
interval: 10s
timeout: 5s
retries: 10
start_period: 50sPOSTGRES_NAME=ciso-assistant
POSTGRES_USER=ciso-assistantuser
POSTGRES_PASSWORD=<your pg password>
DB_HOST=db
DB_PORT=5432extra_hosts:
- "host.docker.internal:host-gateway"docker compose up -ddocker exec -it backend uv run python manage.py createsuperuser# On the source instance — produces backup.json.gz + attachments/ + manifest
uv run clica.py backup-full --dest-dir ./db_backup
# On the new (PostgreSQL-backed) instance, after step 5
uv run clica.py restore-full --src-dir ./db_backupCISO Assistant can emit its operational logs as line-delimited JSON so a SIEM (Microsoft Sentinel, Azure Data Explorer, Splunk, Elastic) can ingest them without custom parsing. This page explains how
The three runtime processes each write their operational logs to standard output, where your container runtime or log shipper collects them:
Backend (Django API)
Worker (the Huey background task process)
Frontend (the SvelteKit server-side process)
By default the output is a human-readable, colourised format meant for reading in a terminal. That format is convenient locally but awkward to parse in a log pipeline. Setting a single environment variable switches every stream to one JSON object per line (newline-delimited JSON), which SIEMs parse natively.
Set the following on the backend, worker, and frontend containers:
The backend and the Huey worker share the same logging configuration, so this one variable covers both. Setting it on the frontend container routes the SvelteKit server process — authentication events, request errors — through the same JSON shape.
The log level also applies to all three streams and defaults to INFO, which captures request-level events. Adjust it if you need more or less detail:
A backend request log line (formatted here for readability — on the wire it is a single line):
A frontend authentication event:
Sensitive OAuth2 query parameters (code, token, id_token, access_token) are redacted from logged request URLs.
In addition to standard output, the backend can mirror its logs to a file. Set LOG_OUTFILE to the destination path; the file is always written in JSON, regardless of LOG_FORMAT. Leave it empty (the default) to log to stdout only.
This is useful when a log shipper tails a file rather than the container's stdout.
Once LOG_FORMAT=json is set, point your collector at each container's stdout (or the file above). Because the lines are newline-delimited JSON, Microsoft Sentinel, Azure Data Explorer, Splunk and Elastic all parse them without a custom grok/regex parser — each JSON key becomes a queryable field. Use request_id to correlate all backend lines belonging to a single request, and logger to separate the frontend stream from the backend stream.
LOG_FORMAT=json # default: plainLOG_LEVEL=INFO # default: INFO. Options: DEBUG, INFO, WARNING, ERROR, CRITICAL{
"request": "GET /api/folders/",
"code": 200,
"request_id": "0d6f1c4e-2a8b-4e1f-9c3a-7b2e5f0a1d44",
"user_id": 1,
"ip": "10.0.2.15",
"ciso_assistant_url": "https://ciso.example.com",
"event": "request_finished",
"timestamp": "2026-06-12T09:21:33.123456Z",
"level": "info",
"logger": "django_structlog.middlewares.request"
}{
"timestamp": "2026-06-12T09:21:34.001Z",
"level": "warning",
"logger": "frontend",
"event": "Login failed",
"status": 400
}timestamp
ISO-8601 UTC timestamp
level
debug, info, warning, error, critical
logger
Source logger (frontend for the SvelteKit process; a Python module path on the backend)
event
The log message
request_id
Correlation id shared by all backend log lines from one request
LOG_OUTFILE=/var/log/ciso-assistant/backend.loguser_id
Authenticated user id, when the request is authenticated
ip
Client IP address
Tips and tricks regarding specific cases
If you have selinux enabled on your distro, you might want to check if it's not preventing the mount volume of the docker compose; you can try something like this:
Audits produced before the semantic compute_result aggregation may have stored results that were collapsed under the older boolean logic (any non-empty compute_result was treated as truthy, so a "Non-compliant" choice could still aggregate to Compliant). This applies to audits backed by questionnaires from either path: the library builder or Excel-imported frameworks.
The recompute_assessment_results Django management command realigns stored results with the current rule.
Properties:
Scoped to compute_result-driven requirements only. The command targets requirement assessments whose requirement carries at least one question choice with a resolvable compute_result. Score-only or manual-entry audits are left alone, so a manually-set result is never silently reset to not_assessed.
Idempotent. Running twice is a no-op the second time; rows already aligned are reported as unchanged.
Per-batch commits by default. Lock duration stays bounded on large tenants. Use --atomic for all-or-nothing semantics on small datasets.
Touches score, result, is_scored, updated_at on the requirement assessments it rewrites. Other fields are left as-is.
Re-triggers CA-level hooks after writes: ComplianceAssessment.upsert_daily_metrics() and CEL outcome evaluation are explicitly called once per touched audit, because bulk_update bypasses the RequirementAssessment.save() signal chain. If any hook fails, the command exits with a CommandError listing the failures, so you don't silently end up with up-to-date results but stale metrics or CEL outcomes. Pass --skip-post-hooks to opt out when you handle those hooks separately.
If you run the application via Docker Compose, prefix with docker compose exec backend (or the equivalent for your deployment).
chcon -Rt svirt_sandbox_file_t ./db# Preview impact, no writes
uv run python manage.py recompute_assessment_results --dry-run
# Apply to all audits, one transaction per batch (default)
uv run python manage.py recompute_assessment_results
# Scope to a single audit
uv run python manage.py recompute_assessment_results \
--compliance-assessment <uuid>
# Tune batch size or wrap the full run in one transaction
uv run python manage.py recompute_assessment_results --batch-size 1000 --atomic
# Skip the post-run CA hooks (metrics + CEL outcomes), e.g. when chaining
# with another job that will recompute them
uv run python manage.py recompute_assessment_results --skip-post-hooksHow to update your local instance. All docker images are available on ghcr with the specific versions matching the repo tags. The latest tag points to the most recent release for both back and front.
The easiest way to update your on-prem/local instance (pro or community)
Run the script update-ciso-assistant:
./update-ciso-assistant.shIn case of issues (unsupported shell, windows, etc.) here are the steps to consider:
backup your db:
if you're using sqlite, copy the file under a different name
if it's postgresql you can use something like pg_dump
stop and clean the containers, this won't affect your data
docker compose rm -fs
restart the compose and let it handle the migration
docker compose up -d
Force remove the previous docker images to get the new ones
Third-party workspaces are consolidated on first boot
Instances that used the third-party module before the one-workspace-per-entity rule gave each assessment its own workspace. The first boot after upgrading merges them, so a vendor assessed several times ends up with a single folder named after it. Only folders move — links, exports and history keep working — but the merge shows up as a longer-than-usual first startup on large instances.
If an entity is skipped, replay it once the conflict is resolved:
Without --entity the command covers every third party; without --apply it changes nothing.
docker rmi ghcr.io/intuitem/ciso-assistant-community/backend:latest ghcr.io/intuitem/ciso-assistant-community/frontend:latest 2> /dev/nullpython manage.py consolidate_entity_workspaces --entity "Acme Corp" # print the plan
python manage.py consolidate_entity_workspaces --entity "Acme Corp" --apply # write itCISO Assistant can use memcached, an optional cache shared by all backend workers, to keep login rate limiting and other cached data consistent. This page explains how to enable it, how login rate lim
The backend runs several worker processes (3 by default). By default, each worker keeps its own private cache in memory. Anything stored in the cache is then only seen by the worker that wrote it:
Rate limits are counted per worker. With 3 workers, a limit of 5 failed logins becomes up to 15.
Changes can take time to reach every worker. For example, a feature flag changed in the settings may still show its old value on the other workers for up to 30 seconds.
Memcached gives all workers, and the background worker (Huey), one cache to share. It is optional and disabled by default: if you enable it, you run one more container and are responsible for operating it.
The cache is used for:
login rate limiting (see )
the rate limit on inbound workflow webhooks
feature flags
locks that stop two workers from running the same metrics computation at the same time
By default, the backend and Huey reach memcached through a unix socket, not over the network. Memcached has no authentication, and anything that can write to it can run code in the backend, because cached values are stored as Python pickles. With a socket, memcached has no network at all: only the containers that mount the socket can reach it, and no URL configured in CISO Assistant (webhook, SIEM, LLM...) can point at it. If several hosts or pods must share one memcached, see .
config/make_config.py asks whether to run memcached. Answer Yes and the generated compose file includes everything below. The other provided compose files do not include memcached.
To add it to your own compose file:
The memcached_socket volume is a small RAM disk that only holds the socket. It belongs to user 1001, the user the backend, Huey and memcached run as. Keep it that way: a plain named volume belongs to root, and memcached could not create its socket in it.
The -m 16 option gives memcached 16 MB of memory. The cache holds rate limit counters, feature flags, short-lived OAuth codes and copies of small API responses.
Set memcached.enabled: true to run memcached as an extra container in the backend pod. The backend, Huey and memcached share its socket through a memory-backed emptyDir volume:
Several backend hosts or pods can only share one memcached over the network. Run memcached as its own service, and set ENABLE_MEMCACHED=True and MEMCACHED_LOCATION to its address (for example memcached:11211) on the backend and Huey. With the Helm chart, leave memcached.enabled at false and set both variables in backend.env and backend.huey.env.
CISO Assistant limits login attempts to slow down password-guessing attacks. The limits apply to local (email and password) logins. By default:
Without memcached, each worker counts on its own, so the effective limits are higher (see ).
A successful login does not count as a failure. When a limit is reached, the login page shows:
Too many login attempts. Please try again later.
The block lifts by itself when the time window ends. To clear all limits at once, for example after a test, restart memcached, or the backend if you do not use memcached:
Set these variables on the backend container:
A value is a comma-separated list of rules written amount/period/per:
amount is the number of attempts allowed in the period.
period is s, m, h or d, optionally preceded by a number: 5m means 5 minutes.
An empty value turns that limit off. If a value is malformed, the backend does not start, and its logs name the variable.
For example, to keep the per-IP limit but remove the per-email lockout, so that knowing an email is not enough to lock its account:
The account is then no longer protected against password guessing spread over many IP addresses. The same goes for second-factor codes, for anyone who already has the password: without a key rule they are limited only per IP address, and an empty value removes their limit entirely.
With the Helm chart, set the variables through backend.env:
The per-IP limits need to know each user's real IP address. With the default setup, every request passes through a reverse proxy (Caddy, Traefik, BunkerWeb...). If the IP address is not passed on correctly, all users appear to come from the proxy, and 10 wrong passwords from anyone block logins for everyone.
The real IP address is passed on in the X-Forwarded-For header:
The reverse proxy adds the client IP address to X-Forwarded-For. Caddy, Traefik and Apache do this by default.
The frontend reads the client IP address from that header. Set these variables on the frontend container:
With the Helm chart, ADDRESS_HEADER and XFF_DEPTH are set on the frontend only when ingress.enabled is true. Use frontend.config.xffDepth to change the number of proxies. If you use your own ingress instead, set both variables yourself through frontend.env.
The same IP address is recorded in the (remote_addr).
Every failed login is logged as Failed login attempt, with the email and the IP address, and recorded in the audit log as Login failed. Failed logins have their own cap in the audit log (AUDITLOG_MAX_RECORDS, like other entries), so a flood of them cannot push other entries out.
Logins refused by these limits are logged too, squashed so that an attack does not flood the logs: a Logins refused by rate limit warning with the IP address or the email and the number of refusals, at the 2nd, 4th, 8th... refusal and at least every 5 minutes while refusals go on. Counts restart 1 hour after the first refusal. A single refusal is not logged.
If you enabled memcached and it is stopped or stuck, the backend keeps working without it, but every login is refused with "Too many login attempts. Please try again later.", even with the correct password. Login rate limiting cannot count attempts without memcached, so it refuses them rather than letting them through. Users who are already logged in can keep working, except for actions that ask for their password or second factor again (such as managing MFA). Applications that sign in through CISO Assistant as an OAuth provider cannot complete their sign-in. The backend health check (/api/health/) still reports the backend as healthy.
While memcached is down, metric computations are skipped or postponed, and the rate limits of inbound webhooks and of the chat assistant are not applied.
Check that memcached is running and start it again if needed:
Logins work again within about 10 seconds of memcached being back, without restarting the backend.
The backend probably sees the same IP address for every user. Check the ip field in the backend logs:
If every line shows the same internal address (for example 172.x.x.x), check the : ADDRESS_HEADER and XFF_DEPTH on the frontend, and X-Forwarded-For on your proxy.
Until the setup is fixed, you can turn the per-IP limits off and keep only the per-email one: set LOGIN_FAILED_RATE_LIMIT=5/5m/key and leave LOGIN_RATE_LIMIT empty (see ).
Memcached is probably not in use, so each worker counts on its own. This is expected if you did not enable it. Otherwise, check that ENABLE_MEMCACHED=True is set on the backend container:
The Settings page in CISO Assistant is where instance-wide configuration lives — the dials and switches that affect everyone using the platform, regardless of which domain or perimeter they're in.
Settings are grouped into the following categories:
General settings — display preferences, default language, currency and daily rate, AI/LLM provider configuration, retention defaults, and a handful of behavioural toggles (self-validation, MFA enforcement, external-link warnings).
Feature flags — toggles that turn whole product areas on or off. Use these to tailor the UI to what your team actually needs and to keep advanced or experimental capabilities out of sight until you want them.
— the remediation deadlines (in days) that apply to vulnerabilities by severity, and the start date the clock runs from. Shown while the Vulnerability SLA feature flag is on.
— switches for the optional external feeds (KEV, EPSS, NVD enrichment) and the network timeout the platform uses when reaching them.
(PRO) — replace the default logo, favicon, and client name with your organisation's identity.
(PRO) — override the subject and body of system emails, and the .docx templates used for document exports.
Editing settings requires the change_globalsettings permission. In practice, this means a Domain Manager on the global domain or an equivalent custom role.
Settings on this page are global — they apply to the whole instance. Domain-level overrides aren't available at this layer; if you need per-domain behaviour, look at the relevant domain or perimeter's own settings.
per is ip, or key for the email (failed logins only). It defaults to ip.
ADDRESS_HEADER
x-forwarded-for
Header that holds the client IP address.
XFF_DEPTH
1
Number of reverse proxies in front of the frontend.
The frontend then passes the IP address on to the backend.
The backend uses the last entry of X-Forwarded-For, which is the one added by the proxy just in front of it.
ENABLE_MEMCACHED
False
Set to True on the backend and huey containers to use memcached.
MEMCACHED_LOCATION
unix:/run/memcached/memcached.sock
Where memcached listens: unix:/absolute/path or /absolute/path for a socket, host:port for TCP. Keep the default with the setup above. A relative socket path is read as a host name.
5 failed logins for the same email in 5 minutes
This account is locked for 5 minutes, even with the correct password. Other accounts are not affected.
10 failed logins from the same IP address in 1 minute
Logins from this IP address are blocked for the rest of the minute.
30 login attempts from the same IP address in 1 minute
Logins from this IP address are blocked for the rest of the minute. Successful attempts count too.
LOGIN_FAILED_RATE_LIMIT
10/m/ip,5/5m/key
Failed logins and wrong second-factor (MFA) codes, per IP address (ip) and per account (key)
LOGIN_RATE_LIMIT
30/m/ip
All login attempts, per IP address
services:
backend:
environment:
- ENABLE_MEMCACHED=True
depends_on:
memcached:
condition: service_started
volumes:
- memcached_socket:/run/memcached
huey:
environment:
- ENABLE_MEMCACHED=True
depends_on:
memcached:
condition: service_started
volumes:
- memcached_socket:/run/memcached
memcached:
image: memcached:1.6-alpine
restart: unless-stopped
command: ["memcached", "-s", "/run/memcached/memcached.sock", "-a", "0700", "-m", "16"]
network_mode: none
read_only: true
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
user: "1001:1001"
volumes:
- memcached_socket:/run/memcached
volumes:
memcached_socket:
driver_opts:
type: tmpfs
device: tmpfs
o: "uid=1001,gid=1001,mode=0700,size=64k"memcached:
enabled: true # sets ENABLE_MEMCACHED on the backend and huey containers
memoryLimit: 16 # in MBdocker compose restart memcachedservices:
backend:
environment:
- LOGIN_FAILED_RATE_LIMIT=10/m/ipbackend:
env:
- name: LOGIN_FAILED_RATE_LIMIT
value: "10/m/ip"docker compose ps memcached
docker compose up -d memcacheddocker compose logs backend | grep auth/logindocker compose exec backend env | grep MEMCACHEDdocker compose down
docker compose up -d
All services logs combined:
docker compose logs
Specific service:
docker compose logs backend
If you didn't get the prompt to create the first user, or lost the password but you still have access to the infra level, you can trigger the createsuperuser command to fix that.
In your compose file folder, try:
docker compose exec backend uv run python manage.py createsuperuser
Alternatively, in a docker environment:
docker ps -a | grep backend (this will get you the id of the Backend for CISO Assistant container, keep it for the next step)
docker exec -it <the_container_id> uv run python manage.py createsuperuser
and you should get a prompt now 😉
docker compose exec backend uv run python manage.py changepassword <user_email>
You'll get a prompt to change the password
In some rare cases, the migration of database schemas can take longer than expected or fail silently. First thing to check is the backend container logs:
Make sure you share these information if you're reporting an issue on Discord or the Support portal.
If you want to trigger the migration to make sure that all increments have been properly applied:
Since v3.18.0, the backend image uses instead of Poetry to manage Python dependencies, and Poetry is no longer installed. If your docker-compose.yml predates that release, the huey service (or any custom command) still calls poetry run and keeps restarting:
In your docker-compose.yml, replace poetry run with uv run in the entrypoint of the huey service:
then run docker compose up -d.
The same applies to any command you run by hand, for example:
most likely because the initialization took longer than expected. Make sure you provide the expected specs or tune the docker compose to give the app more time to finish the init phase.
The backend image now runs on a hardened base. curl is still installed for the time being, so an existing healthcheck keeps working, but it will be removed in a future release. If you maintain your own docker-compose.yml, or generated config/docker-compose-custom.yml with config/make_config.py before this change, replace the test line of the backend service's healthcheck in the file you actually start, and keep its other settings (interval, retries, start_period) as they are:
then run docker compose up -d (for a generated config, docker compose -f docker-compose-custom.yml up -d from config/). The check uses the Python standard library, which is always present in the image.
If you skip this and curl is gone from a later image, the healthcheck fails on every attempt and nothing else comes up, because frontend, caddy, huey and mcp all wait for the backend to be healthy:
You can confirm that case from the health log, which shows curl: not found:
Login rate limiting relies on your reverse proxy passing the client IP address. If you enabled the optional memcached container and it is stopped, every login is refused, even with the correct password, until it is back. Users who are already logged in can keep working, except for actions that ask for their password again:
If all users are blocked at the same time after a few wrong passwords, the backend probably sees the same IP address for everyone. See .
The recommended pattern for a first local setup is to go with ./docker-compose.sh ; In case you can't:
Run
wait for the init to finish and then trigger the first user creation manually:
By default, the BODY_SIZE_LIMIT environment variable is set to 50 MB in the frontend Dockerfile:
In order to upload larger files, this value must be increased. How to do so depends on you rmode of deployment. Here are relevant docs:
docker compose logs backenddocker compose exec backend uv run python manage.py migratehuey | /bin/sh: line 1: poetry: command not found
huey exited with code 127 (restarting)entrypoint:
- /bin/sh
- -c
- |
uv run python manage.py run_huey -w 2 --scheduler-interval 60docker compose exec backend uv run python manage.py migratehealthcheck:
test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://backend:8000/api/health/', timeout=5)"]dependency failed to start: container backend is unhealthydocker inspect --format '{{json .State.Health}}' backenddocker compose up -d memcacheddocker compose up -ddocker compose exec backend uv run python manage.py createsuperuser# frontend/Dockerfile
ENV BODY_SIZE_LIMIT=50000000Feature flags turn whole product areas on or off. They're how you tailor the navigation and the surface area to what your team actually uses, and how you keep experimental or specialised features out of sight until you want them.
Flags affect what's visible in the sidebar, what appears in CRUD pages, and which permissions are even relevant. They do not delete any underlying data — turning a flag off hides the feature; turning it back on restores the UI as it was.
The flags set here are the ceiling. Each user can hide modules they don't need from their own navigation, under My profile → Settings → Modules (see Personal module visibility). A user can't turn on anything that is off here: a flag you disable is shown to them greyed out as Disabled by your organization.
Only flags that gate a navigation area are offered to users, such as compliance, risk_management, tprm, privacy, incidents or xrays. Flags that change how data is displayed or behaves (inherent_risk, dora, focus_mode, comments, custom_fields, the integration and identity flags, …) stay instance-wide only, so every user reads the same data the same way.
A user's hidden modules are personal. They don't affect other users and aren't a substitute for IAM: hiding a module removes it from that user's navigation but doesn't change their permissions.
When you enable a flag, it appears for every user who hasn't hidden it themselves.
This page always shows and saves the organisation's values, even if you have hidden modules for yourself.
xrays — the X-rays inconsistency-detection page.
incidents — incident management.
tasks — the task-management module (one-off and recurring tasks).
control_plan — the consolidated control-plan view across applied controls.
risk_acceptances — the risk-acceptance workflow.
exceptions — tracking.
follow_up — and the standalone Findings list.
findings_from_requirements — adds a Findings tab on a requirement assessment, so a finding can be raised straight from an audit. The audit's findings are collected in a binder created on first use.
ebiosrm — the EBIOS RM module.
scoring_assistant — the OWASP-based scoring assistant.
vulnerabilities — vulnerability tracking.
vulnerability_sla — labelled Vulnerability SLA: the , which gives vulnerabilities a due date from their severity and shows their SLA status. Turned off, no detection or due date is filled in automatically, Refresh SLA due dates is greyed out, and the
compliance — compliance assessments (audits). Effectively master switch for the entire compliance pillar.
auditee_mode — the auditee surface external respondents land in.
campaigns — : bulk-orchestration of assessments, either across your own perimeters or across a set of third parties. PRO.
audit_tree_inheritance — combine an audit's results with parent-domain audits on the same framework. Reveals the Domain inheritance strategy
bia — business impact analyses.
tprm — third-party risk management.
contracts — contracts surface inside TPRM. Default off.
dora — the DORA-specific fields on entities, solutions, assets and contracts, plus the capabilities.
external_ratings — : record scores published by rating services (SecurityScorecard, Bitsight, CyberVadis, …) against your third parties. Default off.
security_advisories — the security advisories catalogue.
cwes — the CWE catalogue.
ttps — the (MITRE ATT&CK Enterprise, ICS and Mobile, ATLAS): tactics and techniques. Also required by threat models. Default off.
metrology — metric definitions, instances, and dashboards.
reports — the reports surface. Default off.
advanced_analytics — Per-audit dashboard (compliance by section, controls coverage, timeline, evidence coverage, threats, exceptions).
outgoing_webhooks — outgoing webhooks. Default off.
audit_log_forwarding — to an external SIEM over HTTP or Kafka. PRO. Default off.
jit_provisioning — : auto-create an account on a user's first SSO login. Also unlocks the IdP groups menu and the IdP groups column on the users table (see idp_groups below), so auto-provisioned users can inherit roles through IdP group mapping.
project_management — projects, accreditations, responsibility matrices. Default off.
journeys — preset journeys for bootstrapping new organisations or domains.
comments — comments on objects.
object_audit_trail — per-object button on detail pages, gated by the Can view object audit trails permission. PRO.
focus_mode — UI mode that filters the entire workspace to a single domain. PRO. Default off.
custom_portals — : the Manage portals admin surface and the public pages it publishes. Default off.
experimental — feature-gate for the experimental area. Use with caution.
Defaults marked Default off are off in fresh installs. Everything else defaults to on. Restart isn't required when a flag is toggled, but a hard refresh in the browser is.
workflows — the : automation on a schedule, an event or a webhook. Default off.
commitment_management — : the delivery date an owner promises on an applied control or a one-off task, and its renegotiation. Default off.
validation_flows — configurable approval workflows that mirror internal review or management-approval processes, attached to objects whose state changes warrant sign-off. Default off.
organisation_issues — context register: issues affecting the organisation.
organisation_objectives — context register: organisational objectives.
policy_documents — the dedicated Policies surface (a filtered view of applied controls).
quick_forms — labelled Forms and Requests: . Turns on Published forms, My requests and the Requests queue. Default off.
document_management — the standalone module: the reading catalogue, document list, and templates. Author or upload documents through a draft → published lifecycle, independent of policies.
quantitative_risk_studies — Monte-Carlo quantitative risk.
inherent_risk — surface inherent-risk columns alongside residual risk on assessments. Default off.
threat_modeling — : attack graphs built from TTP catalog techniques. Needs ttps on as well. Default off.
posture_assessments — : continuous measurement of assets against technical baselines (CIS Benchmarks, hardening guides) with recurring scan results. Default off.
privacy — the privacy register pillar (master switch).
personal_data — personal-data inventory inside the privacy register.
purposes — purposes register.
right_requests — data-subject right requests.
data_breaches — data-breach tracking.
dpias — labelled DPIAs: on the processing register, and the DPIAs page. Runs on quick forms, so turning it on also turns on quick_forms. Default off.
idp_groups — : the SCIM settings tab, and — same as jit_provisioning above — the IdP groups menu and the IdP groups column on the users table. PRO.
service_accounts — for machine-to-machine API access via OAuth2 client credentials. PRO.
notification_center — the in-app and the Notifications settings tab. Email notifications don't depend on it. Default on.
chat_mode — the in-product chat assistant. Default off; only visible when ENABLE_CHAT is set on the instance.
infra_config_management — the settings tab. Only visible when ENABLE_INFRA_CONFIG_MANAGEMENT is set on the instance.
terminologies — organisation-specific label overrides.
custom_fields — org-defined typed fields on objects (Projects, Assets, Applied controls); see . PRO. Default off.
CISO Assistant can optionally enrich its vulnerability and security-advisory catalogues by polling external threat-intelligence feeds. These switches control which feeds are active and how the platform reaches them.
All feeds are off by default — they make outbound network calls, so opt in deliberately.
KEV feed (kev_feed_enabled) — CISA's Known Exploited Vulnerabilities list. Tags vulnerabilities that are confirmed to be exploited in the wild so they can be prioritised.
EPSS feed (epss_feed_enabled) — FIRST's Exploit Prediction Scoring System. Attaches a probabilistic exploitation score to each CVE, useful for prioritisation alongside CVSS severity.
NVD enrichment (nvd_enrich_enabled) — pulls extra metadata from the (CWE mappings, affected configurations, references).
Network timeout (network_timeout) — seconds to wait before giving up on a feed call. Default 30, range 5-120. Tune up if you're behind a slow egress proxy; tune down if you want feed failures to surface quickly rather than block other work.
Enabling a feed doesn't backfill the entire history — feeds are consulted from the moment they're enabled. To enrich historical entries, look for a "refresh" action on the relevant catalog (depends on the feed).
Feed calls happen in background jobs (Huey workers), so toggling a feed doesn't block the request that saves the settings.
Outbound HTTPS access to the feed endpoints is required. The platform doesn't ship with mirrored data.
Control which IP addresses and CIDR ranges are allowed to reach the CISO Assistant backend. Administrators manage the allowlist from the settings UI, and the infrastructure layer picks it up automatic
The Allowed IPs whitelist lets administrators decide which IP addresses and CIDR ranges are permitted to reach the backend API. It is the simplest way to restrict API access — for example, to let a CI runner, an office network, or a machine running the MCP server talk to your instance while keeping everyone else out.
You manage the list from Settings → Infrastructure:
Type an IP address (e.g. 203.0.113.4) or a CIDR range (e.g. 198.51.100.0/24) and click Add.
Remove an entry with the ✕ next to it.
Click Save to apply your changes.
Both IPv4 and IPv6 are supported, up to 50 entries.
CISO Assistant SaaS — already enabled. The Infrastructure tab is available to administrators out of the box; just manage your allowed IPs.
On-premises — disabled by default. If you want this self-service allowlist, enable it by setting the following environment variable on the backend and restarting it:
When disabled, the Infrastructure tab is hidden and the API endpoint below is not registered.
Once configured, the allowlist is published on an /infra-config/ endpoint so your reverse proxy, firewall, or security group can read it and apply the rules automatically — instead of you editing infrastructure config by hand.
From a machine that can reach the backend (not from the public internet):
If the endpoint returns a 404, make sure ENABLE_INFRA_CONFIG_MANAGEMENT=True is set and that the backend has been restarted.
ENABLE_INFRA_CONFIG_MANAGEMENT=Truecurl http://localhost:8000/infra-config/{
"allowed_ips": ["203.0.113.4", "198.51.100.0/24"]
}
Instance-wide settings that don't belong to a more specific category. Grouped here by intent — the order in the UI may differ.
Security objective scale — the labels used for ratings of security objectives (e.g. C/I/A).
EBIOS radar configuration — the max radius and the green / yellow / red zone radii used to draw the EBIOS RM radar chart.
Aggregated scenario matrix — toggles the aggregated view of scenarios on the risk-matrix display.
This section is being expanded.
Material to come: organisation setup (users, groups, custom roles, teams), the IAM model, single sign-on (SAML, OIDC) with per-provider guides (Entra ID, Okta, Google Workspace, Keycloak), bringing content in through and the , and changing the interface language.
reverse_proxy /infra-config/* backend:8000
reverse_proxy /api/* backend:8000
reverse_proxy /* frontend:3000Risk matrix axis options — swap axes, flip vertical, custom axis labels. Cosmetic adjustments to how every risk matrix is rendered.
Display qualifications as risk categories — swaps the term qualification for risk category throughout the interface: field labels on risk scenarios, incidents, feared events, escalation thresholds and quantitative risk scenarios, plus the matching filters and analytics charts. The values themselves (confidentiality, integrity, …) are managed as terminology either way.
Default language — fallback locale for users who haven't picked a preference and for system-generated emails. Must be one of the languages enabled in the instance build.
Report time zone: the time zone used for the times printed in reports and exports, UTC by default. See Report time zone.
Currency — the unit used when displaying applied-control costs and quantitative-risk amounts.
Daily rate — default daily cost used when expressing effort in monetary terms.
Conversion rate — write-only field used when changing the currency to convert existing cost amounts in one operation.
When you change the currency with a conversion rate, the platform sweeps every applied control's cost structure and applies the conversion. Without a conversion rate it just relabels — the numbers stay the same.
Allow self-validation — whether a user can validate workflows they themselves created. Off by default for separation-of-duty reasons.
Show warning on external links — interstitial prompt before opening links that leave the platform.
Enforce MFA — make multi-factor authentication mandatory for every user account.
Allow assignments to entities — whether requirements and tasks can be assigned to third-party entities (not just internal users).
Mapping max depth — how many frameworks a chained mapping path may span when projecting one framework onto another, counted as nodes in the path (2–5, default 3). A value of 3 allows one pivot — A → B → C. See .
Built-in metrics retention (days) — how long built-in metric samples are kept before being aged out. Minimum 1.
Enable email notifications — master switch for outbound email notifications. It doesn't affect the in-app notification inbox. See also Setting up mailer.
These settings drive the optional AI features (chat mode, agentic workflows, RAG over the knowledge base):
LLM provider — which model server the platform calls: Ollama, or OpenAI-compatible (hosted gateway or local server) for any server speaking the OpenAI chat completions API, hosted or local.
Ollama base URL, Ollama model, Ollama embedding model — used when the provider is Ollama.
API base URL, Model name, API key — used when the provider is OpenAI-compatible. The key is write-only and never returned by GET.
Embedding backend — which backend powers semantic search over knowledge. Local sentence transformers or Ollama; hosted providers are not offered here, so selecting one for the LLM leaves embeddings where they are.
System prompt — system prompt prepended to chat-mode conversations.
Send temperature to the model and Temperature — whether to pass a sampling temperature, and its value. Some servers reject the parameter, which is why it can be switched off.
To connect a hosted service such as OVHcloud AI Endpoints or OpenRouter, see Hosted AI providers.
Default custom analytics dashboard — UUID of the dashboard shown by default on the analytics page.
Score scale — the organisation default scale, proposed on the audit form for frameworks without a scale of their own. Pick a preset (0–100, 0–5, 1–5, 1–4, 0–3) or Custom to set your own Minimum score and Maximum score (whole numbers). A custom scale of up to 11 levels can name each level, one column per language (Add translation); the first column is shown to anyone whose language has no label. Wider scales are continuous and carry no labels. Defaults to 0–5. Changing it affects new audits only — existing audits keep their scale. See Choosing the score scale.
Show the documentation score first — where an audit shows both scores (requirement tree, requirement page and form, table mode, auditee view, Advanced Analytics), shows the documentation score before the implementation score, as tools such as the CCB CyFun self-assessment do. Off by default: implementation first.
Shown only when the Domain-tree audit inheritance feature flag is on (under the Audits group).
Domain inheritance strategy — when the same framework is audited at several levels of the domain tree, decides how a child audit combines results with its parent audits. Options: No inheritance (default), Parent always wins, Child always wins, Best case (optimistic), Worst case (prudent). Scores from a different scale are normalised to the top parent's scale. This one org-wide setting drives the Combined view on the Framework report and the inheritance panel in Advanced Analytics. Leaving it on No inheritance keeps the feature dormant even with the flag enabled.
The Vulnerability SLA policy sets, by severity, how many days a vulnerability may stay open. CISO Assistant uses it to give vulnerabilities a due date and to show, in the SLA column, how close each one is to that date.
It sits under Settings → Vulnerability SLA, and only administrators can change it. The tab is shown while the Vulnerability SLA feature flag is on (see Turning the SLA off).
The date the clock starts from:
Detection date (default) — when your organisation discovered the vulnerability. It is set to the creation day unless you enter another one, and you can change it on the vulnerability.
Published date — when the advisory was publicly disclosed. Useful when you work from CVE feeds: deadlines are tighter for vulnerabilities that were public before you recorded them. A vulnerability without a published date falls back to its detection date.
For each severity (Critical, High, Medium, Low, Information), set the number of days allowed for remediation. Leave a field empty and vulnerabilities of that severity get no due date. Vulnerabilities with an undefined severity never get one.
New instances start with 15, 30, 90, 180 and 365 days. Reset to defaults puts these values back, with Detection date as the start date.
A vulnerability created without a due date gets one: its start date plus the days for its severity.
When the severity of a vulnerability without a due date changes, the same rule gives it one. A due date that is already set, whether entered by hand, imported or computed earlier, is kept.
Changing the policy doesn't touch existing due dates. To apply it to existing vulnerabilities, use Refresh SLA due dates on the vulnerabilities list. It recomputes the due date of every vulnerability you can see and overwrites the existing ones, so it asks you to confirm first. Vulnerabilities without a detection date first get their creation date as detection date; severities without a number of days are left as they are.
The SLA column of vulnerability tables, and the SLA badge at the top of a vulnerability's page, show:
A vulnerability without a due date shows no status, unless it is resolved or excepted. The status is worked out each time it is displayed, so it moves on its own as days pass, and a due date entered by hand counts like a computed one.
While the clock runs, both also count the days: how many are left before the due date (On track, Caution) or how many it is late (Overdue), counted from today.
Vulnerability lists can be filtered by SLA status, and the SLA column sorts by urgency: Overdue, Today, Caution, On track, Excepted, then Resolved, with vulnerabilities that have no status last in either direction. Within a status, the earliest due date comes first: the most days late, or the fewest left.
A vulnerability also has a Remediation date, which you can enter at any time. If it's still empty when the status becomes resolved, it's set to that day. It's never cleared: reopening a vulnerability keeps it. A vulnerability's page shows its severity, its SLA status and a timeline of its dates (published, detected, ETA, due, remediated, with today's position while it is open), along with how long it has been open or how long its remediation took.
The Vulnerability SLA switches the whole mechanism. When it is off:
new vulnerabilities get neither a detection date nor a due date automatically, and changing a severity doesn't set one; dates you enter are kept;
Refresh SLA due dates is greyed out;
the SLA column and its filter, the SLA badge on a vulnerability's page and this settings tab are hidden.
The policy itself is kept: turning the flag back on restores it as it was. Existing due dates aren't removed and stay in the Due date column.
Resolved
the vulnerability is Mitigated, Fixed, Not exploitable or Unaffected
Excepted
an approved covers it and hasn't expired: it is off the clock until the exception ends
Overdue
the due date has passed
Today
the due date is today
Caution
less than half of the severity's SLA window is left before the due date
On track
otherwise











Branding settings let you replace the default CISO Assistant visuals with your organisation's identity — useful when the platform is deployed for end-customers, when a parent company hosts multiple subsidiaries, or simply to align the UI with internal design guidelines.
Go to Extra → Settings, then open the Instance tab. Editing these settings requires the permission to change global settings.
Client name — the display name used in headings and email signatures.
Logo — replaces the default product logo in the header. Accepted formats: .png, .jpeg, .jpg, .webp, .svg.
Favicon — replaces the browser-tab icon. Accepted formats: .ico, .png, .jpeg, .jpg, .webp, .svg.
Show images to unauthenticated users — when on (default), the logo and favicon are visible on the login screen and other pre-authentication pages. Turn off to keep branding gated behind authentication.
Logo and favicon are stored as files inside the instance, not as external URLs. Upload them through the Settings UI rather than editing the database directly.
The platform serves a content-hash header alongside each image so browser caches stay coherent when the file is replaced.
Custom templates let you replace the default content CISO Assistant uses for outbound emails and for document exports.
Three template types live under this setting:
Email templates — the body and subject of system-generated emails (notifications, invitations, password reset, …).
Word templates — .docx files used as the visual skin for document exports (audit reports, risk-treatment plans, BIA outputs).
Document layout templates — .html files that control the PDF layout of managed documents (cover page, header/footer, branding).
Every template is identified by two fields:
Template key — a stable identifier for what the template represents (e.g. audit_completion_notification, audit_report_export). The platform looks up the active template by key when the relevant action fires.
Language — the locale the template applies to. The platform falls back to the instance's default language if no template for the user's locale is present.
A template is only used when its is_active flag is on, which lets you stage a draft alongside the live version and flip the switch when ready.
Each email template carries a subject and a body. Body content supports the same templating variables as the default emails — use the in-app preview to inspect the variables available for a given key.
Whether each email is sent at all is set per notification type under Settings → Notifications; see . An email turned off there is still listed here, marked as disabled, so you can keep editing its wording.
Each Word template is an uploaded .docx file containing styled placeholders. The platform substitutes the dynamic content (assessment data, scores, evidence list) when generating the export.
Document layout templates control how a managed document is rendered to PDF — on Export PDF and when a document is published. Each is an uploaded .html file (Django template syntax, with CSS in a <style> block), registered under the document_pdf template key, per language.
When an active template exists for the document's language, the platform renders the document's content into your layout instead of the built-in one — letting you add a cover page, headers and footers, and your organisation's logo (from ). The template receives the document's title, rendered content, version, document_type, date, and the organisation logo.
Download the built-in default first (from the setting) to use as a starting point, then upload your customised version. The built-in layout is used as a fallback when no active template exists for a language, and also if a custom template contains a disallowed tag or fails to render — a broken template never blocks a document from being published or exported.
Editing a template doesn't retroactively change documents that have already been exported.
Inactive templates remain in the database for audit-trail purposes.
Malicious macros — .docx files can embed VBA macros that execute on the machine of whoever opens the exported document. A booby-trapped template effectively ships malware to every report recipient.
User groups are built-in objects giving permissions to all users inside of them, with a specific role across a scope.
Built-in user groups link a domain with a role, which carries a precise set of permissions. Every user in the group receives those permissions on that domain and its sub-domains. If the built-in roles are not granular enough, the PRO edition also lets you define custom roles.
There are 8 built-in roles, plus one more in the PRO edition (see Delegating user creation). The table below gives the high-level shape of each one — the underlying permission matrix has more than 200 entries, so this is deliberately a summary, not an exhaustive list.
Administrator
The Technical tester role is only useful once the posture_assessments feature flag is on (default off) — see . Its user group is created on every domain regardless.
Once your instance is created, five user groups are already present:
Global - Administrator
Global - Analyst
Global - Reader
Global - Approver
Global - Respondent
They give corresponding permissions on Global scope so on every object of your instance.
In the PRO edition, a sixth global group, Global - User creator, lets people other than administrators create users. Its User creator role can do only two things: create users and see the list of users. It cannot put users in groups (that takes group-management rights, such as a domain manager has on their own domain's groups), and it cannot edit, deactivate or delete users; those stay with administrators.
Users always belong to the Global domain, so the role only exists in this Global group and is not granted on sub-domains. Like any role with a create permission, it makes its holder a .
A domain manager who also belongs to Global - User creator can onboard someone end to end: create the user from the Users page, then add them to one of their domain's groups with Add members (see ). The same role can also be given to a that provisions users, on the Global domain.
They are created for each domain you add. For example, if you create a domain R&D, there will be:
R&D - Domain Manager
R&D - Analyst
R&D - Reader
R&D - Approver
R&D - Respondent
They give corresponding permissions on the domain scope so on every object inside R&D.
The Third-party respondent group is the exception: it is not created upfront on domains, but on the of an entity, when you invite that third party's contacts to answer a questionnaire. There is one workspace — and therefore one respondent group — per third party per domain, so a vendor assessed several times keeps the same access across rounds.
You can manage the members of a user group directly from the group's detail page. This is available to administrators, and to domain managers for the groups of their own domains: membership is governed by the change user group permission on the group's domain, so a domain manager can add or remove members without needing global user-management rights.
Open the user group and click Add members. A picker opens listing the users that are not yet in the group:
type in the search field to filter by email, first name or last name, or switch to Browse for a table view with per-column filters;
tick Include inactive to also list deactivated users;
your selection is kept while you search and change pages, and is summarised at the bottom of the picker;
click Add members to confirm.
On the group's Users tab, tick the members to remove, then click Remove from group:
How the CISO Assistant API paginates list endpoints, and the PAGINATE_BY / PAGINATE_MAX environment variables that control the default page size and the maximum a client can request.
Every list endpoint of the API returns a paginated envelope:
{
"count": 1234,
"next": "/api/assets/?limit=5000&offset=5000",
"previous": null,
"results": ["..."]
}A single request never returns more than the configured maximum page size. To retrieve a complete collection, follow the next link (or increase offset) until next is null — count always holds the true total, so a response with fewer rows than count is a page, not the whole dataset.
The default page size can never exceed the ceiling: if PAGINATE_BY is set above PAGINATE_MAX, the effective page size is PAGINATE_MAX. Because the ceiling follows PAGINATE_BY upward, raising only PAGINATE_BY raises both.
limit must be a strictly positive integer and offset a non-negative integer — anything else (including limit=0, which never meant "no limit") is rejected with HTTP 400.
A limit above PAGINATE_MAX is silently clamped: the response then holds fewer rows than requested and a non-null next link.
A GET on a list or detail endpoint accepts fields, a comma-separated list of the columns to return:
This is worth using when a bulk reader needs one or two columns out of a wide row — building a link table between two objects, for instance, or exporting a single attribute for every record. Responses get smaller and the server does less work per row.
The parameter can only narrow a response, never widen one. A name the endpoint does not expose — a model column it does not publish, or a field belonging to a disabled feature — is rejected with HTTP 400.
Fields hidden from your role are still hidden: naming one is accepted, and the response simply does not contain it, exactly as without the parameter.
id is always included, even when it is not requested.
Names are the top-level keys of the response, not paths: request
Under Organization, click on Users and then Add user:
Set up the email of the new user:
Once created, a new user doesn't have any permissions by default. Click edit and update the user groups:
If you are working on a single domain, or working on solo, you might just set `Global - Administrator`
When the user are added, and if the mailer is set, he/she will receive an email to set up the password. If not, you can set a temporary password as illustrated above.
If a user has lost access to all their MFA factors (phone wiped, hardware key lost) and has no recovery codes left, an administrator can disable their MFA from the user's edit page. The link only appears when the target user has MFA enabled and you are not editing yourself. See Setting up Multi-Factor Authentication (MFA) → Admin recovery for the full procedure.




Never infer completeness from a single response — check next and count.
Sort order is deterministic: every ordering carries a unique tiebreaker, so paging through a collection neither skips nor duplicates rows. Rows created or deleted while you page can still shift across page boundaries.
folderfolder.nameEverything else is unchanged — permissions, pagination, filters and ordering behave exactly as they do without the parameter.
PAGINATE_BY
5000
Page size applied when a request passes no limit parameter.
PAGINATE_MAX
max(5000, PAGINATE_BY)
Hard ceiling for the limit parameter. Larger values are clamped, never served.
GET /api/applied-controls/?fields=id,name,status
GET /api/risk-scenarios/?fields=id,applied_controlsR&D - Technical tester
Everything, on the whole instance: all business objects, plus domains, users, user groups, roles, libraries, instance settings and backups. Also approves risk acceptances.
— this is the only role that can change instance-wide configuration.
Domain manager
Everything an Analyst can, on its own domains, plus creating sub-domains and managing the user groups of those domains. Read access to global objects.
Approve risk acceptances. Manage users, roles, libraries or instance settings.
Analyst
Create, update and delete the operational objects of its domains — audits, risk assessments, applied controls, assets, evidences, tasks, findings… Read global and domain objects.
Touch access control (domains, user groups, roles, users). Approve risk acceptances.
Reader
Read everything in its domains: assessments, controls, assets, third-party module, dashboards and metrics.
Create, update or delete anything.
Approver
Read the objects of its domains, and approve or reject risk acceptances and validation flow steps.
Create or update business objects. Its read scope is slightly narrower than Reader — no third-party module, dashboards or metrics.
Respondent
Answer the requirements assigned to it: set compliance results, attach or create applied controls and evidences, comment. See .
Create audits or assessments. Reach the risk, governance or administration modules.
Third-party respondent
The same answering capability as a Respondent, restricted to the questionnaire of the entity assessment the user was invited to. See .
See or do anything outside that questionnaire. Create or update applied controls.
Technical tester
Create and run and their results, and manage the findings binders and findings that follow up on them.
Everything else — read-only on assets, frameworks and perimeters, no access to audits or risk assessments.


Deep dive into CISO Asisstant IAM model
Access security is a foundational aspect of any risk or compliance management platform. In this article, we’ll explore how authentication, authorization, and accounting — the three pillars of the AAA model — are structured and applied within CISO Assistant.
CISO Assistant integrates with leading identity providers (IdPs) via SAML and OIDC, enabling secure and seamless single sign-on (SSO).
legacy protocol based on XML
common in large enterprises, especially for AD
browser-based redirection with signed assertions
modern standard built on OAuth2
uses JWT tokens for identity transport
also browser-based, but more lightweight and versatile
Recommendation: If your IdP supports both, prefer OIDC — it's more modern, flexible, and aligned with today’s security practices.
Multi-Factor Authentication (MFA) is critical for access protection. CISO Assistant supports MFA in two distinct modes, depending on how users authenticate:
MFA is handled entirely by the identity provider (IdP)
the IdP enforces the policy (push notifications, TOTP, biometrics, etc.)
for local accounts, CISO Assistant includes native MFA
based on TOTP (e.g., Google/Microsoft Authenticators) and WebAuthn / passkeys (hardware security keys, platform authenticators)
recovery codes are issued at enrolment so the user can self-recover from a lost factor
if a user loses every factor and runs out of recovery codes, an administrator can disable their MFA from the user's edit page — see . The link is hidden on your own edit page; to disable your own MFA, use the standard MFA settings page on My profile → Settings.
CISO Assistant implements a robust Role-Based Access Control (RBAC) model that balances flexibility, clarity, and operational simplicity.
Each object type has granular CRUD permissions (create, read, update, delete). This model applies across all business entities: users, backups, risks, policies, incidents, data processing, and more. There are more than 200 permissions in CISO Assistant.
Where some details of an object are only for some viewers, View gives the base view and a separate permission adds the rest. Compliance assessments have one: View gives the respondent view, and See all requirements (auditor view) adds the auditor view. Users have one too: View lists users with what pickers and owner fields need (name, email, visible groups), and View full user record adds their sign-in and administration details (superuser flag, MFA status, expiry date, local login, provisioning, observation). Administrator and Reader hold the full record; the other roles that see users hold View only. Users live in the Global domain, so only assignments on Global make the difference: a Global reader sees the full record, a reader on a domain doesn't. Change on users, assigned on Global, also shows the full record. Everyone sees their own full record. A user's audit trail follows the same rule. The Users page is listed for holders of the full record on Global, and for those who can change or create users there. Third-party users are listed only to those who can see one of their representatives or the full user record, and to themselves.
Permissions are grouped into a small set of standard roles — Administrator, Domain manager, Analyst, Reader, Approver, Respondent, Third-party respondent and Technical tester, plus User creator in the PRO edition.
See for what each of them can and cannot do.
Roles are assigned within a domain — a flexible concept representing any relevant business context.
For example, a domain can represent:
a legal entity
a country or region
a subsidiary
a business unit
Domains are hierarchical: a role assigned to a parent domain (e.g., "Group") automatically applies to all its subdomains (e.g., subsidiaries, teams).
Access control is defined via explicit assignments:
A role ➡️ on a domain ➡️ for a group of users
Users do not have direct roles. They inherit permissions through membership in one or more groups.
Groups act as the central pivot for managing access:
receive role assignments
grant users permissions via group membership
defined locally
optionally synced with an IdP (via external plugin)
🚀 This simple yet powerful model accommodates the vast majority of real-world access scenarios. And when needed, the system is fully extensible: it supports custom roles, custom role assignments, and custom user groups to fit even the most specific organizational needs.
CISO Assistant doesn’t just secure human access — it also supports secure, auditable access for automated systems and integrations through Personal Access Tokens (PATs).
A Personal Access Token is a time-limited secret that allows a script, CI/CD pipeline, or service to authenticate with the platform's API on behalf of a user or machine identity — without requiring an interactive login.
time-bounded: expiration is mandatory
RBAC-compliant: inherits the creator’s permissions
revocable: can be revoked by user or admin
admins can restrict who may generate PATs
all tokens are auditable and managed via UI or API
This ensures tight control over non-human access, balancing automation flexibility with strict security hygiene.
The following schematic illustrates the fundamental concepts of IAM in CISO Assistant.
Every domain has members: the people its own and its sub-domains' IAM groups grant roles to. A domain can carry a default role — the role it grants its members, on the domain itself: never recursively, and only there.
For example: if the Domain domain has the Baseline reader default role, then a user granted any role on Domain itself or on its sub-domain ChildDomain through the IAM groups is a member of Domain, and receives Baseline reader on it.
Who is not a member follows from the definition — no exception list to remember:
third parties hold their grants inside third-party workspaces, which are not the domain's groups → never members;
service accounts hold direct role assignments — no group grants them anything, so they read exactly what their own assignment names (least privilege by design);
a grant made outside the IAM groups (a direct assignment) gives exactly what it names, nothing ambient;
deactivated users cannot sign in and therefore exercise no permission at all.
Only roles containing view permissions can be used as a default role, and enclave domains cannot carry one.
Configuring default roles is an enterprise capability: the domain form exposes the control there, and it can be tuned per domain — set to a narrower view-only role, or cleared entirely for domains that should share nothing ambiently. Clearing the root domain's default role gives the instance an explicit-grant policy: no ambient visibility at all, every access traceable to an assigned role. If the root domain carries a custom default role, leave View full user record out of it, or every member sees every user's full record. In the community edition, the default role exists only on the root domain, fixed to Baseline reader, and cannot be changed.
Yes — the shelf pattern: attach the object to a sub-domain (e.g. named "shared"), and add the intended readers to that sub-domain's reader group. This uses ordinary role assignments only, works for any object type, and the audience is exactly the group's member list — explicit and auditable.
CISO Assistant includes native tracking of all key actions:
logins, restorations, configuration changes, approvals…
a searchable audit log accessible via the UI or API
This enables complete accountability over critical operations.
CISO Assistant's AAA model is built on:
Open standards (SAML, OIDC, TOTP, RBAC)
A structured yet manageable authorization system
secure automation through scoped, revocable Personal Access Tokens (PATs)
Built-in traceability from the ground up
It supports complex organizations while remaining readable, scalable, and compliant with modern security expectations.

You can find here CISO Assistant global organization. All entities will be linked to or contained within these objects.
For Access Control purpose, CISO Assistant data is organized in a tree of folders. Starting from a root folder called Global, it divide into sub-folders called domains. The organization of the tree is not hard-coded, it is entirely determined by configuration. Any object in CISO Assistant is attached to a folder (including folders), either directly or indirectly through a parent object that is attached to a folder.
A domain permits to organize your work depending on your use of CISO Assistant. For example, inside a company, you can create a domain for each department for which you need to carry out a variety of perimeters, or if you have different customers, you may as well have a domain for each one in order to delimit your work area.
A domain is the first thing you create on CISO Assistant. It will bring together all objects you need to complete your different perimeters. Every role/permission a user has on a domain are applicable to all objects/actions across the domain. It's all about organization, the only technical aspect is access control, and this is achieved by adding the user to the relevant user group.
In the first/open source version of CISO Assistant, custom role assignment is not available. So, when you create a domain, user groups concerning this domain are automatically created for each built-in role. All you need to do, is to assign your users to their user groups. To learn more about this, jump to User groups.
Perimeters are fundamental context objects defined by the entity using CISO Assistant. They are grouped in domains. They will contain all your risk and audits. Apart from being able to group your various evaluations across the different domains.
There are two specific fields, internal reference and status. Here are the various status options:
-- (None)
Design
Development
Production
End of life
The purpose of a perimeter is at first, it's organizational aspect to solve a problem. But it also makes it possible to improve analytics by breaking them down according to the different assessments, whether for risk or compliance, so as to make your project management more precise and reduce noise.
User groups go hand in hand with domains. they associate permissions with users and define their scope, by being attached to a domain. They follow a simple and consistent RBAC model from a role containing permissions and a domain determining the perimeter. Go to the page for more details.
Dropped


Fine-grained permissions management
Custom roles allow you to refine access control when very specific permissions need to be granted to a user.
In CISO Assistant, domains are the primary mechanism for restricting access. Custom roles should only be created when domain-based restrictions are insufficient.
Keep in mind that custom roles increase IAM complexity and make access reviews more difficult. In most cases, the default roles are sufficient.
Learn how to create and customize roles such as DPO and OPS within your organization
This guide walks you through setting permissions and assigning user groups to streamline your CISO Assistant's role management.
Navigate to the Organization section to begin managing your team's roles.
Access the Roles tab to view and modify existing roles.
Initiate creating a new role by selecting the option to add one.
Enter the name for the new role, such as 'DPO', to define its identity.
Save the newly created role to confirm its addition to your organization.
Open the permissions settings for the new role to customize access.
Expand the permissions list to view all available options.
Select all permissions to grant comprehensive access to the role.
Save the permission settings to apply them to the role.
Start creating another role by selecting the add role option again.
Name this role, for example 'OPS', to specify its function.
Save the role to add it to your organization's role list.
Access the permissions for the newly created role to tailor its access.
Open the detailed permissions menu to adjust specific controls.
Select the option to edit applied controls for fine-tuning.
Modify the applied control by entering a specific control number, such as '205'.
Confirm the changes made to the applied control settings.
View the applied control details to verify the configuration.
Update the applied control with another control number, like '207', if needed.
Return to the permissions overview to continue adjustments.
Select all permissions to ensure full access for the role.
Save all permission changes to finalize the role's capabilities.
Switch to the User Groups section to manage group memberships.
Navigate to the Users tab to assign roles to individuals.
Select a user to modify their group memberships and roles.
Open the user's group assignment settings to begin editing.
Enter the role name, such as 'OPS', to assign it to the user.
Choose the corresponding user group, like 'ACME - OPS', for the role.
Input another role name, for example 'DPO', for additional assignments.
Select the matching user group, such as 'Global - DPO', for this role.
Review all assigned user groups and roles to ensure accuracy.
Save the user group and role assignments to complete the process.
This guide detailed how to create and configure custom roles like DPO and OPS, assign comprehensive permissions, and manage user group memberships effectively. It ensures your organization’s roles are tailored and users are properly assigned for optimal access control.
Configure Single Sign-On with different SAML or OpenID Connect providers
By default, SSO login is only usable by users who already have a CISO Assistant account. If the email returned by the identity provider does not match an existing user, login is rejected with "User not found."
Auto-provisioning (just-in-time, or JIT, provisioning) removes that requirement. When enabled, the first successful SSO login for an unknown email automatically creates the account instead of rejecting it, with no administrator action needed. The new account is created without a local password and is placed into whichever user groups you configure as defaults, so it starts with exactly the roles you intend for a new user, nothing more.
As with SCIM-provisioned users, an auto-provisioned account stays SSO-only for its whole lifetime (see below): it cannot request a password reset or log in locally, even if Force SSO Login is off, unless an administrator explicitly enables Keep local login on that account. This keeps the account's access tied to the identity provider: disable the user there, and they lose access here too, with no local password left behind to fall back on.
Turn it on from Settings > SSO:
Enable auto-provisioning: off by default, preserving today's behavior until you opt in.
Default user groups: the user group(s) automatically granted to accounts created this way. Pick the lowest-privilege group(s) that make sense for a brand-new user (e.g. a read-only or analyst group scoped to the right folder). You can always add more groups to a user by hand afterward.
Default user groups covers only the first login. For role assignment that keeps tracking the identity provider afterward — including on IdPs without SCIM — configure Groups attribute mapping in the SSO advanced settings so SAML or OIDC assertions carry the user's IdP groups directly, and map those groups under . If your identity provider supports SCIM, that same IdP group mapping applies there too, with group membership pushed by the IdP instead of read from the login assertion, and is the better fit for larger organizations.
By default, logging out of CISO Assistant only ends the local CISO Assistant session. The identity provider session stays open, so clicking Log in with SSO again signs the user straight back in without re-authenticating.
To also close the identity provider session on logout, enable Enable service provider-initiated single logout in the SSO settings. When it is on, logging out of CISO Assistant redirects the browser through the identity provider's logout endpoint.
This is service provider-initiated single logout: CISO Assistant asks the identity provider to end its own session for the user. Whether that in turn signs the user out of other applications federated to the same identity provider depends on the identity provider's single-logout configuration and is not something CISO Assistant controls or can guarantee.
The option is off by default, and each protocol needs a logout endpoint on the identity provider side:
OIDC — the provider must expose an end_session_endpoint, and <frontend_url>/login must be registered as an allowed post-logout redirect URI. See .
SAML — the identity provider Single Logout Service URL must be available (read from the metadata, or set in the SLO URL field). See .
Enabling SSO adds the Log in with SSO button but leaves the email/password form in place, so users can still authenticate locally. To make SSO the only way in, turn on Force SSO Login in the SSO settings.
When Force SSO Login is enabled, local password authentication is disabled for everyone — a user who tries the password form is rejected with "This user is not allowed to use local login."
To keep a few accounts able to log in locally (typically break-glass administrators, or a service account used while the identity provider is being set up), enable the per-user Keep local login flag on their user record. These accounts continue to work through the standard password form even while SSO is forced.
Some accounts get Keep local login enabled by default when they are created:
Superusers created with createsuperuser (or at first boot), so the initial administrator is not locked out.
Third-party users (portal / TPRM accounts).
Note that this is only a default on the flag, not a permanent exemption: a regular user promoted to superuser afterwards does not get it automatically, and unticking Keep local login on any of these accounts removes their local access like anyone else.
SCIM-provisioned and auto-provisioned (JIT) users, by contrast, are SSO-only by design, regardless of the Force SSO Login setting. Their lifecycle is meant to be governed entirely by the identity provider, so they don't get a local password fallback unless you explicitly grant them Keep local login.
By default the login page shows the standard email/password form alongside a Log in with SSO button. You can send users straight to your identity provider — skipping the form — by appending ?sso to the login URL:
Opening that link starts the SSO redirect immediately, exactly as if the user had clicked Log in with SSO. It's convenient as a bookmark, or as the link you publish internally when SSO is the expected way in.
To send the user to a specific page after they authenticate, add a next parameter:
https://<your-instance>/login?ssohttps://<your-instance>/login?sso&next=/analytics
Don't find documentation on how to set up SSO with your identity provider? Feel free to reach out to us on , or .
Teams in CISO Assistant are used to group users who work together on the same security activities, such as risk assessments, compliance programs, or audits.
Each team has a Team Leader, optional Deputies, and Members, making it easy to reflect real-world responsibilities and delegation. A team can also have a dedicated team email address for shared communications. Teams centralize ownership, collaboration, and notifications by allowing CISO Assistant to automatically reach the right people based on their role within the team.
Tasks, assessments, assets, applied controls, etc., can be assigned to teams, in the same way as to users.
Provision users from your identity provider with SCIM 2.0, and map external IdP groups to CISO Assistant user groups so that roles are granted automatically.
SCIM 2.0 lets your identity provider (Okta, Microsoft Entra ID, Keycloak, …) push users into CISO Assistant automatically, so you no longer have to create accounts by hand. IdP groups are the bridge between a group that exists in your identity provider and the CISO Assistant user groups it should grant.
Authentication and provisioning are two separate concerns, handled by complementary mechanisms:
Your IdP authenticates users through SAML or OpenID Connect (OIDC).
Your IdP provisions users and their group memberships, either through SCIM, or — for IdPs without SCIM, or for auto-provisioned users — by carrying the user's groups directly in the SAML/OIDC login assertion (see ). Both feed the same IdP group membership.
The link between the two worlds is the IdP group — a faithful mirror of a group that exists in your identity provider. An administrator maps each IdP group to one or more user groups. A user who belongs to an IdP group then inherits the roles of every user group it is mapped to.
This is a groups of groups model: membership flows from IdP group → user groups → roles, and the effective roles are recomputed on the fly. A user's direct (manually assigned) user groups and the ones inherited through their IdP groups are simply added together — neither overrides the other.
IdP groups live under Organization > IdP groups in the sidebar. The page behaves like any other object table — view, create, edit and delete.
Creating or editing an IdP group asks for two things:
Name — the display name of the group as it exists in your identity provider.
User groups — one or more CISO Assistant this IdP group should grant. Edit this list at any time; every member of the IdP group is re-granted immediately.
The members of an IdP group are synced automatically — by SCIM if it's configured, or by the group claim in the user's SAML/OIDC login assertion otherwise — and shown read-only. You will also find an IdP groups column on the Organization > Users table, listing the IdP groups each user belongs to.
Log in as an administrator > Extra > Settings and open the SCIM tab.
Copy the SCIM endpoint URL (for example https://<your-instance>/api/scim/v2/). This is the base URL your identity provider connects to.
Click Generate token and copy the bearer token that is displayed.
In your identity provider's provisioning settings, enter the SCIM endpoint URL and authenticate with the token using the
You can generate several tokens (one per integration) and revoke any of them at any time from the same screen.
Each IdP group is identified by the UUID that CISO Assistant assigns it. Your identity provider stores that id and reuses it, so renaming a group in the IdP simply updates its name — memberships and the user-group mapping are preserved.
The first time your IdP pushes a group that does not exist yet, CISO Assistant creates the IdP group automatically.
Deleting an IdP group (or removing it from the IdP) removes the access it granted, but never touches a user's manually assigned user groups — those are a separate, direct membership.
SCIM provisions accounts; it does not replace authentication. Provisioned users still sign in through your configured provider.
CISO Assistant implements the inbound SCIM 2.0 Users and Groups resources (RFC 7643/7644) and is identity-provider agnostic.
Authorization: Bearer <token>Assign users and groups to CISO Assistant in your identity provider, and let it provision them.





Once you've retrieved the Client ID, the Client Secret and the Issuer URL (sometimes called the discovery or .well-known URL) from your provider, the configuration on CISO Assistant is pretty simple.
Log in into CISO Assistant as an administrator > Extra > Settings
Navigate to SSO settings
Enable SSO
Select the OpenID Connect provider
Enter the Client ID
Enter the Client secret
Enter the Server URL
If your identity provider requires other OAuth scopes, open Advanced settings and enter them as a comma-separated list in Additional scopes. They are appended to the default openid, profile, and email scopes.
And that's it! Don't forget to click the 'Save' button
You should now be able to see the Login with SSO button
Turn on Enable service provider-initiated single logout in the SSO settings to close the OIDC session at the identity provider when users log out of CISO Assistant (see for the general behavior). It additionally requires:
an end_session_endpoint in the provider's OpenID configuration, and
<frontend_url>/login registered as an allowed post-logout redirect URI on the OIDC client.
CISO Assistant calls the end-session endpoint with client_id, id_token_hint, and that post-logout redirect URI.

















Once you've retrieved the IdP Entity ID, the Metadata URL and the Entity ID from your provider (see the list of providers for specific details), the configuration on CISO Assistant is pretty simple.
Log in into CISO Assistant as an administrator > Extra > Settings
Enable SSO
Enter the Idp Entity ID
Choose the option 1 or 2 depending of your provider and fill Metadata URL or SSO URL, SLO URL, x509 certificate retrieved from your provider
Check that the SP Entity ID is similar to the Entity/Client ID specified on your provider
And that's it! Don't forget to save changes
You should now be able to see the Login with SSO button
Allow single label domains: This allows you to authenticate through SAML on a single-label domain (e.g. https://ciso-assistant:8443). If this is left unchecked, the only host forms allowed are:
IPv4
IPv6
Turn on Enable service provider-initiated single logout in the SSO settings to close the SAML session at the identity provider when users log out of CISO Assistant (see for the general behavior).
It also needs the identity provider's Single Logout Service URL. When you configure the identity provider by Metadata URL, this is read automatically from the metadata — the SLO URL field is only used when you configure the identity provider manually.
Some identity providers require signed logout requests. In that case, enable Logout request signed in the advanced SAML settings and configure the SP private key and certificate — and register that SP certificate with the identity provider so it can verify the signature. Microsoft Entra ID, for example, rejects unsigned SAML logout requests.
localhost
Authn request signed: allows the Service Provider (SP) to digitally sign the SAML authentication request sent to the Identity Provider (IdP).
This option should be enabled if your IdP requires signed authentication requests or if you are looking to enforce additional security on SAML authentication flows:






























