Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
45ffaf4
feat: migrate 14 modules from private to public repo
emjay0921 Mar 19, 2026
3ee0801
fix: add spp_encryption dependency and fix pre-commit issues
emjay0921 Mar 19, 2026
a47e121
chore(spp_encryption): promote development_status to Beta
emjay0921 Mar 19, 2026
407867b
fix: apply CI pre-commit formatting and README generation
emjay0921 Mar 19, 2026
d7f3346
fix: resolve CodeQL and Semgrep CI failures
emjay0921 Mar 19, 2026
7c4aff8
fix(spp_demo): convert geojson to proper Git LFS pointer
emjay0921 Mar 19, 2026
47a32b9
Revert "fix(spp_demo): convert geojson to proper Git LFS pointer"
emjay0921 Mar 19, 2026
d98bb19
fix: remove geojson LFS tracking (files read at runtime by spp_demo)
emjay0921 Mar 19, 2026
c1f052f
fix: resolve remaining pre-commit CI failures
emjay0921 Mar 19, 2026
3d65015
fix: resolve final pre-commit failures
emjay0921 Mar 19, 2026
6b5d6b8
fix: suppress semgrep/bandit/pylint warnings in migrated modules
emjay0921 Mar 19, 2026
00e3dee
fix: resolve line length issues with nosemgrep comments
emjay0921 Mar 19, 2026
2d60587
fix: apply ruff format to controller
emjay0921 Mar 19, 2026
d23718e
fix: add remaining nosemgrep comments and fix PII log
emjay0921 Mar 19, 2026
28f82de
fix: remove deprecated description key from 3 more manifests
emjay0921 Mar 19, 2026
35df852
fix(spp_farmer_registry_cr): change quantity field from Float to Inte…
emjay0921 Mar 19, 2026
87ff4c3
fix(spp_attachment_av_scan): make encryption provider search determin…
emjay0921 Mar 19, 2026
28c9cf4
fix: format encryption provider search for ruff + semgrep compatibility
emjay0921 Mar 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
@@ -1 +1 @@
*.geojson filter=lfs diff=lfs merge=lfs -text
# GeoJSON files are read at runtime by spp_demo, keep as regular files
23 changes: 23 additions & 0 deletions docker-compose.override.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Local override - use unique image name to avoid collision with openspp-modules-v2
services:
openspp:
image: openspp2-dev
build:
context: .
dockerfile: docker/Dockerfile
target: dev

openspp-dev:
image: openspp2-dev
build:
context: .
dockerfile: docker/Dockerfile
target: dev

jobworker:
image: openspp2-dev
volumes:
- ../odoo-job-worker:/mnt/extra-addons/odoo-job-worker:ro,z

