Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
99d1db0
Replace legacy Ace editors
Jul 15, 2026
1e4d815
Fix password MFA verification feedback
Jul 15, 2026
d9cc7d2
Add Kusto to all Milkdown code blocks
Jul 15, 2026
454a302
Test Milkdown Kusto language registration
Jul 15, 2026
211789a
Fix nginx collab resolver to work under podman/netavark
Che4ter Jul 15, 2026
fc9ce02
Deny gracefully instead of crashing on non-numeric socket channel id
Che4ter Jul 15, 2026
dc20015
Fix response_api_error() silently forcing 400 on every error
Che4ter Jul 15, 2026
a6a89f2
Fix MFA lockout bypass via session user id mismatch
Che4ter Jul 15, 2026
0d87a06
Fix overlapping note_detail() calls orphaning editor instances
Che4ter Jul 15, 2026
77d90bb
Don't run prose transform over an unterminated code fence
Che4ter Jul 15, 2026
20aa224
Fix snippet insertion, Kusto lookup, and duplicate-destroy in unified…
Che4ter Jul 15, 2026
bc552bf
Handle large notes and add direct export
Jul 16, 2026
72e6263
Fix note export content type
Jul 16, 2026
9fc2907
Fix pycrdt cross-thread GC crash in collab worker and server
Che4ter Jul 16, 2026
1fadfc3
Persist note title in collaborative note editing
Che4ter Jul 16, 2026
d3e291b
Make note overflow menu useful
Jul 16, 2026
ba7ab61
Remove redundant note overflow menu
Jul 16, 2026
a884e77
Remove legacy description preview controls
Jul 16, 2026
2cdd123
Fix Milkdown description modal layout
Jul 16, 2026
91bce72
Align modal Milkdown padding with notes
Jul 16, 2026
781c3d2
Promote tested TST Milkdown, collaboration, and MFA changes
Aug 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion docker/nginx/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,18 @@ if [ -n "${SERVER_NAME:-}" ]; then
export SERVER_NAME
fi

# The /collab/ location resolves its upstream at request time so nginx
# doesn't refuse to start when the (optional) collab service is absent.
# The resolver address is container-runtime-specific: Docker's embedded
# DNS is always 127.0.0.11, but podman/netavark's aardvark-dns listens on
# the network gateway instead. Read the real nameserver out of this
# container's own /etc/resolv.conf so both runtimes work.
DNS_RESOLVER=$(awk '/^nameserver/{print $2; exit}' /etc/resolv.conf)
export DNS_RESOLVER="${DNS_RESOLVER:-127.0.0.11}"

# envsubst will make a substitution on every $variable in a file, since the nginx file contains nginx variable like $host, we have to limit the substitution to this set
# otherwise, each nginx variable will be replaced by an empty string
envsubst '${INTERFACE_HTTPS_PORT} ${IRIS_UPSTREAM_SERVER} ${IRIS_UPSTREAM_PORT} ${SERVER_NAME} ${KEY_FILENAME} ${CERT_FILENAME} ${IRIS_FRONTEND_SERVER} ${IRIS_FRONTEND_PORT}' < /etc/nginx/nginx.conf > /tmp/nginx.conf
envsubst '${INTERFACE_HTTPS_PORT} ${IRIS_UPSTREAM_SERVER} ${IRIS_UPSTREAM_PORT} ${SERVER_NAME} ${KEY_FILENAME} ${CERT_FILENAME} ${IRIS_FRONTEND_SERVER} ${IRIS_FRONTEND_PORT} ${DNS_RESOLVER}' < /etc/nginx/nginx.conf > /tmp/nginx.conf
cp /tmp/nginx.conf /etc/nginx/nginx.conf
rm /tmp/nginx.conf

Expand Down
22 changes: 15 additions & 7 deletions docker/nginx/nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -155,13 +155,21 @@ http {
}

