diff --git a/components/package.json b/components/package.json index a4228b15cb4..245cce9263c 100644 --- a/components/package.json +++ b/components/package.json @@ -1,6 +1,6 @@ { "name": "defectdojo", - "version": "3.5.102", + "version": "3.5.103", "license": "BSD-3-Clause", "private": true, "dependencies": { diff --git a/docs/assets/images/Avoiding_Duplicates_Reimport_Recurring_Tests.png b/docs/assets/images/Avoiding_Duplicates_Reimport_Recurring_Tests.png index e0ac5e38074..9c7ee48297e 100644 Binary files a/docs/assets/images/Avoiding_Duplicates_Reimport_Recurring_Tests.png and b/docs/assets/images/Avoiding_Duplicates_Reimport_Recurring_Tests.png differ diff --git a/docs/assets/images/PRO_attach_files_menu.png b/docs/assets/images/PRO_attach_files_menu.png index c6c86317e30..a599eb2507d 100644 Binary files a/docs/assets/images/PRO_attach_files_menu.png and b/docs/assets/images/PRO_attach_files_menu.png differ diff --git a/docs/assets/images/PRO_finding_files_tab.png b/docs/assets/images/PRO_finding_files_tab.png index 8a00b05db60..8e60e99115f 100644 Binary files a/docs/assets/images/PRO_finding_files_tab.png and b/docs/assets/images/PRO_finding_files_tab.png differ diff --git a/docs/assets/images/pro_similar_findings.png b/docs/assets/images/pro_similar_findings.png index 086caf1b2bb..a29ea51221a 100644 Binary files a/docs/assets/images/pro_similar_findings.png and b/docs/assets/images/pro_similar_findings.png differ diff --git a/docs/assets/images/pro_similar_findings_actions.png b/docs/assets/images/pro_similar_findings_actions.png index 82490b92bb1..66bbd3aab79 100644 Binary files a/docs/assets/images/pro_similar_findings_actions.png and b/docs/assets/images/pro_similar_findings_actions.png differ diff --git a/docs/assets/images/using_reimport_3.png b/docs/assets/images/using_reimport_3.png index 93d5d5c0d0f..9c7ee48297e 100644 Binary files a/docs/assets/images/using_reimport_3.png and b/docs/assets/images/using_reimport_3.png differ diff --git a/docs/content/admin/feature_flags/PRO__feature_flags.md b/docs/content/admin/feature_flags/PRO__feature_flags.md index 1e064f2de1d..ecde6a3f391 100644 --- a/docs/content/admin/feature_flags/PRO__feature_flags.md +++ b/docs/content/admin/feature_flags/PRO__feature_flags.md @@ -140,7 +140,7 @@ Features under **MCP** appear on the Feature Flags page beneath the MCP server s | **Administrator MFA Reset** | Let superusers reset multi-factor authentication for another user from the Users page. The reset removes that user's authenticator and recovery codes and notifies them; they set MFA up again at their next sign-in if it is required. Turning this off removes the action. Users can still remove their own MFA from their profile. | On | [Docs](/admin/user_management/pro__mfa/#recovering-a-user-who-has-lost-their-mfa-device) | | **AI Inventory and Governance** (BETA) | Inventory the AI in every scanned repository: coding assistants and their rules files, MCP servers, models, AI packages, AI service endpoints, agent skills and AI provider keys. Policies mark each component authorized, unauthorized or needs review, unauthorized components become Findings, and each Asset can export an AI bill of materials. Inventories that arrive while this is off are still stored and appear when it is turned on. Requires **Locations**. | Off | [Docs](/asset_modelling/locations/pro__ai_inventory/) | | **AI Threat Modeling** | Generate a STRIDE threat model, attacker profiles, attack paths and security requirements from a feature design with Sensei. | On | [Docs](/sensei/threat_modeling/) | -| **Airgapped instance** | This instance has no route off its network, so it never contacts DefectDojo. The version check, external tools, announcements, threat intel downloads and error and performance diagnostics stop, and both support pages show the DefectDojo support address instead. Load threat intel bundles from a file. The DD_AIRGAPPED environment variable turns this on for the whole deployment. | Off | [Docs](/get_started/pro/onprem/airgapped/) | +| **Airgapped Instance** | This instance has no route off its network, so it never contacts DefectDojo. The version check, external tools, announcements, threat intel downloads and error and performance diagnostics stop, and both support pages show the DefectDojo support address instead. Load threat intel bundles from a file. The DD_AIRGAPPED environment variable turns this on for the whole deployment. | Off | [Docs](/get_started/pro/onprem/airgapped/) | | **Asset Discovery** (BETA) | List the devices your EDR sees on the network that have no agent of its own, as CrowdStrike Falcon Discover and Microsoft Defender for Endpoint report them. Turn it on per connection with Report Unmanaged Devices. The devices are candidates: none becomes an Asset, and none counts toward Total Findings. | Off | [Docs](/asset_modelling/pro__unmanaged_devices/) | | **Asset Exposure** (BETA) | Compute whether an asset is reachable from outside, and whether a finding's code is actually deployed in production, from the evidence connectors already collect. Both feed prioritization; exposure can also drive the FedRAMP VDR SLA tiers. | Off | [Docs](/triage_findings/finding_scoring/asset_exposure/) | | **Asset Identity** | Let connectors find each Asset by the identifier its source uses instead of by name, and list every identifier it is known by on its Identity card. Records that are already mapped stay where they are. Turning this off goes back to matching by name; the identifiers are kept. | Off | [Docs](/asset_modelling/engagements_tests/pro__assets/#asset-identity-aliases) | diff --git a/docs/content/admin/notifications/configure_personal_notifs.md b/docs/content/admin/notifications/configure_personal_notifs.md index 12f00e36d46..b464c80e41c 100644 --- a/docs/content/admin/notifications/configure_personal_notifs.md +++ b/docs/content/admin/notifications/configure_personal_notifs.md @@ -31,9 +31,19 @@ In addition to standard personal notifications, DefectDojo Users can also receiv ![image](images/Configure_System_&_Personal_Notifications_3.png) -This configuration can be changed from the **Notifications** section on the **Asset** page: e.g. `your-instance.defectdojo.com/product/{id}`. +In the Pro UI, open the Asset, then choose **Notifications** from the ⚙️ gear menu in the page header (the same menu appears on each Asset row in the Asset lists). The dialog lists the events that can be followed for one Asset, with a column for each delivery method: -From here, you can set whether you want to receive **🔔 Alert**, **Mail** or **Slack** notifications for actions taken on this particular Asset. These notifications apply in addition to any system\-wide notifications you are already receiving. +* **Engagement Added** and **Engagement Closed** +* **Test Added** and **Scan Added** +* **SLA Breach** and **SLA Breach (Combined)** +* **Risk Acceptance Expiring** +* **Finding Risk Accepted**, **Threat Intel Escalation** and **AI Component Discovered**: the Pro events that are sent about one Asset + +Tick a box to receive that event over that method, then **Submit**. The box under a method's name ticks or clears it for every event. Opening the dialog changes nothing: an Asset starts with every event off until settings are submitted for it. Other Pro events (connector health, work assignments, review requests and the like) are sent to named people or are not about one Asset, so they are set on Personal Notifications only. + +In the Classic UI, this configuration is changed from the **Notifications** section on the **Asset** page: e.g. `your-instance.defectdojo.com/product/{id}`. + +From here, you can set whether you want to receive **🔔 Alert**, **Mail** or **Slack** notifications for actions taken on this particular Asset. These notifications apply in addition to your Personal Notifications and any system\-wide notifications you are already receiving, and they stop if you lose access to the Asset. Microsoft Teams cannot send personal notifications of any kind, so Teams notifications cannot be chosen from this menu. diff --git a/docs/content/admin/notifications/configure_system_notifs.md b/docs/content/admin/notifications/configure_system_notifs.md index b1149fdb3c1..29959e45363 100644 --- a/docs/content/admin/notifications/configure_system_notifs.md +++ b/docs/content/admin/notifications/configure_system_notifs.md @@ -43,4 +43,15 @@ This restriction does not apply to System Notifications that are sent to a speci See our guide on [Role-Based Access Control](../../user_management/about_perms_and_roles/) for more information on RBAC and setting permissions. -However, the connected System Email, Slack and Teams accounts cannot apply RBAC as they are not associated with a specific DefectDojo user. **All selected system-wide notifications will be sent to these locations, so you should ensure that these channels can only be accessed by specific people in your organization.** \ No newline at end of file +However, the connected System Email, Slack and Teams accounts cannot apply RBAC as they are not associated with a specific DefectDojo user. **All selected system-wide notifications will be sent to these locations, so you should ensure that these channels can only be accessed by specific people in your organization.** + +## Notification webhooks a Group owns + +Notification webhooks are managed by superusers on **Notification Webhooks**. With Group Ownership on, a webhook can also belong to a Group, so the Group's Triage Engine rules post to it: + +* A member whose Role in the Group carries **Integration Edit** creates one for the Group (**Owned By**), and changes or deletes the Group's webhooks. Members whose Role carries **Integration View** see them. The header value is shown only to someone who can change the webhook. +* A Group's webhook receives no personal or system notifications; it is used by the rules that name it. +* A Group's webhook may not post to a private, loopback or link-local address. Creating, editing or transferring one with such an address into a Group is refused, and so is a send to it, unless the instance allows private destinations for the Triage Engine (`DD_RULES_V2_ALLOW_PRIVATE_EGRESS`). +* **Share** shows it to other Groups, and **Transfer** moves it to another Group. Only a superuser moves one to the instance, which makes it a system webhook. +* The **Owner** column shows who owns each webhook. System webhooks read as owned by the instance. + diff --git a/docs/content/admin/sso/PRO__login_settings.md b/docs/content/admin/sso/PRO__login_settings.md index 7d58c8df972..2fd28c4c5dc 100644 --- a/docs/content/admin/sso/PRO__login_settings.md +++ b/docs/content/admin/sso/PRO__login_settings.md @@ -25,6 +25,7 @@ Find it under **Connect > Authorization > Login Settings**. Changing it takes th Session Age and Idle Timeout apply to every login, whether by username and password or by SSO. A user whose session ends, for either reason, is taken to the login page on their next click, and lands back on the page they were on once they sign in again. +- In the Pro UI, a **Session Expiring** warning counts down the time left before a session ends, opening five minutes ahead by default (the `DD_SESSION_EXPIRE_WARNING` environment variable, in seconds, and never more than half the Idle Timeout ahead of an inactivity deadline). While inactivity is what will end the session, **Stay Signed In** starts a new Idle Timeout. When Session Age is what will end it, the warning says the session cannot be extended: save any work, then sign in again. Every open tab shares the session, so staying signed in from one tab closes the warning in the others. - Only requests the user makes count as activity. A page left open in a browser tab still reaches the Idle Timeout, even though it keeps refreshing its notification count in the background. - Activity is recorded at most once a minute, so an idle session can end up to a minute before the Idle Timeout, never after it. - A change to either setting reaches every running server within a minute, and applies to sessions that are already signed in. Shortening Session Age signs out anyone whose login is older than the new value. diff --git a/docs/content/admin/user_management/PRO__service_users.md b/docs/content/admin/user_management/PRO__service_users.md index 9dce093c45a..b09fb7d7472 100644 --- a/docs/content/admin/user_management/PRO__service_users.md +++ b/docs/content/admin/user_management/PRO__service_users.md @@ -94,7 +94,7 @@ On the Service Users page it is marked with a **Group Automation** chip, and the * It has no password and no token, and none can be issued, so it can never authenticate to the API or the UI. * Its access is exactly its group's: the group's Organization and Asset Roles and its Global Roles. Its access cards say the access is set at the group level, and it cannot be given a Global Role, a membership or a group of its own. -* Its menu offers only **View Service User**. It cannot be edited, disabled, given a new token or deleted here, and the Users API refuses to deactivate it. Change its access by changing the group's, and remove it by deleting the group. +* Its menu offers only **View Service User**. It cannot be edited, disabled, given a new token or deleted here, and the Users API refuses to deactivate it. Change its access by changing the group's, and remove it by deleting the group. Its API token, when the **Group API Tokens** flag is on, is issued and revoked on the group's page: see [the group's API token](../create_user_group/#the-groups-api-token). * Its name and description follow the group's current name. Group Automation identities do not count toward the licensed user total, the same as any service user. diff --git a/docs/content/admin/user_management/create_user_group.md b/docs/content/admin/user_management/create_user_group.md index 5b005278c67..1e02db3e05b 100644 --- a/docs/content/admin/user_management/create_user_group.md +++ b/docs/content/admin/user_management/create_user_group.md @@ -39,12 +39,12 @@ With Access Administration on (Pro), a Group's page has these tabs: * **Access**: what the Group reaches in its own right: its Global Roles, its Configuration Permissions, and the Organizations and Assets it holds a Role on, with how each one is reached. Every member of the Group receives this access through it, and a Group that holds a Global Role is marked as having instance-wide access. The Group's own Roles are changed here, with **Add to Organization**, **Add to Asset**, **Change Role** and **Remove**. Each change is checked against your permissions on that Organization or Asset, exactly as on its own Members dialog. * **Default Profile**: the [Default Profile](#default-profiles) the Group's Members start from, and what that profile sets. See [Set Group Defaults](#set-group-defaults). -With Group Ownership on, the page also has an **Owned Content** tab: one row for each kind of content a Group can own (**Rules**, **Report Templates**, **Report Blocks**, **Report Themes** and **Generated Reports**), with how many of the Group's you can see. A report schedule belongs to its Template, as a rule's schedule belongs to the rule, so it has no row of its own. A kind's name opens its list filtered to the Group, and that page's title and breadcrumb name the Group (for example **Report Templates Owned By AppSec**, under a link back to the Group). +With Group Ownership on, the page also has an **Owned Content** tab: one row for each kind of content a Group can own (**Rules**, **Report Templates**, **Report Blocks**, **Report Themes**, **Generated Reports**, **Dashboards**, **Webhook Receivers**, **Notification Webhooks**, **Downstream Connectors** and **Ticket Templates**), with how many of the Group's you can see. A report schedule belongs to its Template, as a rule's schedule belongs to the rule, so it has no row of its own. A kind's name opens its list filtered to the Group, and that page's title and breadcrumb name the Group (for example **Report Templates Owned By AppSec**, under a link back to the Group). * The kind's name opens its usual list filtered to the Group: the **Owner** filter is set to the Group, and clearing it shows the whole list again. * The row menu has **View** (the same filtered list) and, where you can create that kind for the Group, **New**, which opens the create page with **Owned By** already set to the Group. See [Group ownership of rules](/automation/triage_engine/sharing_and_versions/#group-ownership) and [of Report Builder content](/metrics_reports/reports/report-builder/#group-ownership). -A kind is listed when the Group owns something of that kind that you can see, or when you can create it there (your Role in the Group, or a Global Role, carries its **Add** permission; for a rule you must also be a member of the Group), and only while Triage Engine or Reporting is turned on. +A kind is listed when the Group owns something of that kind that you can see, or when you can create it there (your Role in the Group, or a Global Role, carries its **Add** permission, or **Integration Edit** for connectors, ticket templates and notification webhooks; for a rule or a webhook receiver you must also be a member of the Group), and only while the feature it belongs to (Triage Engine, Reporting or Downstream Connectors) is turned on. The Group's page shows what it owns. Who owns something is chosen where it is created (**Owned By**) and changed with **Transfer** in its row menu. @@ -62,7 +62,7 @@ Rules and report schedules a Group owns do not run as the person who created the The identity has exactly the Group's access, and nothing of its own: * It sees and edits what the Group's Organization and Asset Roles allow, and what the Group's Global Roles allow. When the Group gains or loses access, so does the identity, immediately. -* It has no password and no API token, and it cannot sign in. +* It has no password and cannot sign in. It has no API token unless one is issued for the Group (see [The group's API token](#the-groups-api-token)). * It cannot be given a Global Role, a membership or any access of its own. Its access is set on the Group. * It is removed when the Group is deleted. @@ -70,9 +70,23 @@ Because the identity carries the Group's access, a Group that holds a Global Rol Changing what a Group's rule or report schedule does needs membership of the Group as well as the permission to edit it, because the change acts with the Group's access. See [Group Content permissions](../pro__custom_rbac_roles/#group-content-permissions). +### The group's API token + +With the **Group API Tokens** feature flag on, a Group's automation identity can hold one API token, so the Group's own automation (a pipeline, a script) calls the API as the Group. The token sees exactly what the Group owns and has been granted, nothing more. It acts as the Group, not as whoever issued it, so it can read Group content (connectors and ticket templates, for example, with their secrets masked) that the issuer's own Role in the Group does not show them. + +The **Automation Identity** card on the Group's **Overview** tab shows the token's first characters, who issued it and when, and when it expires. A superuser, or a member whose Role in the Group carries **Service User Edit**, manages it there: + +* **Issue Token** creates the token, with an expiry of 30, 60, 90 or 180 days or none. The token is shown once; DefectDojo keeps only a hash of it. Issuing creates the automation identity if the Group has none yet. +* **Replace Token** issues a new token; the old one stops working at once. OAuth access tokens already exchanged from it keep working until they expire, within the hour. +* **Revoke Token** ends the token at once (OAuth access tokens already exchanged from it expire within the hour). Revoking works even while the feature flag is off. + +When the Group holds a Global Role, its token reaches everything that Role grants, so only a superuser may issue it; the card still offers its other managers **Revoke Token**. The token is used like any [service user](../pro__service_users/#use-the-token) token, in the `Authorization` header with the `Token` keyword. Group API Tokens needs Service Users on: a group token is a service user token. Turning the **Group API Tokens** flag (or **Service Users**) off refuses every group token, and every OAuth access token exchanged from one, until it is turned on again. + ### Deleting a Group that owns content -Deleting a Group does not delete its rules, templates, blocks, themes or schedules. Its rules are turned off, its templates, blocks and themes are hidden from everyone except superusers and holders of the matching global permission, and its schedules stop running. The rules, templates, blocks and themes appear under **Settings > Groups > Unowned Content** (a schedule waits with its Template and comes back when the Template is transferred), where an administrator holding **Rule Edit** or **Report Template Edit** as a Global Role transfers them to another Group or to the instance. The Group's automation identity is removed with the Group. +Deleting a Group does not delete its rules, templates, blocks, themes, schedules, webhook receivers, notification webhooks, connectors or ticket templates. Its rules are turned off, its templates, blocks and themes are hidden from everyone except superusers and holders of the matching global permission, and its schedules stop running. Its webhook receivers stop accepting deliveries, and its notification webhooks are made permanently inactive, so nothing is sent through them until they have a new owner. The rules, templates, blocks, themes, receivers, notification webhooks, connectors and ticket templates appear under **Settings > Groups > Unowned Content** (a schedule waits with its Template and comes back when the Template is transferred), where an administrator holding the matching global Edit permission (**Rule Edit**, **Report Template Edit** or **Integration Edit**) transfers them to another Group or to the instance. Only a superuser moves a notification webhook to the instance, because that makes it a system webhook. The Group's automation identity, and with it the Group's API token, is removed with the Group. + +Its dashboards, page layouts and saved table views are kept too, hidden from everyone except superusers and holders of the matching share permission (**Dashboard Share**, **Page Layout Share**, **Table Preference Share**) as a Global Role. A dashboard waits in the **Group Dashboards** section of **Manage Layouts**, tagged **Unowned**, until it is transferred. A layout the Group had shared with everyone stays shared. ## Create a User Group diff --git a/docs/content/admin/user_management/pro_permissions_overhaul.md b/docs/content/admin/user_management/pro_permissions_overhaul.md index 67e28fcd028..15fb796537a 100644 --- a/docs/content/admin/user_management/pro_permissions_overhaul.md +++ b/docs/content/admin/user_management/pro_permissions_overhaul.md @@ -39,6 +39,8 @@ Permissions in DefectDojo When looking at Organization or Asset, you can open the Permissions window to set permissions quickly. This menu can be found in a Table by clicking the horizontal dots **"⋮"**. IF looking at an individual **Asset** or **Organization** page, this menu can be found under the blue gear ‘⚙️’. +On an **Organization** page, the **Members** and **Groups** widgets manage the same access without opening the Permissions window. Each lists the users or groups that hold a role on the Organization, Owners first, with their role. **Add User** and **Add Group** under the list open a form to pick a user or group and a **Role**. The menu at the left of a row offers **Change Role**, which opens a form, and **Remove**, which asks you to confirm. These controls are shown only to someone who can manage the Organization's members or groups, and an Owner row can be changed only by someone who can grant the Owner role: for anyone else it shows a lock. + ## Setting Permissions through the permissions window ![image](images/pro_permissions_2.png) diff --git a/docs/content/asset_modelling/OS_questionnaires/OS__questionnaires.md b/docs/content/asset_modelling/OS_questionnaires/OS__questionnaires.md index 1414ce31612..4b5977de51a 100644 --- a/docs/content/asset_modelling/OS_questionnaires/OS__questionnaires.md +++ b/docs/content/asset_modelling/OS_questionnaires/OS__questionnaires.md @@ -170,10 +170,14 @@ In order to deploy a Linked Questionnaire: The Linked Questionnaire will now be active for any Users with access to the Engagement. +In DefectDojo Pro, open the Engagement, select the **Questionnaires** tab of the **Overview** tile, click **Add Questionnaire**, choose the Questionnaire and click **Submit**. The button shows to Users who can edit the Engagement while a Questionnaire template is still left to link. The tab lists the unanswered Questionnaires first, then the answered ones with who answered and when, and is shown only while **Enable Questionnaires** is on in System Settings. The ⋮ menu at the left of each row holds **Assign User**, **Link to a Different Engagement** (for Users with the permission to change Questionnaires who can also edit both this Engagement and the one it moves to) and **Delete Questionnaire**. + #### Sharing a Linked Questionnaire To share the Linked Questionnaire directly with internal DefectDojo Users, click the ⋮ kebab menu and select **Share Questionnaire** from the dropdown. A link will appear which can be copied and forwarded to the intended recipient. +In DefectDojo Pro, open the ⋮ menu at the left of the Questionnaire's row on the **Questionnaires** tab and select **Share Questionnaire**: the link to its answer page is copied to the clipboard. + ![image](images/q_ss10.png) As mentioned, Linked Questionnaires can only be shared with DefectDojo Users. @@ -213,6 +217,8 @@ To respond to a Linked Questionnaire: ![image](images/q_ss15.png) +In DefectDojo Pro, open the **Questionnaires** tab of the Engagement's **Overview** tile and click **Answer** on the Questionnaire's row (or **Edit Responses** in its ⋮ menu once it has been answered), fill in the answers and click **Submit**. Answering is open to Users who can edit the Engagement, and the page returns to the Engagement once the responses are saved. **View Responses** on an answered row shows who answered, when, and every answer. + Linked Questionnaires do not permit external/anonymous responses because DefectDojo access is required in order to access the Engagement. ## Responses diff --git a/docs/content/asset_modelling/PRO__asset_attribute_options.md b/docs/content/asset_modelling/PRO__asset_attribute_options.md index ccaada8a098..e8b121d99f1 100644 --- a/docs/content/asset_modelling/PRO__asset_attribute_options.md +++ b/docs/content/asset_modelling/PRO__asset_attribute_options.md @@ -19,7 +19,9 @@ lists the current options with: is the part you edit. - **Value** — a fixed machine key stored on the asset and used by the API, imports and automation rules. It is set when an option is created and never changes afterward, so relabeling an option - never breaks an integration. + never breaks an integration. A value may contain a comma: the findings filters that take a + comma-separated list of values (`product_lifecycle` on `/api/v2/findings/`) read a value in + double quotes as one value, for example `"beta, ga",production`. - **Icon** and **Display order** — optional. The icon is a Font Awesome name; display order controls where the option appears in the dropdown. - **Assets Using** — how many assets currently reference the option. diff --git a/docs/content/asset_modelling/PRO__custom_fields.md b/docs/content/asset_modelling/PRO__custom_fields.md index f538bf9330b..1fcd9096263 100644 --- a/docs/content/asset_modelling/PRO__custom_fields.md +++ b/docs/content/asset_modelling/PRO__custom_fields.md @@ -46,6 +46,8 @@ add the fields that apply to it. Each definition has: required field always appears on those forms and cannot be hidden from them. - **Allow Other** (Single Select and Multi Select only): whether the field also offers an **Other** choice that takes a typed value. See [The Other choice](#the-other-choice). +- **Formula** (Integer and Decimal fields): fills the value in from the record's numbers instead of + by hand. See [Computed fields](#computed-fields). ### Data types @@ -87,6 +89,102 @@ typed text to the field's options, which makes it an ordinary option. It is also enabled rule sets the field to a value outside its options, and the message names those rules: update or disable them first. +## Computed fields + +Any Integer or Decimal custom field can be **computed**: DefectDojo fills its value in with a +formula, and nobody types it. A typical use is a health score for each Asset, or a weighted risk on +each Finding. Give the field a formula when you add it, or type one into the **Options or Formula** +column of its row; clearing the formula makes the field an ordinary one again. Because a formula +replaces every value already entered in the field, DefectDojo asks before an existing field becomes +computed, and again before a computed field goes back to being entered by hand (its values stay as +last computed). **Insert Number** under the formula, in the field's row and when you add a field, +lists every number it can read and adds the one you pick to the end of the formula. + +A computed field holds what its formula gives and nothing else. Where the formula has no result for a +record (an input it reads is missing, it divides by zero, it averages no Findings, or a step is too +large to be a number), the record's value is empty, including a value typed in before the field became +computed. + +A formula reads the record's own numbers: + +- **Built-in numbers**: `fields.`. On a Finding: `cwe`, `cvssv3_score`, `cvssv4_score`, + `epss_score`, `epss_percentile`, `line`, `occurrences`, `scanner_confidence`, `priority`, + `risk_score`, `threat_score`, `threat_ladder_rung`, `pain_rating` and `reachability_confidence`. On + an Asset: `grade`, `user_records`, `revenue`, `exposure_confidence` and `patch_cycle_days`. On a + Test: `percent_complete`. +- **Number custom fields**: `custom_fields.` for the record's Integer and Decimal fields, + computed ones included. +- **Finding counts** (on an Organization, Asset, Engagement, Test or Risk Acceptance): + `findings.active.total`, `findings.active.risk.urgent` (and `needs_action`, `medium`, `low`, + `unscored`), `findings.active.severity.critical` (and `high`, `medium`, `low`, `info`), and the + same under `findings.open` for open Findings (active, and not a duplicate, false positive, out of + scope, risk accepted or mitigated). +- **Finding aggregates** (on the same records): `findings...` followed + by any Finding number, built-in or custom. For example `findings.open.max.fields.epss_score` is the + highest EPSS score among the record's open Findings, and `findings.active.sum.custom_fields.cost` + adds up a Finding custom field. A sum over no Findings is 0; an average, lowest or highest over none + is missing. + +It combines them with `+ - * /`, parentheses, comparisons, `and`, `or`, `not`, and the functions +`min`, `max`, `abs`, `round`, `div(a, b, if_zero)`, `coalesce` and `if(test, a, b)`. For example, a +score from 100 down that weighs each active Finding by its priority band: + +``` +100 * (1 - div(10 * findings.active.risk.urgent + 5 * findings.active.risk.needs_action + + 2 * findings.active.risk.medium + findings.active.risk.low, 10 * findings.active.total, 0)) +``` + +### Computed fields that read each other + +A computed field can read another computed field, on the same record or, through an aggregate, on its +Findings: a computed `weighted_risk` on each Finding can feed an Asset's +`findings.active.sum.custom_fields.weighted_risk`. DefectDojo recomputes them in order, so a field is +always written before anything that reads it. A formula that would make fields read each other in a +loop is refused when it is saved, and the message names the loop. A field that a computed formula +reads cannot be deleted, or changed to a type other than Integer or Decimal, until those formulas +stop reading it; the message names them. + +### When values are recomputed + +- **After Findings change.** Saving or deleting a Finding (including an import) recomputes, shortly + afterwards, the Finding's own computed fields and every computed field on its Asset, Organization, + Engagements, Tests and the Risk Acceptances on its Engagements. +- **After an input changes.** Changing a custom field value that a computed formula reads (by hand, + through the API or from a rule) recomputes that record, and a Finding's value recomputes its Asset. + Saving an Asset or a Test recomputes it when a formula reads its built-in numbers. +- **When the formula changes** (or the field's type, or whether it is computed), the field and every + computed field that reads it are recomputed for every record at once. Changing only its label + recomputes nothing. +- **Every hour**, every computed field is recomputed in full. That catches changes made without saving + each record, such as bulk updates, priority recalculations and records moving between Assets. A + sweep still running when the next hour comes is never overlapped; the next one is skipped. + +When a computed field gets a new value, the Triage Engine hears it the same way as a rule writing that +field. On an Asset, a rule triggered [On Asset Event](/automation/triage_engine/node_reference/#on-asset-event) +for updates sees `custom_fields.` in `ctx.changed_fields`, so an If / Filter node with +`ctx.changed_fields` `contains` `custom_fields.` runs only when that value moves. On a Finding, +it is a Finding update. A recompute that leaves the value unchanged raises no event. The one +recompute that announces no Finding updates is the one after the formula itself changes: a new formula +can move the value on every Finding at once, so a rule reading it sees the new value on that Finding's +next change instead. Asset updates are announced as usual. + +When a rule's write is what moved the computed value (the rule set a field the formula reads), the +event belongs to that rule's run, as a change the rule made itself would: it does not start the same +rule again, and it counts toward the limit on how many rules one change can set off in a chain. A rule +that writes a field from a computed field reading that same field therefore runs once per change, +rather than feeding itself forever. + +Counts and aggregates include every Finding of the record, whoever is looking, so everyone sees the +same value, as with the Asset grade. An Integer field rounds half away from zero; a Decimal field keeps +up to four decimal places. A record whose formula reads a number it does not have, or divides by zero +with `/`, is left empty: use `div` or `coalesce` to give such a record a value of its own. + +A computed value cannot be typed on the record, set through the API, or written by a rule; on the +record and its forms it shows read-only, with its formula. A field cannot be made computed while an +enabled rule writes it (the message names the rules). To compute a value only on some records, or on a +schedule of your own, use a [rule with a formula](/automation/triage_engine/node_reference/#formulas) +instead. + ## Filling in values Once fields are defined for a record type, fill them in from the record's **⋮ (kebab) menu → Custom @@ -139,6 +237,14 @@ A select field that allows Other also gets a **