Skip to content

Commit 9ed7cb4

Browse files
committed
docs(admin): document ICAP antivirus configuration
The ICAP section was a single paragraph ending 'Additional documentation is work in progress'. Replaces it with the settings and their defaults, REQMOD versus RESPMOD, the vendor-specific virus response header and its silent failure mode, verification via occ files_antivirus:test, how each ICAP response code is interpreted, transport security, and what data is sent to the scanning service. AI-Assisted-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Anna Larch <anna@nextcloud.com>
1 parent 0fb4372 commit 9ed7cb4

1 file changed

Lines changed: 357 additions & 3 deletions

File tree

admin_manual/configuration_server/antivirus_configuration.rst

Lines changed: 357 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -231,12 +231,366 @@ Files marked as scanned will not be scanned for the next four weeks.
231231
Configuring ICAP on Nextcloud
232232
-----------------------------
233233

234-
Nextcloud offers the integration of antivirus protection based on the ICAP protocol.
235-
The settings are outlined here.
236-
Additional documentation is work in progress.
234+
Instead of talking to ClamAV directly, the Antivirus app for Files can hand
235+
files to an external scanning service using ICAP, the Internet Content
236+
Adaptation Protocol (`RFC 3507 <https://www.rfc-editor.org/rfc/rfc3507>`_).
237+
In this mode Nextcloud does not run a scanner itself: it opens a TCP
238+
connection to an ICAP server, streams the file to it and acts on the verdict
239+
that comes back.
240+
241+
Use ICAP mode when:
242+
243+
* you already operate an enterprise scanning appliance or scanning service
244+
that speaks ICAP, possibly shared with your mail gateway or proxy. The app
245+
contains specific handling for Kaspersky and McAfee products, and the
246+
administration settings ship presets for ClamAV / c-icap, Kaspersky and
247+
FortiSandbox;
248+
* you want to run ClamAV behind ``c-icap`` on a separate host, so that
249+
scanning load and signature updates are kept away from the Nextcloud
250+
servers;
251+
* your scanner should also enforce a file type or file extension policy, which
252+
ICAP mode understands in addition to malware verdicts (see
253+
`How ICAP responses are interpreted`_).
254+
255+
To enable it, select ``ICAP`` as the mode in the Antivirus Configuration panel
256+
on your Admin page, or set it with ``occ``::
257+
258+
sudo -E -u www-data php occ config:app:set files_antivirus av_mode --value="icap"
237259

238260
.. figure:: ../images/antivirus-icap.png
239261