location /collab/ {
# Resolve at request time (Docker's embedded DNS) instead of at
# nginx startup: the collab service is a BV-only optional
# feature and isn't present in every compose stack (e.g. the
# upstream-derived dev/CI stack). A static proxy_pass hostname
# would make nginx refuse to start entirely when "collab" can't
# be resolved; this way only /collab/ requests fail if it's absent.
resolver 127.0.0.11 valid=30s;
# Resolve at request time (the container's own DNS resolver)
# instead of at nginx startup: the collab service is a BV-only
# optional feature and isn't present in every compose stack (e.g.
# the upstream-derived dev/CI stack). A static proxy_pass
# hostname would make nginx refuse to start entirely when
# "collab" can't be resolved; this way only /collab/ requests
# fail if it's absent.
#
# ${DNS_RESOLVER} is templated by entrypoint.sh from the
# container's /etc/resolv.conf. It is NOT hardcoded to Docker's
# embedded-DNS address (127.0.0.11) because that address doesn't
# exist under podman/netavark, where aardvark-dns listens on the
# network gateway instead — a hardcoded 127.0.0.11 here silently
# 502s every /collab/ request after a 30s resolver timeout.
resolver ${DNS_RESOLVER} valid=30s;
set $collab_upstream collab;

proxy_set_header Host $http_host;
Expand Down
5 changes: 4 additions & 1 deletion e2e/tests/administrator/case/ioc.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,17 @@ test.beforeEach(async({ page }) => {

test('should be able to update IOC', async ({ page }) => {
const iocValue = `IOC value - ${crypto.randomUUID()}`;
const iocDescription = `IOC description - ${crypto.randomUUID()}`;

await page.getByRole('button', { name: 'Add IOC' }).click();
await page.getByRole('button', { name: 'None' }).click();
await page.getByRole('listbox').getByRole('option', { name: 'AS', exact: true }).click();
await page.getByLabel('IOC Value *').fill(iocValue);
await page.locator('#ioc_description .ProseMirror').fill(iocDescription);
await page.getByRole('button', { name: 'Save' }).click();

await page.getByRole('link', { name: iocValue }).click();
await expect(page.locator('#ioc_description .ProseMirror')).toContainText(iocDescription);
const newIocValue = `IOC value - ${crypto.randomUUID()}`;
await page.getByLabel('IOC Value *').fill(newIocValue);
await page.getByRole('button', { name: 'Update' }).click();
Expand Down Expand Up @@ -89,4 +92,4 @@ test('should be able to update IOC custom attribute', async ({ page, rest }) =>
partial_overwrite: false
}
})
});
});
12 changes: 10 additions & 2 deletions source/app/blueprints/access_controls.py
Original file line number Diff line number Diff line change
Expand Up @@ -413,9 +413,17 @@ def wrap(*args, **kwargs):
return redirect(not_authenticated_redirection_url(request.full_path))

chan_id = args[0].get('channel')
if chan_id:
if not chan_id:
return _ac_return_access_denied(caseid=0)

# Clients occasionally join a room before a case is selected
# (e.g. `case-null`), producing a non-numeric id. That's not an
# authorization bypass attempt, just a premature join - deny
# gracefully instead of raising and killing the socketio
# event-handler thread.
try:
case_id = int(chan_id.replace('case-', '').split('-')[0])
else:
except ValueError:
return _ac_return_access_denied(caseid=0)

access = ac_fast_check_user_has_case_access(iris_current_user.id, case_id, access_level)
Expand Down
1 change: 0 additions & 1 deletion source/app/blueprints/pages/case/templates/case.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
<link rel="stylesheet" href="/static/assets/css/select2.css">
<link rel="stylesheet" href="/static/assets/css/bootstrap-multiselect.min.css">
<link rel="stylesheet" href="/static/assets/css/bootstrap-select.min.css">
<link rel="stylesheet" href="/static/assets/css/milkdown_editor.css">
{% endblock stylesheets %}
{% block content %}
{% if current_user.is_authenticated %}
Expand Down
3 changes: 1 addition & 2 deletions source/app/blueprints/pages/case/templates/case_assets.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
{% extends "layouts/default_ext.html" %} {% block title %} Case Assets {% endblock title %} {% block stylesheets %}
{% include 'includes/header_case.html' %}
<link rel="stylesheet" href="/static/assets/css/bootstrap-select.min.css">
<link rel="stylesheet" href="/static/assets/css/milkdown_editor.css">
{% endblock stylesheets %}
{% block content %}
{% include 'includes/navigation_ext.html' %}
Expand Down Expand Up @@ -148,4 +147,4 @@ <h5>Upload assets list (CSV format)</h5>
<script type="module" src="/static/assets/js/iris/milkdown_editor.js"></script>
<script type="module" src="/static/assets/js/iris/milkdown_split.js"></script>