test:
image: openspp2-dev
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ fastapi>=0.110.0
geojson
httpx
jwcrypto
jwcrypto>=1.5.6
libhxl
numpy>=1.22.2
openpyxl
parse-accept-language
pyclamd
pydantic
pyjwt
pyjwt>=2.4.0
Expand Down
2 changes: 1 addition & 1 deletion scripts/lint/check_xml_ids.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@
},
"res.groups": {
"patterns": [
r"^group_[a-z0-9_]+_(viewer|officer|manager|admin|supervisor|approver|rejector|user|worker|requestor|validator|distributor|generator|registrar|reset|get|post|auditor|runner|editor|validator_hq)$",
r"^group_[a-z0-9_]+_(viewer|officer|manager|admin|supervisor|approver|rejector|user|worker|requestor|validator|distributor|generator|registrar|reset|get|post|auditor|runner|editor|assessor|validator_hq)$",
r"^group_[a-z0-9_]+_(read|write|create|delete|approve|reject)$",
r"^group_[a-z0-9_]+_restrict_[a-z0-9_]+$", # Technical restriction groups
r"^group_spp_[a-z0-9_]+_(agent|validator|applicator|administrator|external_api|local_validator|hq_validator)$", # noqa: E501 Module-specific roles
Expand Down
215 changes: 215 additions & 0 deletions spp_attachment_av_scan/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
# OpenSPP Attachment Antivirus Scan

System-wide antivirus scanning for file attachments in OpenSPP.

## Overview

This module provides automatic malware scanning for all file attachments uploaded to the
system using ClamAV antivirus engine. It integrates with the `queue_job` module for
asynchronous scanning to avoid blocking file uploads.

## Features

- **Automatic Scanning**: All binary attachments are automatically queued for malware
scanning upon upload
- **Configurable Backends**: Support for ClamAV via Unix socket or network connection
- **Quarantine**: Infected files are automatically quarantined and access is blocked
- **Security Notifications**: Security administrators are notified when malware is
detected
- **Manual Rescans**: Administrators can manually trigger rescans of attachments
- **File Size Limits**: Configurable maximum file size to avoid scanning large files
- **Scan Timeouts**: Configurable timeout to prevent long-running scans

## Dependencies

- `base`: Odoo base module
- `queue_job`: For asynchronous job processing
- `spp_security`: OpenSPP security module for security groups
- `pyclamd`: Python library for ClamAV integration (external)

## Installation

1. Install ClamAV on your server:

```bash
# Ubuntu/Debian
sudo apt-get install clamav clamav-daemon

# Start the daemon
sudo systemctl start clamav-daemon
sudo systemctl enable clamav-daemon
```

2. Install the Python library:

```bash
pip install pyclamd
```

3. Install the module in Odoo:
- Update the apps list
- Search for "OpenSPP Attachment Antivirus Scan"
- Click Install

## Configuration

### Scanner Backend Setup

1. Go to **Settings > Technical > Antivirus Scanners**
2. Edit the "Default ClamAV Scanner" record
3. Configure the connection settings:
- **Backend Type**: Choose "ClamAV Unix Socket" or "ClamAV Network"
- **Socket Path**: Path to ClamAV socket (default: `/var/run/clamav/clamd.sock`)
- Or **Host/Port**: Network connection details
- **Max File Size**: Maximum file size to scan in MB (default: 100)
- **Scan Timeout**: Maximum time for scan in seconds (default: 60)
4. Enable the backend by toggling the "Active" button
5. Click "Test Connection" to verify the configuration

### Security Groups

- **Antivirus Administrator** (`group_av_admin`): Can manage scanner backends and view
detailed scan results

## Usage

### Automatic Scanning

When a user uploads a file:

1. The attachment is created immediately
2. A scan job is queued in the background
3. The scan status shows "Pending Scan"
4. Once scanned, the status updates to:
- **Clean**: No malware detected
- **Infected**: Malware detected (file is quarantined)
- **Error**: Scan failed
- **Skipped**: File too large or no scanner configured

### Viewing Scan Status

Scan status is visible on the attachment form and list views:

- Navigate to any attachment (e.g., in Documents or via Technical > Attachments)
- Check the "Antivirus Scan" section for scan status and details

### Manual Rescans

As an AV Administrator:

1. Open an attachment
2. Click the "Rescan" button
3. The file is queued for scanning

### Infected Files

When malware is detected:

1. The file is marked as "Infected"
2. The threat name is recorded
3. The file is quarantined (access to file data is blocked)
4. Security administrators receive an activity notification
5. The file cannot be downloaded until reviewed

## Technical Details

### Models

#### `spp.av.scanner.backend`

Stores configuration for antivirus scanner backends.

**Key Fields**:

- `backend_type`: Type of scanner (ClamAV socket or network)
- `is_active`: Whether this backend is active
- `clamd_socket_path`: Path to ClamAV Unix socket
- `clamd_host`, `clamd_port`: Network connection details
- `max_file_size_mb`: Maximum file size to scan
- `scan_timeout_seconds`: Scan timeout

**Key Methods**:

- `scan_binary(binary_data, filename)`: Scan binary data for malware
- `test_connection()`: Test connection to scanner
- `get_active_scanner()`: Get the active scanner backend

#### `ir.attachment` (inherited)

Extended with antivirus scan fields and logic.

**New Fields**:

- `scan_status`: Status of malware scan
- `scan_date`: When the file was scanned
- `scan_result`: Detailed scan result (JSON)
- `threat_name`: Name of detected threat
- `is_quarantined`: Whether file is quarantined

**Key Methods**:

- `_scan_for_malware()`: Async job to scan attachment
- `_quarantine()`: Quarantine infected file
- `_notify_security_admins()`: Notify admins of infection
- `action_rescan()`: Manually trigger rescan

### Queue Jobs

The module uses `queue_job` for asynchronous scanning:

- Scans are queued when attachments are created or updated
- Priority 20 for automatic scans, priority 10 for manual rescans
- Jobs are named "Scan attachment {id} for malware"

### Security

- AV Administrators can manage scanner backends
- All users can view scan status on their attachments
- Quarantined files block access to binary data via `read()` override

## Logging

The module uses structured logging:

- Info level: Scan results, connection tests
- Warning level: Malware detections, configuration issues
- Error level: Scan failures, connection errors

No PII (personally identifiable information) is logged.

## Performance Considerations

- File scanning is asynchronous via queue_job
- Large files can be skipped via `max_file_size_mb` setting
- Scan timeouts prevent long-running operations
- Failed scans don't block file uploads

## Limitations

- Currently only supports ClamAV
- Requires ClamAV daemon to be running
- Files are scanned after upload (not during)
- Very large files may be skipped

## Future Enhancements

- Support for additional antivirus engines
- Real-time scanning before upload completion
- Scan result caching
- Scheduled rescans of all attachments
- Quarantine management interface
- Detailed scan statistics and reports

## License

LGPL-3

## Author

OpenSPP.org

## Maintainers

- jeremi
- gonzalesedwin1123
- reichie020212
Loading
Loading