262+
Settings
263+
~~~~~~~~
264+
265+
The host and port are shared with the other network modes; all other settings
266+
below are specific to ICAP mode.
267+
268+
.. list-table::
269+
:header-rows: 1
270+
:widths: 30 20 50
271+
272+
* - Option
273+
- Default
274+
- Description
275+
* - ``av_mode``
276+
- ``executable``
277+
- Set to ``icap`` to enable ICAP mode.
278+
* - ``av_host``
279+
- (empty)
280+
- Hostname or IP address of the ICAP server. Scanning fails if this is
281+
not set.
282+
* - ``av_port``
283+
- ``3310``
284+
- Port of the ICAP server. Note that this default is the ClamAV daemon
285+
port, not the ICAP default port of 1344, so it almost always has to be
286+
changed.
287+
* - ``av_icap_request_service``
288+
- ``avscan``
289+
- The ICAP service to call, that is the path part of the request URI
290+
``icap://<av_host>/<service>``. ``c-icap`` with ClamAV uses ``avscan``;
291+
other products use different names, for example ``req`` for Kaspersky
292+
and ``respmod`` for FortiSandbox.
293+
* - ``av_icap_mode``
294+
- ``reqmod``
295+
- Either ``reqmod`` or ``respmod``. See
296+
`Choosing between REQMOD and RESPMOD`_.
297+
* - ``av_icap_response_header``
298+
- ``X-Infection-Found``
299+
- The ICAP response header from which the threat name is read. This is
300+
vendor specific; see `The virus response header`_.
301+
* - ``av_icap_tls``
302+
- ``false``
303+
- Whether to wrap the ICAP connection in TLS. See
304+
`Transport security`_.
305+
* - ``av_icap_chunk_size``
306+
- ``1048576``
307+
- Number of bytes buffered in memory before they are written to the ICAP
308+
server as one chunk of the chunked request body.
309+
* - ``av_icap_connect_timeout``
310+
- ``5``
311+
- Timeout in seconds for establishing the TCP or TLS connection to the
312+
ICAP server. The read timeout on an established connection is fixed at
313+
600 seconds.
314+
315+
All of these can be set with ``occ``, for example::
316+
317+
sudo -E -u www-data php occ config:app:set files_antivirus av_host --value="icap.example.com"
318+
sudo -E -u www-data php occ config:app:set files_antivirus av_port --value="1344"
319+
sudo -E -u www-data php occ config:app:set files_antivirus av_icap_request_service --value="avscan"
320+
sudo -E -u www-data php occ config:app:set files_antivirus av_icap_mode --value="reqmod"
321+
sudo -E -u www-data php occ config:app:set files_antivirus av_icap_response_header --value="X-Infection-Found"
322+
sudo -E -u www-data php occ config:app:set files_antivirus av_icap_tls --value="true"
323+
sudo -E -u www-data php occ config:app:set files_antivirus av_icap_chunk_size --value="1048576"
324+
sudo -E -u www-data php occ config:app:set files_antivirus av_icap_connect_timeout --value="5"
325+
326+
The Antivirus Configuration panel exposes the mode, host, port, service,
327+
virus response header and TLS switch, plus a preset selector that fills in
328+
service, header and mode for ClamAV / c-icap, Kaspersky and FortiSandbox.
329+
The chunk size and the connection timeout can only be set with ``occ``.
330+
331+
The settings shared with the other modes also apply in ICAP mode: the
332+
streaming limits ``av_stream_max_length``, ``av_max_file_size`` and
333+
``av_scan_first_bytes`` (see `What is sent to the scanning service`_), and the
334+
handling options ``av_block_unscannable``, ``av_block_unreachable`` and
335+
``av_infected_action`` (see `How ICAP responses are interpreted`_).
336+
337+
Choosing between REQMOD and RESPMOD
338+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
339+
340+
ICAP servers usually offer both request modification (REQMOD) and response
341+
modification (RESPMOD) services, and expect the client to use the one the
342+
service was configured for. The app builds a synthetic HTTP message around the
343+
file in both cases:
344+
345+
* ``reqmod`` sends an ICAP ``REQMOD`` request whose encapsulated HTTP request
346+
is ``PUT <path> HTTP/1.0`` with a ``Host: nextcloud`` header, followed by
347+
the file contents as the request body.
348+
349+
* ``respmod`` sends an ICAP ``RESPMOD`` request whose encapsulated HTTP
350+
request is ``GET <path> HTTP/1.0`` with a ``Host: nextcloud`` header, plus a
351+
synthetic HTTP response consisting of ``HTTP/1.0 200 OK`` and a
352+
``Content-Length`` header, followed by the file contents as the response
353+
body.
354+
355+
Both variants send ``Allow: 204``, so that the scanner may answer with
356+
``204 No Content`` for a file it does not want to modify, and an
357+
``X-Client-IP`` header (see `What is sent to the scanning service`_). In both
358+
cases the body is transferred with chunked encoding, so the scanner receives
359+
the file as a stream.
360+
361+
The practical difference is the ``Content-Length`` header: RESPMOD tells the
362+
scanner the size of the object before the body arrives, REQMOD does not.
363+
Scanners that need to decide up front whether they will accept an object, or
364+
that queue large objects differently, therefore work better with RESPMOD.
365+
Pick the mode your ICAP service expects; if the vendor documents both, prefer
366+
RESPMOD for large files.
367+
368+
.. note:: In RESPMOD the announced ``Content-Length`` is the real file size
369+
only when the size is known at the time the scan starts, which is the case
370+
for background scans and for uploads that provide a length. Where the size
371+
is not known, a dummy length of 1 byte is announced instead. This is
372+
accepted by the scanners tested, but it is a reason why a scanner that
373+
strictly validates ``Content-Length`` against the body may reject requests.
374+
375+
The virus response header
376+
~~~~~~~~~~~~~~~~~~~~~~~~~
377+
378+
When the ICAP server reports an infection it names the threat in a response
379+
header, and the name of that header differs per vendor. ``av_icap_response_header``
380+
must match exactly what your scanner sends: ``c-icap`` with ClamAV uses
381+
``X-Infection-Found``, Kaspersky uses ``X-Virus-ID``, and other products use
382+
other names. The value of that header becomes the threat name that is logged
383+
and shown to the user.
384+
385+
.. warning:: A wrong ``av_icap_response_header`` fails silently and in the
386+
unsafe direction. If the configured header is not the one the scanner
387+
sends, no threat name is ever found, the response is treated as clean, and
388+
infected files are accepted without any error being logged. Always verify
389+
the setting against a real detection instead of assuming that an
390+
error-free upload means scanning works.
391+
392+
Two vendor behaviours are recognised without the header and therefore keep
393+
working if it is misconfigured: Kaspersky in product editions before 2020 and
394+
McAfee report an infection as an encapsulated HTTP status of
395+
``403 Forbidden`` or ``403 VirusFound``, which the app also treats as
396+
infected.
397+
398+
Verifying the configuration
399+
~~~~~~~~~~~~~~~~~~~~~~~~~~~
400+
401+
Use the built-in test command, which scans harmless content and two variants
402+
of the EICAR test file through the configured backend::
403+
404+
sudo -E -u www-data php occ files_antivirus:test
405+
406+
The command fails if the clean content is reported as infected, or if either
407+
EICAR sample is reported as clean, which is exactly the failure mode of a
408+
wrong virus response header. It reports files that could not be scanned or
409+
were not scanned separately, so a run that reports pending or unscannable
410+
results is not a confirmation that detection works.
411+
412+
ICAP mode also supports debug output, which prints the generated ICAP request
413+
headers and the raw ICAP response. This is the quickest way to see which
414+
header a scanner actually returns::
415+
416+
sudo -E -u www-data php occ files_antivirus:test --debug
417+
418+
Saving the settings in the Antivirus Configuration panel also runs a scan of
419+
dummy content and reports the result, so a saved configuration that cannot
420+
reach the ICAP server is reported immediately.
421+
422+
How ICAP responses are interpreted
423+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
424+
425+
ICAP mode distinguishes more outcomes than clean and infected:
426+
427+
.. list-table::
428+
:header-rows: 1
429+
:widths: 30 20 50
430+
431+
* - ICAP response
432+
- Result
433+
- Handling
434+
* - ``200`` or ``204``, no virus response header and no encapsulated
435+
``403``
436+
- Clean
437+
- The file is accepted.
438+
* - ``200`` with the configured virus response header, or an encapsulated
439+
HTTP status containing ``403 Forbidden`` or ``403 VirusFound``
440+
- Infected
441+
- The upload is rejected and the file is deleted, see
442+
`Where scanning happens`_.
443+
* - ``500`` with ``X-Error-Code: file_type_blocked`` or
444+
``file_extension_blocked``
445+
- Infected
446+
- Treated exactly like a malware detection, so an ICAP scanner can also
447+
enforce a file type policy for Nextcloud uploads.
448+
* - ``500`` with ``X-Error-Code: decode_error``,
449+
``max_archive_layers_exceeded`` or ``password_protected``
450+
- Unscannable
451+
- Accepted or rejected according to ``av_block_unscannable``.
452+
* - ``202``
453+
- Not checked
454+
- Accepted or rejected according to ``av_block_unreachable``.
455+
* - Any other response, including ``500`` with an unknown or absent
456+
``X-Error-Code``
457+
- Error
458+
- The scan fails with an error and the upload does not complete.
459+
460+
Two settings decide what happens to a file the scanner did not clear:
461+
462+
``av_block_unscannable``
463+
Default ``false``. Files the scanner reported as unscannable, such as
464+
password-protected archives or archives nested more deeply than the scanner
465+
will unpack, are **accepted** with the default setting. Set it to ``true``
466+
to reject them instead::
467+
468+
sudo -E -u www-data php occ config:app:set files_antivirus av_block_unscannable --value="true"
469+
470+
``av_block_unreachable``
471+
Default ``true``. With the default setting, a file is rejected when the ICAP
472+
server cannot be reached at all or answers ``202``, and the user is told
473+
that the upload cannot be completed. Set it to ``false`` to accept uploads
474+
while the scanner is unavailable; such files are not marked as scanned, so
475+
the background scanner picks them up later.
476+
477+
.. warning:: These two defaults mean that, out of the box, a file that could
478+
not be checked because it is unscannable is treated as safe and stored,
479+
while a file that could not be checked because the scanner was unreachable
480+
is refused. Decide deliberately which behaviour you want before relying on
481+
ICAP scanning as a control.
482+
483+
Transport security
484+
~~~~~~~~~~~~~~~~~~
485+
486+
By default, ``av_icap_tls`` is ``false`` and the ICAP connection is a plain
487+
TCP connection. With TLS enabled::
488+
489+
sudo -E -u www-data php occ config:app:set files_antivirus av_icap_tls --value="true"
490+
491+
the connection is established as ``tls://<av_host>:<av_port>`` with peer and
492+
peer name verification enabled, validated against the CA bundle managed by
493+
Nextcloud. A scanner presenting a self-signed or private-CA certificate
494+
therefore needs that certificate imported into Nextcloud, in the same way as
495+
for external storage.
496+
497+
.. warning:: ICAP has no transport security of its own. With
498+
``av_icap_tls`` disabled, the complete contents of every scanned file, the
499+
file path and the uploader's IP address travel to the ICAP server in
500+
plaintext. Only leave TLS off when the connection stays on a network you
501+
fully control, and never for a remote or cloud-hosted scanning service.
502+
503+
What is sent to the scanning service
504+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
505+
506+
For a data protection assessment, this is what leaves the Nextcloud server on
507+
every ICAP scan:
508+
509+
* **The file contents.** The full bytes of the file, streamed to the scanner
510+
in chunks of ``av_icap_chunk_size``, subject to the limits below.
511+
512+
* **The file path**, in the URI of the encapsulated HTTP request and
513+
URL-encoded segment by segment. This is the internal absolute path of the
514+
file, so for a file in an account's own storage it has the form
515+
``/<account>/files/<path>``: the account's user ID is part of it. For
516+
chunked WebDAV uploads, which are written to a temporary
517+
``.ocTransferId<number>.part`` file, the app recovers the real destination
518+
path from the WebDAV request URI so that the scanner sees the intended file
519+
name and extension rather than the ``.part`` name.
520+
521+
* **The uploader's IP address**, in the ``X-Client-IP`` header, whenever the
522+
scan is triggered by a request that has a remote address. Background scans
523+
have none, so the header is empty there.
524+
525+
* **Fixed protocol headers** that carry no information about the instance: a
526+
constant ``User-Agent``, the ICAP ``Host`` header, which is the configured
527+
``av_host`` and thus the scanner's own name, and a literal
528+
``Host: nextcloud`` in the encapsulated HTTP request.
529+
530+
No account name, display name, email address, group membership, share
531+
information, comment or tag is sent. Nothing is transmitted back into
532+
Nextcloud from the scanner except the ICAP status code and headers, and the
533+
threat name from the virus response header is stored and logged with the scan
534+
result.
535+
536+
Three settings bound how much of a file is sent, and all three change what
537+
the verdict is actually based on:
538+
539+
``av_scan_first_bytes``
540+
Default ``-1``, meaning the whole file. With a positive value, only about
541+
the first that many bytes of a file are sent, so the scanner returns a
542+
verdict on a prefix of the file and malware later in the file is not seen.
543+
544+
``av_stream_max_length``
545+
Default ``26214400`` (25 MiB). A file larger than this is not sent over a
546+
single ICAP connection: the request is finished and evaluated, and a new
547+
ICAP request is opened for the following bytes. Each segment is scanned on
548+
its own, and a detection in any segment marks the file as infected.
549+
Detections that depend on seeing a whole object, such as a large archive,
550+
can be missed when the object spans a segment boundary, so raise this limit
551+
rather than lowering it if your scanner and the PHP ``memory_limit`` allow
552+
for it.
553+
554+
``av_max_file_size``
555+
Default ``-1``, meaning no limit. With a positive value, files larger than
556+
this are excluded from background scanning entirely, and chunked WebDAV
557+
uploads larger than this are not scanned at all. They are stored unscanned;
558+
they are not partially scanned.
559+
560+
.. warning:: If your ICAP server is operated by a third party or hosted
561+
outside your own infrastructure, then enabling ICAP mode means you are
562+
transferring file contents, file paths including account user IDs, and
563+
client IP addresses to that third party for every upload and every
564+
background scan. Cover this in your processing records and contracts
565+
before enabling it.
566+
567+
Where scanning happens
568+
~~~~~~~~~~~~~~~~~~~~~~
569+
570+
ICAP scanning is not a separate code path; it is the same pipeline the ClamAV
571+
modes use, so the following applies to all modes:
572+
573+
* Uploads are scanned while they are being written. The app installs a storage
574+
wrapper that observes every write, so the data is streamed to the scanner as
575+
it arrives rather than after the file is complete.
576+
577+
* When the verdict is "infected", the newly written file is deleted and the
578+
upload is refused with the message *Virus <name> is detected in the file.
579+
Upload cannot be completed.* The trash bin is paused while the file is
580+
deleted, so the infected file does not end up in the user's trash. An entry
581+
is written to the log and to the user's activity stream.
582+
583+
* Files that already existed, files that were accepted while the scanner was
584+
unavailable, and files whose scan is older than ``av_rescan_days`` (default
585+
``28``) are handled by the background scanner, which scans in batches on the
586+
cron schedule. For infected files found by the background scanner, the
587+
action is controlled by ``av_infected_action``, which defaults to
588+
``only_log``. See `Manage the background scanner`_.
589+
590+
* Some content is never sent to the scanner: end-to-end encrypted files and
591+
their metadata, and anything under a directory listed in
592+
``av_blocklisted_directories``.
593+
240594

241595
Disabling background scan task
242596
------------------------------

0 commit comments

Comments
 (0)