{% endblock javascripts %}
{% endblock javascripts %}
10 changes: 1 addition & 9 deletions source/app/blueprints/pages/case/templates/case_notes_v2.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
{% extends "layouts/default_ext.html" %} {% block title %} Case notes {% endblock title %} {% block stylesheets %}
{% include 'includes/header_case.html' %}
<link rel="stylesheet" href="/static/assets/css/bootstrap-select.min.css">
<link rel="stylesheet" href="/static/assets/css/milkdown_editor.css">
{% endblock stylesheets %}
{% block content %}
{% include 'includes/navigation_ext.html' %}
Expand Down Expand Up @@ -88,7 +87,7 @@ <h4 class="page-title mb-0" id="currentNoteTitle"></h4>
<i class="fa-brands fa-markdown mr-2"></i>
</span>
</button>
<button type="button" class="btn bg-transparent btn-xs" onclick="download_note();return false;" title="Download as MD">
<button type="button" class="btn bg-transparent btn-xs" onclick="download_note();return false;" title="Export note as Markdown" aria-label="Export note as Markdown">
<span class="btn-label">
<i class="fa-solid fa-download mr-2"></i>
</span>
Expand All @@ -98,13 +97,6 @@ <h4 class="page-title mb-0" id="currentNoteTitle"></h4>
<i class="fa-solid fa-trash mr-2 text-danger"></i>
</span>
</button>
<div class="dropdown">
<button class="btn bg-transparent" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<span aria-hidden="true"><i class="fas fa-ellipsis-v"></i></span>
</button>
<div class="dropdown-menu pull-right" id="note_quick_actions" aria-labelledby="dropdownMenuButton">
</div>
</div>
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,26 +79,7 @@ <h4 class="modal-title mr-4">{% if event.event_id %} Event ID #{{ event.event_i
<div class="row">
<div class="form-group mt-3 col-12">
<label for="event_content" class="placeholder">Event description</label>
<div class="md_description_field">
<div class="form-group mt--2">
<button type="button" class="float-right icon-note btn btn-circle btn-sm mt-2" onclick="edit_in_event_desc();" >
</button>
<button type="button" style="display: none;" class="btn btn-dark btn-sm float-right mr-2 mt-2"
onclick="preview_event_description();" id="event_preview_button"><i class="fa-solid fa-eye"></i></button>
</div>
<div class="row">
<div class="col mb--2 ml--2" id="event_edition_btn" style="display:none;">
</div>
</div>
<div class="row" style="margin-left:0px;">
<div class="col-12" id="container_event_desc_content">
<div id="event_description" contenteditable="true" spellcheck="true" class="mr-2" data-theme="{% if current_user.in_dark_mode %}dark{% else %}light{% endif %}">{% if event.event_content %}{{ event.event_content }}{% endif %}</div>
<textarea id="event_desc_content" rows="10" cols="82" style="display: none"></textarea>
</div>
<div class="col-12" id="container_event_description" style="display:none">
<div id="target_event_desc"></div>
</div>
</div>
<div id="event_description" contenteditable="true" spellcheck="true" data-theme="{% if current_user.in_dark_mode %}dark{% else %}light{% endif %}">{% if event.event_content %}{{ event.event_content }}{% endif %}</div>
</div>
</div>
</div>
Expand Down Expand Up @@ -326,4 +307,4 @@ <h4 class="modal-title mr-4">{% if event.event_id %} Event ID #{{ event.event_i
]);
$('#event_iocs').trigger('change');
</script>
{% endif %}
{% endif %}
23 changes: 2 additions & 21 deletions source/app/blueprints/pages/case/templates/modal_add_case_ioc.html
Original file line number Diff line number Diff line change
Expand Up @@ -72,26 +72,7 @@ <h4 class="modal-title mr-4">{% if ioc.ioc_id %}Edit IOC #{{ ioc.ioc_id }}{% els
{% endif %}
<div class="form-group mt-3">
<label for="ioc_description" class="placeholder">Description</label>
<div class="md_description_field">
<div class="form-group mt--2">
<button type="button" class="float-right icon-note btn btn-circle btn-sm mt-2" onclick="edit_in_ioc_desc();">
</button>
<button type="button" style="display: none;" class="btn btn-dark btn-sm float-right mr-2 mt-2"
onclick="preview_ioc_description();" id="ioc_preview_button"><i class="fa-solid fa-eye"></i></button>
</div>
<div class="row">
<div class="col mb--2 ml--2" id="ioc_edition_btn" style="display:none;">
</div>
</div>
<div class="row" style="margin-left:0px;">
<div class="col-12" id="container_ioc_desc_content">
<div id="ioc_description" contenteditable="true" spellcheck="true" class="mr-2" data-theme="{% if current_user.in_dark_mode %}dark{% else %}light{% endif %}">{% if ioc and ioc.ioc_description %}{{ ioc.ioc_description }}{% endif %}</div>
<textarea id="ioc_desc_content" rows="10" cols="82" style="display: none"></textarea>
</div>
<div class="col-12" id="container_ioc_description" style="display:none">
<div id="target_ioc_desc"></div>
</div>
</div>
<div id="ioc_description" contenteditable="true" spellcheck="true" data-theme="{% if current_user.in_dark_mode %}dark{% else %}light{% endif %}">{% if ioc and ioc.ioc_description %}{{ ioc.ioc_description }}{% endif %}</div>
</div>
</div>
<div class="form-group">
Expand Down Expand Up @@ -144,4 +125,4 @@ <h4 class="modal-title mr-4">{% if ioc.ioc_id %}Edit IOC #{{ ioc.ioc_id }}{% els
{% else %}
$('#ioc_tlp_id').selectpicker('val', '2');
{% endif %}
</script>
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -100,26 +100,7 @@ <h4 class="modal-title mr-4">{% if rfile.id %}Edit evidence #{{rfile.id}}{% else

<div class="form-group">
<label for="rfile_desc" class="placeholder">Description</label>
<div class="md_description_field">
<div class="form-group mt--2">
<button type="button" class="float-right icon-note btn btn-circle btn-sm mt-2" onclick="edit_in_evidence_desc();" >
</button>
<button type="button" style="display: none;" class="btn btn-dark btn-sm float-right mr-2 mt-2"
onclick="preview_evidence_description();" id="evidence_preview_button"><i class="fa-solid fa-eye"></i></button>
</div>
<div class="row">
<div class="col mb--2 ml--2" id="evidence_edition_btn" style="display:none;">
</div>
</div>
<div class="row" style="margin-left:0px;">
<div class="col-12" id="container_evidence_desc_content">
<div id="evidence_description" contenteditable="true" spellcheck="true" class="mr-2" data-theme="{% if current_user.in_dark_mode %}dark{% else %}light{% endif %}">{% if rfile %}{{ rfile.file_description }}{% endif %}</div>
<textarea id="evidence_desc_content" rows="10" cols="82" style="display: none"></textarea>
</div>
<div class="col-12" id="container_evidence_description" style="display:none">
<div id="target_evidence_desc"></div>
</div>
</div>
<div id="evidence_description" contenteditable="true" spellcheck="true" data-theme="{% if current_user.in_dark_mode %}dark{% else %}light{% endif %}">{% if rfile %}{{ rfile.file_description }}{% endif %}</div>
</div>
</div>
<div class="form-group">
Expand All @@ -142,4 +123,4 @@ <h4 class="modal-title mr-4">{% if rfile.id %}Edit evidence #{{rfile.id}}{% else
<button type="button" class="btn btn-outline-success float-right" onclick="add_rfile();">Register</button>
{% endif %}
</div>
</div>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -59,26 +59,7 @@ <h4 class="modal-title mr-4">{% if task.id %} Task ID #{{ task.id }}{% else %}
</div>
<div class="form-group mt-3">
<label for="task_description" class="placeholder">Description</label>
<div class="md_description_field">
<div class="form-group mt--2">
<button type="button" class="float-right icon-note btn btn-circle btn-sm mt-2" onclick="edit_in_task_desc();">
</button>
<button type="button" style="display: none;" class="btn btn-dark btn-sm float-right mr-2 mt-2"
onclick="preview_task_description();" id="task_preview_button"><i class="fa-solid fa-eye"></i></button>
</div>
<div class="row">
<div class="col mb--2 ml--2" id="task_edition_btn" style="display:none;">
</div>
</div>
<div class="row" style="margin-left:0px;">
<div class="col-12" id="container_task_desc_content">
<div id="task_description" contenteditable="true" spellcheck="true" class="mr-2" data-theme="{% if current_user.in_dark_mode %}dark{% else %}light{% endif %}">{% if task.task_description %}{{ task.task_description }}{% endif %}</div>
<textarea id="task_desc_content" rows="10" cols="82" style="display: none"></textarea>
</div>
<div class="col-12" id="container_task_description" style="display:none">
<div id="target_task_desc"></div>
</div>
</div>
<div id="task_description" contenteditable="true" spellcheck="true" data-theme="{% if current_user.in_dark_mode %}dark{% else %}light{% endif %}">{% if task.task_description %}{{ task.task_description }}{% endif %}</div>
</div>
</div>
<div class="form-group mt-3">
Expand Down
Loading
Loading