diff --git a/Dockerfile b/Dockerfile index 4568784..3d9440a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -121,7 +121,6 @@ RUN echo "@edge https://dl-cdn.alpinelinux.org/alpine/edge/community" >> /etc/ap perl-xml-libxml \ postfix \ postfix-pcre \ - rspamd \ spamassassin \ spamassassin-client \ supervisor \ @@ -144,11 +143,8 @@ RUN mkdir -p /etc/happydeliver \ /var/lib/authentication_milter \ /var/spool/postfix/authentication_milter \ /var/spool/postfix/spamassassin \ - /var/spool/postfix/rspamd \ && chown -R happydeliver:happydeliver /var/lib/happydeliver /var/log/happydeliver \ - && chown -R mail:mail /var/spool/postfix/authentication_milter /var/spool/postfix/spamassassin \ - && chown rspamd:mail /var/spool/postfix/rspamd \ - && chmod 750 /var/spool/postfix/rspamd + && chown -R mail:mail /var/spool/postfix/authentication_milter /var/spool/postfix/spamassassin # Copy the built application COPY --from=builder /build/happyDeliver /usr/local/bin/happyDeliver @@ -158,7 +154,6 @@ RUN chmod +x /usr/local/bin/happyDeliver COPY docker/postfix/ /etc/postfix/ COPY docker/authentication_milter/authentication_milter.json /etc/authentication_milter.json COPY docker/spamassassin/ /etc/mail/spamassassin/ -COPY docker/rspamd/local.d/ /etc/rspamd/local.d/ COPY docker/supervisor/ /etc/supervisor/ COPY docker/entrypoint.sh /entrypoint.sh @@ -170,13 +165,7 @@ RUN chmod +x /entrypoint.sh EXPOSE 25 8080 # Default configuration -ENV HAPPYDELIVER_DATABASE_TYPE=sqlite \ - HAPPYDELIVER_DATABASE_DSN=/var/lib/happydeliver/happydeliver.db \ - HAPPYDELIVER_DOMAIN=happydeliver.local \ - HAPPYDELIVER_ADDRESS_PREFIX=test- \ - HAPPYDELIVER_DNS_TIMEOUT=5s \ - HAPPYDELIVER_HTTP_TIMEOUT=10s \ - HAPPYDELIVER_RSPAMD_API_URL=http://127.0.0.1:11334 +ENV HAPPYDELIVER_DATABASE_TYPE=sqlite HAPPYDELIVER_DATABASE_DSN=/var/lib/happydeliver/happydeliver.db HAPPYDELIVER_DOMAIN=happydeliver.local HAPPYDELIVER_ADDRESS_PREFIX=test- HAPPYDELIVER_DNS_TIMEOUT=5s HAPPYDELIVER_HTTP_TIMEOUT=10s HAPPYDELIVER_RBL=zen.spamhaus.org,bl.spamcop.net,b.barracudacentral.org,dnsbl.sorbs.net,dnsbl-1.uceprotect.net,bl.mailspike.net # Volume for persistent data VOLUME ["/var/lib/happydeliver", "/var/log/happydeliver"] diff --git a/README.md b/README.md index 4c4013b..3b28292 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ An open-source email deliverability testing platform that analyzes test emails a ## Features -- **Complete Email Analysis**: Analyzes SPF, DKIM, DMARC, BIMI, ARC, SpamAssassin and rspamd scores, DNS records, blacklist status, content quality, and more +- **Complete Email Analysis**: Analyzes SPF, DKIM, DMARC, BIMI, ARC, SpamAssassin scores, DNS records, blacklist status, content quality, and more - **REST API**: Full-featured API for creating tests and retrieving reports - **LMTP Server**: Built-in LMTP server for seamless MTA integration - **Scoring System**: Gives A to F grades and scoring with weighted factors across dns, authentication, spam, blacklists, content, and headers @@ -26,7 +26,6 @@ The easiest way to run happyDeliver is using the all-in-one Docker container tha - **Postfix MTA**: Receives emails on port 25 - **authentication_milter**: Entreprise grade email authentication - **SpamAssassin**: Spam scoring and analysis -- **rspamd**: Second spam filter for cross-validated scoring - **happyDeliver API**: REST API server on port 8080 - **SQLite Database**: Persistent storage for tests and reports @@ -163,27 +162,10 @@ The server will start on `http://localhost:8080` by default. #### 3. Integrate with your existing e-mail setup -It is expected your setup annotate the email with eg. opendkim, spamassassin, rspamd, ... +It is expected your setup annotate the email with eg. opendkim, spamassassin, ... happyDeliver will not perform thoses checks, it relies instead on standard software to have real world annotations. -#### Receiver Hostname - -happyDeliver filters `Authentication-Results` headers by hostname to only trust headers added by your MTA (and not headers that may have been injected by the sender). By default, it uses the system hostname (`os.Hostname()`). - -If your MTA's `authserv-id` (the hostname at the beginning of `Authentication-Results` headers) differs from the machine running happyDeliver, you must set it explicitly: - -```bash -./happyDeliver server -receiver-hostname mail.example.com -``` - -Or via environment variable: -```bash -HAPPYDELIVER_RECEIVER_HOSTNAME=mail.example.com ./happyDeliver server -``` - -**How to find the correct value:** look at the `Authentication-Results` headers in a received email. They start with the authserv-id, e.g. `Authentication-Results: mail.example.com; spf=pass ...` — in this case, use `mail.example.com`. - -If the value is misconfigured, happyDeliver will log a warning when the last `Received` hop doesn't match the expected hostname. +Choose one of the following way to integrate happyDeliver in your existing setup: #### Postfix LMTP Transport @@ -287,7 +269,7 @@ The deliverability score is calculated from A to F based on: - **Authentication**: IPRev, SPF, DKIM, DMARC, BIMI and ARC validation - **Blacklist**: RBL/DNSBL checks - **Headers**: Required headers, MIME structure, Domain alignment -- **Spam**: SpamAssassin and rspamd scores (combined 50/50) +- **Spam**: SpamAssassin score - **Content**: HTML quality, links, images, unsubscribe ## Funding diff --git a/api/openapi.yaml b/api/openapi.yaml index 225e26c..8463007 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -333,8 +333,6 @@ components: $ref: '#/components/schemas/AuthenticationResults' spamassassin: $ref: '#/components/schemas/SpamAssassinResult' - rspamd: - $ref: '#/components/schemas/RspamdResult' dns_results: $ref: '#/components/schemas/DNSResults' blacklists: @@ -350,19 +348,6 @@ components: listed: false - rbl: "bl.spamcop.net" listed: false - whitelists: - type: object - additionalProperties: - type: array - items: - $ref: '#/components/schemas/BlacklistCheck' - description: Map of IP addresses to their DNS whitelist check results (informational only) - example: - "192.0.2.1": - - rbl: "list.dnswl.org" - listed: false - - rbl: "swl.spamhaus.org" - listed: false content_analysis: $ref: '#/components/schemas/ContentAnalysis' header_analysis: @@ -416,7 +401,7 @@ components: type: integer minimum: 0 maximum: 100 - description: Spam filter score (SpamAssassin + rspamd combined, in percentage) + description: SpamAssassin score (in percentage) example: 15 spam_grade: type: string @@ -789,7 +774,7 @@ components: properties: result: type: string - enum: [pass, fail, invalid, missing, none, neutral, softfail, temperror, permerror, declined, domain_pass, orgdomain_pass, skipped] + enum: [pass, fail, invalid, missing, none, neutral, softfail, temperror, permerror, declined, domain_pass, orgdomain_pass] description: Authentication result example: "pass" domain: @@ -858,17 +843,6 @@ components: - is_spam - test_details properties: - deliverability_score: - type: integer - minimum: 0 - maximum: 100 - description: SpamAssassin deliverability score (0-100, higher is better) - example: 80 - deliverability_grade: - type: string - enum: [A+, A, B, C, D, E, F] - description: Letter grade for SpamAssassin deliverability score - example: "B" version: type: string description: SpamAssassin version @@ -926,71 +900,11 @@ components: format: float description: Score contribution of this test example: -1.9 - params: - type: string - description: Symbol parameters or options - example: "0.02" description: type: string description: Human-readable description of what this test checks example: "Bayes spam probability is 0 to 1%" - RspamdResult: - type: object - required: - - score - - threshold - - is_spam - - symbols - properties: - deliverability_score: - type: integer - minimum: 0 - maximum: 100 - description: rspamd deliverability score (0-100, higher is better) - example: 85 - deliverability_grade: - type: string - enum: [A+, A, B, C, D, E, F] - description: Letter grade for rspamd deliverability score - example: "A" - score: - type: number - format: float - description: rspamd spam score - example: -3.91 - threshold: - type: number - format: float - description: Score threshold for spam classification - example: 15.0 - action: - type: string - description: rspamd action (no action, add header, rewrite subject, soft reject, reject) - example: "no action" - is_spam: - type: boolean - description: Whether message is classified as spam (action is reject or soft reject) - example: false - server: - type: string - description: rspamd server that processed the message - example: "rspamd.example.com" - symbols: - type: object - additionalProperties: - $ref: '#/components/schemas/SpamTestDetail' - description: Map of triggered rspamd symbols to their details - example: - BAYES_HAM: - name: "BAYES_HAM" - score: -1.9 - params: "0.02" - report: - type: string - description: Full rspamd report (raw X-Spamd-Result header) - - DNSResults: type: object required: @@ -1331,7 +1245,7 @@ components: type: object required: - ip - - blacklists + - checks - listed_count - score - grade @@ -1340,7 +1254,7 @@ components: type: string description: The IP address that was checked example: "192.0.2.1" - blacklists: + checks: type: array items: $ref: '#/components/schemas/BlacklistCheck' @@ -1360,8 +1274,3 @@ components: enum: [A+, A, B, C, D, E, F] description: Letter grade representation of the score example: "A+" - whitelists: - type: array - items: - $ref: '#/components/schemas/BlacklistCheck' - description: List of DNS whitelist check results (informational only) diff --git a/docker/README.md b/docker/README.md index 2199eeb..3769365 100644 --- a/docker/README.md +++ b/docker/README.md @@ -110,38 +110,14 @@ Default configuration for the Docker environment: The container accepts these environment variables: - `HAPPYDELIVER_DOMAIN`: Email domain for test addresses (default: happydeliver.local) -- `HAPPYDELIVER_RECEIVER_HOSTNAME`: Hostname used to filter `Authentication-Results` headers (see below) -- `POSTFIX_CERT_FILE` / `POSTFIX_KEY_FILE`: TLS certificate and key paths for Postfix SMTP -### Receiver Hostname +Note that the hostname of the container is used to filter the authentication tests results. -happyDeliver filters `Authentication-Results` headers by hostname to only trust results from the expected MTA. By default, it uses the system hostname (i.e., the container's `--hostname`). - -In the all-in-one Docker container, the container hostname is also used as the `authserv-id` in the embedded Postfix and authentication_milter, so everything matches automatically. - -**When bypassing the embedded Postfix** (e.g., routing emails from your own MTA via LMTP), your MTA's `authserv-id` will likely differ from the container hostname. In that case, set `HAPPYDELIVER_RECEIVER_HOSTNAME` to your MTA's hostname: - -```bash -docker run -d \ - -e HAPPYDELIVER_DOMAIN=example.com \ - -e HAPPYDELIVER_RECEIVER_HOSTNAME=mail.example.com \ - ... -``` - -To find the correct value, look at the `Authentication-Results` headers in a received email — they start with the authserv-id, e.g. `Authentication-Results: mail.example.com; spf=pass ...`. - -If the value is misconfigured, happyDeliver will log a warning when the last `Received` hop doesn't match the expected hostname. - -Example (all-in-one, no override needed): +Example: ```bash docker run -e HAPPYDELIVER_DOMAIN=example.com --hostname mail.example.com ... ``` -Example (external MTA integration): -```bash -docker run -e HAPPYDELIVER_DOMAIN=example.com -e HAPPYDELIVER_RECEIVER_HOSTNAME=mail.example.com ... -``` - ## Volumes **Required volumes:** diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index ef45b61..1bc3eff 100644 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -15,10 +15,6 @@ mkdir -p /var/spool/postfix/authentication_milter chown mail:mail /var/spool/postfix/authentication_milter chmod 750 /var/spool/postfix/authentication_milter -mkdir -p /var/spool/postfix/rspamd -chown rspamd:mail /var/spool/postfix/rspamd -chmod 750 /var/spool/postfix/rspamd - # Create log directory mkdir -p /var/log/happydeliver /var/cache/authentication_milter /var/spool/authentication_milter /var/lib/authentication_milter /run/authentication_milter chown happydeliver:happydeliver /var/log/happydeliver diff --git a/docker/postfix/main.cf b/docker/postfix/main.cf index 5a73fb3..fcdb75c 100644 --- a/docker/postfix/main.cf +++ b/docker/postfix/main.cf @@ -28,7 +28,7 @@ transport_maps = pcre:/etc/postfix/transport_maps # OpenDKIM for DKIM verification milter_default_action = accept milter_protocol = 6 -smtpd_milters = unix:/var/spool/postfix/authentication_milter/authentication_milter.sock unix:/var/spool/postfix/spamassassin/spamass-milter.sock unix:/var/spool/postfix/rspamd/rspamd-milter.sock +smtpd_milters = unix:/var/spool/postfix/authentication_milter/authentication_milter.sock unix:/var/spool/postfix/spamassassin/spamass-milter.sock non_smtpd_milters = $smtpd_milters # SPF policy checking diff --git a/docker/rspamd/local.d/actions.conf b/docker/rspamd/local.d/actions.conf deleted file mode 100644 index f3ed60c..0000000 --- a/docker/rspamd/local.d/actions.conf +++ /dev/null @@ -1,5 +0,0 @@ -no_action = 0; -reject = null; -add_header = null; -rewrite_subject = null; -greylist = null; \ No newline at end of file diff --git a/docker/rspamd/local.d/milter_headers.conf b/docker/rspamd/local.d/milter_headers.conf deleted file mode 100644 index 378b8a3..0000000 --- a/docker/rspamd/local.d/milter_headers.conf +++ /dev/null @@ -1,5 +0,0 @@ -# Add "extended Rspamd headers" -extended_spam_headers = true; - -skip_local = false; -skip_authenticated = false; \ No newline at end of file diff --git a/docker/rspamd/local.d/options.inc b/docker/rspamd/local.d/options.inc deleted file mode 100644 index 485d0c9..0000000 --- a/docker/rspamd/local.d/options.inc +++ /dev/null @@ -1,3 +0,0 @@ -# rspamd options for happyDeliver -# Disable Bayes learning to keep the setup stateless -use_redis = false; diff --git a/docker/rspamd/local.d/worker-proxy.inc b/docker/rspamd/local.d/worker-proxy.inc deleted file mode 100644 index 04c9a1d..0000000 --- a/docker/rspamd/local.d/worker-proxy.inc +++ /dev/null @@ -1,6 +0,0 @@ -# Enable rspamd milter proxy worker via Unix socket for Postfix integration -bind_socket = "/var/spool/postfix/rspamd/rspamd-milter.sock mode=0660 owner=rspamd group=mail"; -upstream "local" { - default = yes; - self_scan = yes; -} diff --git a/docker/spamassassin/local.cf b/docker/spamassassin/local.cf index ce9a31c..c248ef6 100644 --- a/docker/spamassassin/local.cf +++ b/docker/spamassassin/local.cf @@ -48,14 +48,3 @@ rbl_timeout 5 # Don't use user-specific rules user_scores_dsn_timeout 3 user_scores_sql_override 0 - -# Disable Validity network rules -dns_query_restriction deny sa-trusted.bondedsender.org -dns_query_restriction deny sa-accredit.habeas.com -dns_query_restriction deny bl.score.senderscore.com -score RCVD_IN_VALIDITY_CERTIFIED_BLOCKED 0 -score RCVD_IN_VALIDITY_RPBL_BLOCKED 0 -score RCVD_IN_VALIDITY_SAFE_BLOCKED 0 -score RCVD_IN_VALIDITY_CERTIFIED 0 -score RCVD_IN_VALIDITY_RPBL 0 -score RCVD_IN_VALIDITY_SAFE 0 \ No newline at end of file diff --git a/docker/supervisor/supervisord.conf b/docker/supervisor/supervisord.conf index 74f1810..c0c7002 100644 --- a/docker/supervisor/supervisord.conf +++ b/docker/supervisor/supervisord.conf @@ -33,16 +33,6 @@ stderr_logfile=/var/log/happydeliver/authentication_milter.log user=mail group=mail -# rspamd spam filter -[program:rspamd] -command=/usr/bin/rspamd -f -u rspamd -g mail -autostart=true -autorestart=true -priority=11 -stdout_logfile=/var/log/happydeliver/rspamd.log -stderr_logfile=/var/log/happydeliver/rspamd_error.log -user=root - # SpamAssassin daemon [program:spamd] command=/usr/sbin/spamd --max-children 5 --helper-home-dir /var/lib/spamassassin --syslog stderr --pidfile /var/run/spamd.pid diff --git a/go.mod b/go.mod index 038eb22..e9da3d6 100644 --- a/go.mod +++ b/go.mod @@ -1,15 +1,15 @@ module git.happydns.org/happyDeliver -go 1.25.0 +go 1.24.6 require ( github.com/JGLTechnologies/gin-rate-limit v1.5.6 github.com/emersion/go-smtp v0.24.0 github.com/getkin/kin-openapi v0.133.0 - github.com/gin-gonic/gin v1.12.0 + github.com/gin-gonic/gin v1.11.0 github.com/google/uuid v1.6.0 - github.com/oapi-codegen/runtime v1.3.0 - golang.org/x/net v0.52.0 + github.com/oapi-codegen/runtime v1.1.2 + golang.org/x/net v0.49.0 gorm.io/driver/postgres v1.6.0 gorm.io/driver/sqlite v1.6.0 gorm.io/gorm v1.31.1 @@ -64,14 +64,14 @@ require ( github.com/ugorji/go/codec v1.3.1 // indirect github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect github.com/woodsbury/decimal128 v1.4.0 // indirect - go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect + go.uber.org/mock v0.6.0 // indirect golang.org/x/arch v0.23.0 // indirect - golang.org/x/crypto v0.49.0 // indirect - golang.org/x/mod v0.33.0 // indirect - golang.org/x/sync v0.20.0 // indirect - golang.org/x/sys v0.42.0 // indirect - golang.org/x/text v0.35.0 // indirect - golang.org/x/tools v0.42.0 // indirect + golang.org/x/crypto v0.47.0 // indirect + golang.org/x/mod v0.31.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/text v0.33.0 // indirect + golang.org/x/tools v0.40.0 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 10c9b72..96ea7bc 100644 --- a/go.sum +++ b/go.sum @@ -10,8 +10,12 @@ github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= +github.com/bytedance/sonic v1.14.2 h1:k1twIoe97C1DtYUo+fZQy865IuHia4PR5RPiuGPPIIE= +github.com/bytedance/sonic v1.14.2/go.mod h1:T80iDELeHiHKSc0C9tubFygiuXoGzrkjKzX2quAx980= github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE= github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k= +github.com/bytedance/sonic/loader v0.4.0 h1:olZ7lEqcxtZygCK9EKYKADnpQoYkRQxaeY2NYzevs+o= +github.com/bytedance/sonic/loader v0.4.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE= github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -36,16 +40,22 @@ github.com/emersion/go-smtp v0.24.0/go.mod h1:ZtRRkbTyp2XTHCA+BmyTFTrj8xY4I+b4Mc github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/gabriel-vasile/mimetype v1.4.11 h1:AQvxbp830wPhHTqc1u7nzoLT+ZFxGY7emj5DR5DYFik= +github.com/gabriel-vasile/mimetype v1.4.11/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ= github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE= github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= -github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8= -github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc= +github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= +github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls= +github.com/go-openapi/jsonpointer v0.22.2 h1:JDQEe4B9j6K3tQ7HQQTZfjR59IURhjjLxet2FB4KHyg= +github.com/go-openapi/jsonpointer v0.22.2/go.mod h1:0lBbqeRsQ5lIanv3LHZBrmRGHLHcQoOXQnf88fHlGWo= github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= +github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= +github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= @@ -56,6 +66,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688= +github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU= github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w= github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= @@ -63,6 +75,8 @@ github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -90,6 +104,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk= +github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= github.com/jackc/pgx/v5 v5.8.0 h1:TYPDoleBBme0xGSAX3/+NujXXtpZn9HBONkQC7IEZSo= github.com/jackc/pgx/v5 v5.8.0/go.mod h1:QVeDInX2m9VyzvNeiCJVjCkNFqzsNb43204HshNSZKw= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= @@ -118,6 +134,8 @@ github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8 github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= +github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/go-sqlite3 v1.14.33 h1:A5blZ5ulQo2AtayQ9/limgHEkFreKj1Dv226a1K73s0= github.com/mattn/go-sqlite3 v1.14.33/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -132,8 +150,8 @@ github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oapi-codegen/oapi-codegen/v2 v2.5.1 h1:5vHNY1uuPBRBWqB2Dp0G7YB03phxLQZupZTIZaeorjc= github.com/oapi-codegen/oapi-codegen/v2 v2.5.1/go.mod h1:ro0npU1BWkcGpCgGD9QwPp44l5OIZ94tB3eabnT7DjQ= -github.com/oapi-codegen/runtime v1.3.0 h1:vyK1zc0gDWWXgk2xoQa4+X4RNNc5SL2RbTpJS/4vMYA= -github.com/oapi-codegen/runtime v1.3.0/go.mod h1:kOdeacKy7t40Rclb1je37ZLFboFxh+YLy0zaPCMibPY= +github.com/oapi-codegen/runtime v1.1.2 h1:P2+CubHq8fO4Q6fV1tqDBZHCwpVpvPg7oKiYzQgXIyI= +github.com/oapi-codegen/runtime v1.1.2/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg= github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw= github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c= @@ -158,8 +176,12 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= +github.com/quic-go/quic-go v0.57.0 h1:AsSSrrMs4qI/hLrKlTH/TGQeTMY0ib1pAOX7vA3AdqE= +github.com/quic-go/quic-go v0.57.0/go.mod h1:ly4QBAjHA2VhdnxhojRsCUOeJwKYg+taDlos92xb1+s= github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw= github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= +github.com/redis/go-redis/v9 v9.16.0 h1:OotgqgLSRCmzfqChbQyG1PHC3tLNR89DG4jdOERSEP4= +github.com/redis/go-redis/v9 v9.16.0/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370= github.com/redis/go-redis/v9 v9.17.2 h1:P2EGsA4qVIM3Pp+aPocCJ7DguDHhqrXNhVcEp4ViluI= github.com/redis/go-redis/v9 v9.17.2/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= @@ -194,8 +216,6 @@ github.com/vmware-labs/yaml-jsonpath v0.3.2/go.mod h1:U6whw1z03QyqgWdgXxvVnQ90zN github.com/woodsbury/decimal128 v1.4.0 h1:xJATj7lLu4f2oObouMt2tgGiElE5gO6mSWUjQsBgUlc= github.com/woodsbury/decimal128 v1.4.0/go.mod h1:BP46FUrVjVhdTbKT+XuQh2xfQaGki9LMIRJSFuh6THU= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE= -go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg= @@ -203,11 +223,11 @@ golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= -golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= -golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= +golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= +golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -215,13 +235,13 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= -golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= -golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -237,21 +257,24 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= -golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= -golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= +golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= -golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= +golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= +golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -264,6 +287,8 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/api/handlers.go b/internal/api/handlers.go index 470136e..80c8f9a 100644 --- a/internal/api/handlers.go +++ b/internal/api/handlers.go @@ -41,7 +41,7 @@ import ( type EmailAnalyzer interface { AnalyzeEmailBytes(rawEmail []byte, testID uuid.UUID) (reportJSON []byte, err error) AnalyzeDomain(domain string) (dnsResults *DNSResults, score int, grade string) - CheckBlacklistIP(ip string) (checks []BlacklistCheck, whitelists []BlacklistCheck, listedCount int, score int, grade string, err error) + CheckBlacklistIP(ip string) (checks []BlacklistCheck, listedCount int, score int, grade string, err error) } // APIHandler implements the ServerInterface for handling API requests @@ -359,7 +359,7 @@ func (h *APIHandler) CheckBlacklist(c *gin.Context) { } // Perform blacklist check using analyzer - checks, whitelists, listedCount, score, grade, err := h.analyzer.CheckBlacklistIP(request.Ip) + checks, listedCount, score, grade, err := h.analyzer.CheckBlacklistIP(request.Ip) if err != nil { c.JSON(http.StatusBadRequest, Error{ Error: "invalid_ip", @@ -372,8 +372,7 @@ func (h *APIHandler) CheckBlacklist(c *gin.Context) { // Build response response := BlacklistCheckResponse{ Ip: request.Ip, - Blacklists: checks, - Whitelists: &whitelists, + Checks: checks, ListedCount: listedCount, Score: score, Grade: BlacklistCheckResponseGrade(grade), diff --git a/internal/config/cli.go b/internal/config/cli.go index 77108ca..3accc99 100644 --- a/internal/config/cli.go +++ b/internal/config/cli.go @@ -34,12 +34,10 @@ func declareFlags(o *Config) { flag.StringVar(&o.Email.Domain, "domain", o.Email.Domain, "Domain used to receive emails") flag.StringVar(&o.Email.TestAddressPrefix, "address-prefix", o.Email.TestAddressPrefix, "Expected email adress prefix (deny address that doesn't start with this prefix)") flag.StringVar(&o.Email.LMTPAddr, "lmtp-addr", o.Email.LMTPAddr, "LMTP server listen address") - flag.StringVar(&o.Email.ReceiverHostname, "receiver-hostname", o.Email.ReceiverHostname, "Hostname used to filter Authentication-Results headers (defaults to os.Hostname())") flag.DurationVar(&o.Analysis.DNSTimeout, "dns-timeout", o.Analysis.DNSTimeout, "Timeout when performing DNS query") flag.DurationVar(&o.Analysis.HTTPTimeout, "http-timeout", o.Analysis.HTTPTimeout, "Timeout when performing HTTP query") flag.Var(&StringArray{&o.Analysis.RBLs}, "rbl", "Append a RBL (use this option multiple time to append multiple RBLs)") flag.BoolVar(&o.Analysis.CheckAllIPs, "check-all-ips", o.Analysis.CheckAllIPs, "Check all IPs found in email headers against RBLs (not just the first one)") - flag.StringVar(&o.Analysis.RspamdAPIURL, "rspamd-api-url", o.Analysis.RspamdAPIURL, "rspamd API URL for symbol descriptions (default: use embedded list)") flag.DurationVar(&o.ReportRetention, "report-retention", o.ReportRetention, "How long to keep reports (e.g., 720h, 30d). 0 = keep forever") flag.UintVar(&o.RateLimit, "rate-limit", o.RateLimit, "API rate limit (requests per second per IP)") flag.Var(&URL{&o.SurveyURL}, "survey-url", "URL for user feedback survey") diff --git a/internal/config/config.go b/internal/config/config.go index 9d803d0..4a335c9 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -34,11 +34,6 @@ import ( openapi_types "github.com/oapi-codegen/runtime/types" ) -func getHostname() string { - h, _ := os.Hostname() - return h -} - // Config represents the application configuration type Config struct { DevProxy string @@ -63,7 +58,6 @@ type EmailConfig struct { Domain string TestAddressPrefix string LMTPAddr string - ReceiverHostname string } // AnalysisConfig contains timeout and behavior settings for email analysis @@ -71,9 +65,7 @@ type AnalysisConfig struct { DNSTimeout time.Duration HTTPTimeout time.Duration RBLs []string - DNSWLs []string - CheckAllIPs bool // Check all IPs found in headers, not just the first one - RspamdAPIURL string // rspamd API URL for fetching symbol descriptions (empty = use embedded list) + CheckAllIPs bool // Check all IPs found in headers, not just the first one } // DefaultConfig returns a configuration with sensible defaults @@ -91,13 +83,11 @@ func DefaultConfig() *Config { Domain: "happydeliver.local", TestAddressPrefix: "test-", LMTPAddr: "127.0.0.1:2525", - ReceiverHostname: getHostname(), }, Analysis: AnalysisConfig{ DNSTimeout: 5 * time.Second, HTTPTimeout: 10 * time.Second, RBLs: []string{}, - DNSWLs: []string{}, CheckAllIPs: false, // By default, only check the first IP }, } diff --git a/internal/receiver/receiver.go b/internal/receiver/receiver.go index f06f535..062a091 100644 --- a/internal/receiver/receiver.go +++ b/internal/receiver/receiver.go @@ -98,17 +98,6 @@ func (r *EmailReceiver) ProcessEmailBytes(rawEmail []byte, recipientEmail string log.Printf("Analysis complete. Grade: %s. Score: %d/100", result.Report.Grade, result.Report.Score) - // Warn if the last Received hop doesn't match the expected receiver hostname - if r.config.Email.ReceiverHostname != "" && - result.Report.HeaderAnalysis != nil && - result.Report.HeaderAnalysis.ReceivedChain != nil && - len(*result.Report.HeaderAnalysis.ReceivedChain) > 0 { - lastHop := (*result.Report.HeaderAnalysis.ReceivedChain)[0] - if lastHop.By != nil && *lastHop.By != r.config.Email.ReceiverHostname { - log.Printf("WARNING: Last Received hop 'by' field (%s) does not match expected receiver hostname (%s): check your RECEIVER_HOSTNAME config as authentication results will be false", *lastHop.By, r.config.Email.ReceiverHostname) - } - } - // Marshal report to JSON reportJSON, err := json.Marshal(result.Report) if err != nil { diff --git a/pkg/analyzer/analyzer.go b/pkg/analyzer/analyzer.go index f21d1f8..e7ae561 100644 --- a/pkg/analyzer/analyzer.go +++ b/pkg/analyzer/analyzer.go @@ -41,13 +41,10 @@ type EmailAnalyzer struct { // NewEmailAnalyzer creates a new email analyzer with the given configuration func NewEmailAnalyzer(cfg *config.Config) *EmailAnalyzer { generator := NewReportGenerator( - cfg.Email.ReceiverHostname, cfg.Analysis.DNSTimeout, cfg.Analysis.HTTPTimeout, cfg.Analysis.RBLs, - cfg.Analysis.DNSWLs, cfg.Analysis.CheckAllIPs, - cfg.Analysis.RspamdAPIURL, ) return &EmailAnalyzer{ @@ -123,28 +120,22 @@ func (a *APIAdapter) AnalyzeDomain(domain string) (*api.DNSResults, int, string) return dnsResults, score, grade } -// CheckBlacklistIP checks a single IP address against DNS blacklists and whitelists -func (a *APIAdapter) CheckBlacklistIP(ip string) ([]api.BlacklistCheck, []api.BlacklistCheck, int, int, string, error) { +// CheckBlacklistIP checks a single IP address against DNS blacklists +func (a *APIAdapter) CheckBlacklistIP(ip string) ([]api.BlacklistCheck, int, int, string, error) { // Check the IP against all configured RBLs checks, listedCount, err := a.analyzer.generator.rblChecker.CheckIP(ip) if err != nil { - return nil, nil, 0, 0, "", err + return nil, 0, 0, "", err } // Calculate score using the existing function // Create a minimal RBLResults structure for scoring - results := &DNSListResults{ + results := &RBLResults{ Checks: map[string][]api.BlacklistCheck{ip: checks}, IPsChecked: []string{ip}, ListedCount: listedCount, } - score, grade := a.analyzer.generator.rblChecker.CalculateScore(results, false) + score, grade := a.analyzer.generator.rblChecker.CalculateRBLScore(results) - // Check the IP against all configured DNSWLs (informational only) - whitelists, _, err := a.analyzer.generator.dnswlChecker.CheckIP(ip) - if err != nil { - whitelists = nil - } - - return checks, whitelists, listedCount, score, grade, nil + return checks, listedCount, score, grade, nil } diff --git a/pkg/analyzer/authentication.go b/pkg/analyzer/authentication.go index 2beeb1f..07f7794 100644 --- a/pkg/analyzer/authentication.go +++ b/pkg/analyzer/authentication.go @@ -28,13 +28,11 @@ import ( ) // AuthenticationAnalyzer analyzes email authentication results -type AuthenticationAnalyzer struct { - receiverHostname string -} +type AuthenticationAnalyzer struct{} // NewAuthenticationAnalyzer creates a new authentication analyzer -func NewAuthenticationAnalyzer(receiverHostname string) *AuthenticationAnalyzer { - return &AuthenticationAnalyzer{receiverHostname: receiverHostname} +func NewAuthenticationAnalyzer() *AuthenticationAnalyzer { + return &AuthenticationAnalyzer{} } // AnalyzeAuthentication extracts and analyzes authentication results from email headers @@ -42,7 +40,7 @@ func (a *AuthenticationAnalyzer) AnalyzeAuthentication(email *EmailMessage) *api results := &api.AuthenticationResults{} // Parse Authentication-Results headers - authHeaders := email.GetAuthenticationResults(a.receiverHostname) + authHeaders := email.GetAuthenticationResults() for _, header := range authHeaders { a.parseAuthenticationResultsHeader(header, results) } @@ -152,32 +150,27 @@ func (a *AuthenticationAnalyzer) CalculateAuthenticationScore(results *api.Authe score := 0 - // Core authentication (90 points total) - // SPF (30 points) - score += 30 * a.calculateSPFScore(results) / 100 + // IPRev (15 points) + score += 15 * a.calculateIPRevScore(results) / 100 - // DKIM (30 points) - score += 30 * a.calculateDKIMScore(results) / 100 + // SPF (25 points) + score += 25 * a.calculateSPFScore(results) / 100 - // DMARC (30 points) - score += 30 * a.calculateDMARCScore(results) / 100 + // DKIM (23 points) + score += 23 * a.calculateDKIMScore(results) / 100 + + // X-Google-DKIM (optional) - penalty if failed + score += 12 * a.calculateXGoogleDKIMScore(results) / 100 + + // X-Aligned-From + score += 2 * a.calculateXAlignedFromScore(results) / 100 + + // DMARC (25 points) + score += 25 * a.calculateDMARCScore(results) / 100 // BIMI (10 points) score += 10 * a.calculateBIMIScore(results) / 100 - // Penalty-only: IPRev (up to -7 points on failure) - if iprevScore := a.calculateIPRevScore(results); iprevScore < 100 { - score += 7 * (iprevScore - 100) / 100 - } - - // Penalty-only: X-Google-DKIM (up to -12 points on failure) - score += 12 * a.calculateXGoogleDKIMScore(results) / 100 - - // Penalty-only: X-Aligned-From (up to -5 points on failure) - if xAlignedScore := a.calculateXAlignedFromScore(results); xAlignedScore < 100 { - score += 5 * (xAlignedScore - 100) / 100 - } - // Ensure score doesn't exceed 100 if score > 100 { score = 100 diff --git a/pkg/analyzer/authentication_arc_test.go b/pkg/analyzer/authentication_arc_test.go index 7f2f99e..9269d70 100644 --- a/pkg/analyzer/authentication_arc_test.go +++ b/pkg/analyzer/authentication_arc_test.go @@ -50,7 +50,7 @@ func TestParseARCResult(t *testing.T) { }, } - analyzer := NewAuthenticationAnalyzer("") + analyzer := NewAuthenticationAnalyzer() for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -136,7 +136,7 @@ func TestValidateARCChain(t *testing.T) { }, } - analyzer := NewAuthenticationAnalyzer("") + analyzer := NewAuthenticationAnalyzer() for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/analyzer/authentication_bimi_test.go b/pkg/analyzer/authentication_bimi_test.go index 7cb9c85..b1b5468 100644 --- a/pkg/analyzer/authentication_bimi_test.go +++ b/pkg/analyzer/authentication_bimi_test.go @@ -64,7 +64,7 @@ func TestParseBIMIResult(t *testing.T) { }, } - analyzer := NewAuthenticationAnalyzer("") + analyzer := NewAuthenticationAnalyzer() for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/analyzer/authentication_dkim_test.go b/pkg/analyzer/authentication_dkim_test.go index 3218639..2aab530 100644 --- a/pkg/analyzer/authentication_dkim_test.go +++ b/pkg/analyzer/authentication_dkim_test.go @@ -58,7 +58,7 @@ func TestParseDKIMResult(t *testing.T) { }, } - analyzer := NewAuthenticationAnalyzer("") + analyzer := NewAuthenticationAnalyzer() for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/analyzer/authentication_dmarc_test.go b/pkg/analyzer/authentication_dmarc_test.go index 3b8fb08..d7fda84 100644 --- a/pkg/analyzer/authentication_dmarc_test.go +++ b/pkg/analyzer/authentication_dmarc_test.go @@ -48,7 +48,7 @@ func TestParseDMARCResult(t *testing.T) { }, } - analyzer := NewAuthenticationAnalyzer("") + analyzer := NewAuthenticationAnalyzer() for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/analyzer/authentication_iprev.go b/pkg/analyzer/authentication_iprev.go index e799094..6538cbb 100644 --- a/pkg/analyzer/authentication_iprev.go +++ b/pkg/analyzer/authentication_iprev.go @@ -69,5 +69,5 @@ func (a *AuthenticationAnalyzer) calculateIPRevScore(results *api.Authentication } } - return 100 + return 0 } diff --git a/pkg/analyzer/authentication_iprev_test.go b/pkg/analyzer/authentication_iprev_test.go index 5b46995..d0529b5 100644 --- a/pkg/analyzer/authentication_iprev_test.go +++ b/pkg/analyzer/authentication_iprev_test.go @@ -93,7 +93,7 @@ func TestParseIPRevResult(t *testing.T) { }, } - analyzer := NewAuthenticationAnalyzer("") + analyzer := NewAuthenticationAnalyzer() for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -181,7 +181,7 @@ func TestParseAuthenticationResultsHeader_IPRev(t *testing.T) { }, } - analyzer := NewAuthenticationAnalyzer("") + analyzer := NewAuthenticationAnalyzer() for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/analyzer/authentication_spf.go b/pkg/analyzer/authentication_spf.go index fc41e3c..479c325 100644 --- a/pkg/analyzer/authentication_spf.go +++ b/pkg/analyzer/authentication_spf.go @@ -63,16 +63,6 @@ func (a *AuthenticationAnalyzer) parseLegacySPF(email *EmailMessage) *api.AuthRe return nil } - // Verify receiver matches our hostname - if a.receiverHostname != "" { - receiverRe := regexp.MustCompile(`receiver=([^\s;]+)`) - if matches := receiverRe.FindStringSubmatch(receivedSPF); len(matches) > 1 { - if matches[1] != a.receiverHostname { - return nil - } - } - } - result := &api.AuthResult{} // Extract result (first word) diff --git a/pkg/analyzer/authentication_spf_test.go b/pkg/analyzer/authentication_spf_test.go index 960aef5..7a84c49 100644 --- a/pkg/analyzer/authentication_spf_test.go +++ b/pkg/analyzer/authentication_spf_test.go @@ -60,7 +60,7 @@ func TestParseSPFResult(t *testing.T) { }, } - analyzer := NewAuthenticationAnalyzer("") + analyzer := NewAuthenticationAnalyzer() for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -161,7 +161,7 @@ func TestParseLegacySPF(t *testing.T) { }, } - analyzer := NewAuthenticationAnalyzer("") + analyzer := NewAuthenticationAnalyzer() for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/analyzer/authentication_test.go b/pkg/analyzer/authentication_test.go index 7122f53..27901b5 100644 --- a/pkg/analyzer/authentication_test.go +++ b/pkg/analyzer/authentication_test.go @@ -100,7 +100,7 @@ func TestGetAuthenticationScore(t *testing.T) { }, } - scorer := NewAuthenticationAnalyzer("") + scorer := NewAuthenticationAnalyzer() for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -247,7 +247,7 @@ func TestParseAuthenticationResultsHeader(t *testing.T) { }, } - analyzer := NewAuthenticationAnalyzer("") + analyzer := NewAuthenticationAnalyzer() for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -353,7 +353,7 @@ func TestParseAuthenticationResultsHeader(t *testing.T) { func TestParseAuthenticationResultsHeader_OnlyFirstResultParsed(t *testing.T) { // This test verifies that only the first occurrence of each auth method is parsed - analyzer := NewAuthenticationAnalyzer("") + analyzer := NewAuthenticationAnalyzer() t.Run("Multiple SPF results - only first is parsed", func(t *testing.T) { header := "mail.example.com; spf=pass smtp.mailfrom=first@example.com; spf=fail smtp.mailfrom=second@example.com" diff --git a/pkg/analyzer/authentication_x_aligned_from.go b/pkg/analyzer/authentication_x_aligned_from.go index eb0cf98..36da2b0 100644 --- a/pkg/analyzer/authentication_x_aligned_from.go +++ b/pkg/analyzer/authentication_x_aligned_from.go @@ -61,5 +61,5 @@ func (a *AuthenticationAnalyzer) calculateXAlignedFromScore(results *api.Authent } } - return 100 + return 0 } diff --git a/pkg/analyzer/authentication_x_aligned_from_test.go b/pkg/analyzer/authentication_x_aligned_from_test.go index 0fdd69d..220ac39 100644 --- a/pkg/analyzer/authentication_x_aligned_from_test.go +++ b/pkg/analyzer/authentication_x_aligned_from_test.go @@ -66,7 +66,7 @@ func TestParseXAlignedFromResult(t *testing.T) { }, } - analyzer := NewAuthenticationAnalyzer("") + analyzer := NewAuthenticationAnalyzer() for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -126,7 +126,7 @@ func TestCalculateXAlignedFromScore(t *testing.T) { }, } - analyzer := NewAuthenticationAnalyzer("") + analyzer := NewAuthenticationAnalyzer() for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/analyzer/authentication_x_google_dkim_test.go b/pkg/analyzer/authentication_x_google_dkim_test.go index f9704c0..be29a08 100644 --- a/pkg/analyzer/authentication_x_google_dkim_test.go +++ b/pkg/analyzer/authentication_x_google_dkim_test.go @@ -60,7 +60,7 @@ func TestParseXGoogleDKIMResult(t *testing.T) { }, } - analyzer := NewAuthenticationAnalyzer("") + analyzer := NewAuthenticationAnalyzer() for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/analyzer/content.go b/pkg/analyzer/content.go index d14d157..05aecfa 100644 --- a/pkg/analyzer/content.go +++ b/pkg/analyzer/content.go @@ -38,10 +38,9 @@ import ( // ContentAnalyzer analyzes email content (HTML, links, images) type ContentAnalyzer struct { - Timeout time.Duration - httpClient *http.Client - listUnsubscribeURLs []string // URLs from List-Unsubscribe header - hasOneClickUnsubscribe bool // True if List-Unsubscribe-Post: List-Unsubscribe=One-Click + Timeout time.Duration + httpClient *http.Client + listUnsubscribeURLs []string // URLs from List-Unsubscribe header } // NewContentAnalyzer creates a new content analyzer with configurable timeout @@ -116,10 +115,6 @@ func (c *ContentAnalyzer) AnalyzeContent(email *EmailMessage) *ContentResults { // Parse List-Unsubscribe header URLs for use in link detection c.listUnsubscribeURLs = email.GetListUnsubscribeURLs() - // Check for one-click unsubscribe support - listUnsubscribePost := email.Header.Get("List-Unsubscribe-Post") - c.hasOneClickUnsubscribe = strings.EqualFold(strings.TrimSpace(listUnsubscribePost), "List-Unsubscribe=One-Click") - // Get HTML and text parts htmlParts := email.GetHTMLParts() textParts := email.GetTextParts() @@ -737,7 +732,6 @@ func (c *ContentAnalyzer) GenerateContentAnalysis(results *ContentResults) *api. HasHtml: api.PtrTo(results.HTMLContent != ""), HasPlaintext: api.PtrTo(results.TextContent != ""), HasUnsubscribeLink: api.PtrTo(results.HasUnsubscribe), - UnsubscribeMethods: &[]api.ContentAnalysisUnsubscribeMethods{}, } // Calculate text-to-image ratio (inverse of image-to-text) @@ -884,19 +878,8 @@ func (c *ContentAnalyzer) GenerateContentAnalysis(results *ContentResults) *api. // Unsubscribe methods if results.HasUnsubscribe { - *analysis.UnsubscribeMethods = append(*analysis.UnsubscribeMethods, api.Link) - } - - for _, url := range c.listUnsubscribeURLs { - if strings.HasPrefix(url, "mailto:") { - *analysis.UnsubscribeMethods = append(*analysis.UnsubscribeMethods, api.Mailto) - } else if strings.HasPrefix(url, "http:") || strings.HasPrefix(url, "https:") { - *analysis.UnsubscribeMethods = append(*analysis.UnsubscribeMethods, api.ListUnsubscribeHeader) - } - } - - if slices.Contains(*analysis.UnsubscribeMethods, api.ListUnsubscribeHeader) && c.hasOneClickUnsubscribe { - *analysis.UnsubscribeMethods = append(*analysis.UnsubscribeMethods, api.OneClick) + methods := []api.ContentAnalysisUnsubscribeMethods{api.Link} + analysis.UnsubscribeMethods = &methods } return analysis diff --git a/pkg/analyzer/content_test.go b/pkg/analyzer/content_test.go index 4ad01a8..9289d95 100644 --- a/pkg/analyzer/content_test.go +++ b/pkg/analyzer/content_test.go @@ -144,74 +144,6 @@ func TestIsUnsubscribeLink(t *testing.T) { linkText: "Read more", expected: false, }, - // Multilingual keyword detection - URL path - { - name: "German abmelden in URL", - href: "https://example.com/abmelden?id=42", - linkText: "Click here", - expected: true, - }, - { - name: "French se-desabonner slug in URL (no accent/space - not detected by keyword)", - href: "https://example.com/se-desabonner?id=42", - linkText: "Click here", - expected: false, - }, - // Multilingual keyword detection - link text - { - name: "German Abmelden in link text", - href: "https://example.com/manage?id=42&lang=de", - linkText: "Abmelden", - expected: true, - }, - { - name: "French Se désabonner in link text", - href: "https://example.com/manage?id=42&lang=fr", - linkText: "Se désabonner", - expected: true, - }, - { - name: "Russian Отписаться in link text", - href: "https://example.com/manage?id=42&lang=ru", - linkText: "Отписаться", - expected: true, - }, - { - name: "Chinese 退订 in link text", - href: "https://example.com/manage?id=42&lang=zh", - linkText: "退订", - expected: true, - }, - { - name: "Japanese 登録を取り消す in link text", - href: "https://example.com/manage?id=42&lang=ja", - linkText: "登録を取り消す", - expected: true, - }, - { - name: "Korean 구독 해지 in link text", - href: "https://example.com/manage?id=42&lang=ko", - linkText: "구독 해지", - expected: true, - }, - { - name: "Dutch Uitschrijven in link text", - href: "https://example.com/manage?id=42&lang=nl", - linkText: "Uitschrijven", - expected: true, - }, - { - name: "Polish Odsubskrybuj in link text", - href: "https://example.com/manage?id=42&lang=pl", - linkText: "Odsubskrybuj", - expected: true, - }, - { - name: "Turkish Üyeliği sonlandır in link text", - href: "https://example.com/manage?id=42&lang=tr", - linkText: "Üyeliği sonlandır", - expected: true, - }, } analyzer := NewContentAnalyzer(5 * time.Second) diff --git a/pkg/analyzer/dns.go b/pkg/analyzer/dns.go index 29d8211..3098934 100644 --- a/pkg/analyzer/dns.go +++ b/pkg/analyzer/dns.go @@ -54,7 +54,7 @@ func NewDNSAnalyzerWithResolver(timeout time.Duration, resolver DNSResolver) *DN } // AnalyzeDNS performs DNS validation for the email's domain -func (d *DNSAnalyzer) AnalyzeDNS(email *EmailMessage, headersResults *api.HeaderAnalysis) *api.DNSResults { +func (d *DNSAnalyzer) AnalyzeDNS(email *EmailMessage, authResults *api.AuthenticationResults, headersResults *api.HeaderAnalysis) *api.DNSResults { // Extract domain from From address if headersResults.DomainAlignment.FromDomain == nil || *headersResults.DomainAlignment.FromDomain == "" { return &api.DNSResults{ @@ -104,14 +104,19 @@ func (d *DNSAnalyzer) AnalyzeDNS(email *EmailMessage, headersResults *api.Header // SPF validates the MAIL FROM command, which corresponds to Return-Path results.SpfRecords = d.checkSPFRecords(spfDomain) - // Check DKIM records by parsing DKIM-Signature headers directly - for _, sig := range parseDKIMSignatures(email.Header["Dkim-Signature"]) { - dkimRecord := d.checkDKIMRecord(sig.Domain, sig.Selector) - if dkimRecord != nil { - if results.DkimRecords == nil { - results.DkimRecords = new([]api.DKIMRecord) + // Check DKIM records (from authentication results) + // DKIM can be for any domain, but typically the From domain + if authResults != nil && authResults.Dkim != nil { + for _, dkim := range *authResults.Dkim { + if dkim.Domain != nil && dkim.Selector != nil { + dkimRecord := d.checkDKIMRecord(*dkim.Domain, *dkim.Selector) + if dkimRecord != nil { + if results.DkimRecords == nil { + results.DkimRecords = new([]api.DKIMRecord) + } + *results.DkimRecords = append(*results.DkimRecords, *dkimRecord) + } } - *results.DkimRecords = append(*results.DkimRecords, *dkimRecord) } } diff --git a/pkg/analyzer/dns_dkim.go b/pkg/analyzer/dns_dkim.go index 1a8a199..7ac858d 100644 --- a/pkg/analyzer/dns_dkim.go +++ b/pkg/analyzer/dns_dkim.go @@ -29,38 +29,6 @@ import ( "git.happydns.org/happyDeliver/internal/api" ) -// DKIMHeader holds the domain and selector extracted from a DKIM-Signature header. -type DKIMHeader struct { - Domain string - Selector string -} - -// parseDKIMSignatures extracts domain and selector from DKIM-Signature header values. -func parseDKIMSignatures(signatures []string) []DKIMHeader { - var results []DKIMHeader - for _, sig := range signatures { - var domain, selector string - for _, part := range strings.Split(sig, ";") { - kv := strings.SplitN(strings.TrimSpace(part), "=", 2) - if len(kv) != 2 { - continue - } - key := strings.TrimSpace(kv[0]) - val := strings.TrimSpace(kv[1]) - switch key { - case "d": - domain = val - case "s": - selector = val - } - } - if domain != "" && selector != "" { - results = append(results, DKIMHeader{Domain: domain, Selector: selector}) - } - } - return results -} - // checkapi.DKIMRecord looks up and validates DKIM record for a domain and selector func (d *DNSAnalyzer) checkDKIMRecord(domain, selector string) *api.DKIMRecord { // DKIM records are at: selector._domainkey.domain diff --git a/pkg/analyzer/dns_dkim_test.go b/pkg/analyzer/dns_dkim_test.go index 45da53c..8d94d20 100644 --- a/pkg/analyzer/dns_dkim_test.go +++ b/pkg/analyzer/dns_dkim_test.go @@ -26,220 +26,6 @@ import ( "time" ) -func TestParseDKIMSignatures(t *testing.T) { - tests := []struct { - name string - signatures []string - expected []DKIMHeader - }{ - { - name: "Empty input", - signatures: nil, - expected: nil, - }, - { - name: "Empty string", - signatures: []string{""}, - expected: nil, - }, - { - name: "Simple Gmail-style", - signatures: []string{ - `v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=from:to:subject:date:message-id; bh=abcdef1234567890=; b=SIGNATURE_DATA_HERE==`, - }, - expected: []DKIMHeader{{Domain: "gmail.com", Selector: "20210112"}}, - }, - { - name: "Microsoft 365 style", - signatures: []string{ - `v=1; a=rsa-sha256; c=relaxed/relaxed; d=contoso.com; s=selector1; h=From:Date:Subject:Message-ID; bh=UErATeHehIIPIXPeUA==; b=SIGNATURE_DATA==`, - }, - expected: []DKIMHeader{{Domain: "contoso.com", Selector: "selector1"}}, - }, - { - name: "Tab-folded multiline (Postfix-style)", - signatures: []string{ - "v=1; a=rsa-sha256; c=relaxed/simple; d=nemunai.re; s=thot;\r\n\tt=1760866834; bh=YNB7c8Qgm8YGn9X1FAXTcdpO7t4YSZFiMrmpCfD/3zw=;\r\n\th=From:To:Subject;\r\n\tb=T4TFaypMpsHGYCl3PGLwmzOYRF11rYjC7lF8V5VFU+ldvG8WBpFn==", - }, - expected: []DKIMHeader{{Domain: "nemunai.re", Selector: "thot"}}, - }, - { - name: "Space-folded multiline (RFC-style)", - signatures: []string{ - "v=1; a=rsa-sha256; c=relaxed/relaxed;\r\n d=football.example.com; i=@football.example.com;\r\n q=dns/txt; s=test; t=1528637909; h=from:to:subject;\r\n bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=;\r\n b=F45dVWDfMbQDGHJFlXUNB2HKfbCeLRyhDXgFpEL8Gwps==", - }, - expected: []DKIMHeader{{Domain: "football.example.com", Selector: "test"}}, - }, - { - name: "d= and s= on separate continuation lines", - signatures: []string{ - "v=1; a=rsa-sha256;\r\n\tc=relaxed/relaxed;\r\n\td=mycompany.com;\r\n\ts=selector1;\r\n\tbh=hash=;\r\n\tb=sig==", - }, - expected: []DKIMHeader{{Domain: "mycompany.com", Selector: "selector1"}}, - }, - { - name: "No space after semicolons", - signatures: []string{ - `v=1;a=rsa-sha256;c=relaxed/relaxed;d=example.net;s=mail;h=from:to:subject;bh=abc=;b=xyz==`, - }, - expected: []DKIMHeader{{Domain: "example.net", Selector: "mail"}}, - }, - { - name: "Multiple spaces after semicolons", - signatures: []string{ - `v=1; a=rsa-sha256; c=relaxed/relaxed; d=example.com; s=myselector; bh=hash=; b=sig==`, - }, - expected: []DKIMHeader{{Domain: "example.com", Selector: "myselector"}}, - }, - { - name: "Ed25519 signature (RFC 8463)", - signatures: []string{ - "v=1; a=ed25519-sha256; c=relaxed/relaxed;\r\n d=football.example.com; i=@football.example.com;\r\n q=dns/txt; s=brisbane; t=1528637909; h=from:to:subject;\r\n bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=;\r\n b=/gCrinpcQOoIfuHNQIbq4pgh9kyIK3AQ==", - }, - expected: []DKIMHeader{{Domain: "football.example.com", Selector: "brisbane"}}, - }, - { - name: "Multiple signatures (ESP double-signing)", - signatures: []string{ - `v=1; a=rsa-sha256; c=relaxed/relaxed; d=mydomain.com; s=mail; h=from:to:subject; bh=hash1=; b=sig1==`, - `v=1; a=rsa-sha256; c=relaxed/relaxed; d=sendib.com; s=mail; h=from:to:subject; bh=hash1=; b=sig2==`, - }, - expected: []DKIMHeader{ - {Domain: "mydomain.com", Selector: "mail"}, - {Domain: "sendib.com", Selector: "mail"}, - }, - }, - { - name: "Dual-algorithm signing (Ed25519 + RSA, same domain, different selectors)", - signatures: []string{ - `v=1; a=ed25519-sha256; c=relaxed/relaxed; d=football.example.com; s=brisbane; h=from:to:subject; bh=hash=; b=edSig==`, - `v=1; a=rsa-sha256; c=relaxed/relaxed; d=football.example.com; s=test; h=from:to:subject; bh=hash=; b=rsaSig==`, - }, - expected: []DKIMHeader{ - {Domain: "football.example.com", Selector: "brisbane"}, - {Domain: "football.example.com", Selector: "test"}, - }, - }, - { - name: "Amazon SES long selectors", - signatures: []string{ - `v=1; a=rsa-sha256; c=relaxed/simple; d=amazonses.com; s=224i4yxa5dv7c2xz3womw6peuabd; h=from:to:subject; bh=sesHash=; b=sesSig==`, - `v=1; a=rsa-sha256; c=relaxed/simple; d=customerdomain.io; s=ug7nbtf4gccmlpwj322ax3p6ow6fovbt; h=from:to:subject; bh=sesHash=; b=customSig==`, - }, - expected: []DKIMHeader{ - {Domain: "amazonses.com", Selector: "224i4yxa5dv7c2xz3womw6peuabd"}, - {Domain: "customerdomain.io", Selector: "ug7nbtf4gccmlpwj322ax3p6ow6fovbt"}, - }, - }, - { - name: "Subdomain in d=", - signatures: []string{ - `v=1; a=rsa-sha256; c=relaxed/relaxed; d=mail.example.co.uk; s=dkim2025; h=from:to:subject; bh=hash=; b=sig==`, - }, - expected: []DKIMHeader{{Domain: "mail.example.co.uk", Selector: "dkim2025"}}, - }, - { - name: "Deeply nested subdomain", - signatures: []string{ - `v=1; a=rsa-sha256; c=relaxed/relaxed; d=bounce.transactional.mail.example.com; s=s2048; h=from:to:subject; bh=hash=; b=sig==`, - }, - expected: []DKIMHeader{{Domain: "bounce.transactional.mail.example.com", Selector: "s2048"}}, - }, - { - name: "Selector with hyphens (Microsoft 365 custom domain style)", - signatures: []string{ - `v=1; a=rsa-sha256; c=relaxed/relaxed; d=example.com; s=selector1-contoso-com; h=from:to:subject; bh=hash=; b=sig==`, - }, - expected: []DKIMHeader{{Domain: "example.com", Selector: "selector1-contoso-com"}}, - }, - { - name: "Selector with dots", - signatures: []string{ - `v=1; a=rsa-sha256; c=relaxed/relaxed; d=example.com; s=smtp.mail; h=from:to:subject; bh=hash=; b=sig==`, - }, - expected: []DKIMHeader{{Domain: "example.com", Selector: "smtp.mail"}}, - }, - { - name: "Single-character selector", - signatures: []string{ - `v=1; a=rsa-sha256; c=relaxed/relaxed; d=tiny.io; s=x; h=from:to:subject; bh=hash=; b=sig==`, - }, - expected: []DKIMHeader{{Domain: "tiny.io", Selector: "x"}}, - }, - { - name: "Postmark-style timestamp selector, s= before d=", - signatures: []string{ - `v=1; a=rsa-sha1; c=relaxed/relaxed; s=20130519032151pm; d=postmarkapp.com; h=From:Date:Subject; bh=vYFvy46eesUDGJ45hyBTH30JfN4=; b=iHeFQ+7rCiSQs3DPjR2eUSZSv4i==`, - }, - expected: []DKIMHeader{{Domain: "postmarkapp.com", Selector: "20130519032151pm"}}, - }, - { - name: "d= and s= at the very end", - signatures: []string{ - `v=1; a=rsa-sha256; c=relaxed/relaxed; h=from:to:subject; bh=hash=; b=sig==; d=example.net; s=trailing`, - }, - expected: []DKIMHeader{{Domain: "example.net", Selector: "trailing"}}, - }, - { - name: "Full tag set", - signatures: []string{ - `v=1; a=rsa-sha256; d=example.com; s=selector1; c=relaxed/simple; q=dns/txt; i=user@example.com; t=1255993973; x=1256598773; h=From:Sender:Reply-To:Subject:Date:Message-Id:To:Cc; bh=+7qxGePcmmrtZAIVQAtkSSGHfQ/ftNuvUTWJ3vXC9Zc=; b=dB85+qM+If1KGQmqMLNpqLgNtUaG5dhGjYjQD6/QXtXmViJx8tf9gLEjcHr+musLCAvr0Fsn1DA3ZLLlUxpf4AR==`, - }, - expected: []DKIMHeader{{Domain: "example.com", Selector: "selector1"}}, - }, - { - name: "Missing d= tag", - signatures: []string{ - `v=1; a=rsa-sha256; c=relaxed/relaxed; s=selector1; h=from:to; bh=hash=; b=sig==`, - }, - expected: nil, - }, - { - name: "Missing s= tag", - signatures: []string{ - `v=1; a=rsa-sha256; c=relaxed/relaxed; d=example.com; h=from:to; bh=hash=; b=sig==`, - }, - expected: nil, - }, - { - name: "Missing both d= and s= tags", - signatures: []string{ - `v=1; a=rsa-sha256; c=relaxed/relaxed; h=from:to; bh=hash=; b=sig==`, - }, - expected: nil, - }, - { - name: "Mix of valid and invalid signatures", - signatures: []string{ - `v=1; a=rsa-sha256; c=relaxed/relaxed; d=good.com; s=sel1; h=from:to; bh=hash=; b=sig==`, - `v=1; a=rsa-sha256; c=relaxed/relaxed; s=orphan; h=from:to; bh=hash=; b=sig==`, - `v=1; a=rsa-sha256; c=relaxed/relaxed; d=also-good.com; s=sel2; h=from:to; bh=hash=; b=sig==`, - }, - expected: []DKIMHeader{ - {Domain: "good.com", Selector: "sel1"}, - {Domain: "also-good.com", Selector: "sel2"}, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result := parseDKIMSignatures(tt.signatures) - if len(result) != len(tt.expected) { - t.Fatalf("parseDKIMSignatures() returned %d results, want %d\n got: %+v\n want: %+v", len(result), len(tt.expected), result, tt.expected) - } - for i := range tt.expected { - if result[i].Domain != tt.expected[i].Domain { - t.Errorf("result[%d].Domain = %q, want %q", i, result[i].Domain, tt.expected[i].Domain) - } - if result[i].Selector != tt.expected[i].Selector { - t.Errorf("result[%d].Selector = %q, want %q", i, result[i].Selector, tt.expected[i].Selector) - } - } - }) - } -} - func TestValidateDKIM(t *testing.T) { tests := []struct { name string diff --git a/pkg/analyzer/headers.go b/pkg/analyzer/headers.go index 37718bb..b7ff3bb 100644 --- a/pkg/analyzer/headers.go +++ b/pkg/analyzer/headers.go @@ -109,13 +109,6 @@ func (h *HeaderAnalyzer) CalculateHeaderScore(analysis *api.HeaderAnalysis) (int maxGrade -= 1 } - // Check MIME-Version header (-5 points if present but not "1.0") - if check, exists := headers["mime-version"]; exists && check.Present { - if check.Valid != nil && !*check.Valid { - score -= 5 - } - } - // Check Message-ID format (10 points) if check, exists := headers["message-id"]; exists && check.Present { // If Valid is set and true, award points @@ -273,10 +266,6 @@ func (h *HeaderAnalyzer) GenerateHeaderAnalysis(email *EmailMessage, authResults headers[strings.ToLower(headerName)] = *check } - // Check MIME-Version header (recommended but absence is not penalized) - mimeVersionCheck := h.checkHeader(email, "MIME-Version", "recommended") - headers[strings.ToLower("MIME-Version")] = *mimeVersionCheck - // Check optional headers optionalHeaders := []string{"List-Unsubscribe", "List-Unsubscribe-Post"} for _, headerName := range optionalHeaders { @@ -331,21 +320,12 @@ func (h *HeaderAnalyzer) checkHeader(email *EmailMessage, headerName string, imp valid = false headerIssues = append(headerIssues, "Invalid Message-ID format (should be )") } - if len(email.Header["Message-Id"]) > 1 { - valid = false - headerIssues = append(headerIssues, fmt.Sprintf("Multiple Message-ID headers found (%d); only one is allowed", len(email.Header["Message-Id"]))) - } case "Date": // Validate date format if _, err := h.parseEmailDate(value); err != nil { valid = false headerIssues = append(headerIssues, fmt.Sprintf("Invalid date format: %v", err)) } - case "MIME-Version": - if value != "1.0" { - valid = false - headerIssues = append(headerIssues, fmt.Sprintf("MIME-Version should be '1.0', got '%s'", value)) - } case "From", "To", "Cc", "Bcc", "Reply-To", "Sender", "Resent-From", "Resent-To", "Return-Path": // Parse address header using net/mail and get normalized address if normalizedAddr, err := h.validateAddressHeader(value); err != nil { diff --git a/pkg/analyzer/parser.go b/pkg/analyzer/parser.go index 00de151..79d8310 100644 --- a/pkg/analyzer/parser.go +++ b/pkg/analyzer/parser.go @@ -28,9 +28,16 @@ import ( "mime/multipart" "net/mail" "net/textproto" + "os" "strings" ) +var hostname = "" + +func init() { + hostname, _ = os.Hostname() +} + // EmailMessage represents a parsed email message type EmailMessage struct { Header mail.Header @@ -211,18 +218,18 @@ func buildRawHeaders(header mail.Header) string { } // GetAuthenticationResults extracts Authentication-Results headers -// If receiverHostname is provided, only returns headers that begin with that hostname -func (e *EmailMessage) GetAuthenticationResults(receiverHostname string) []string { +// If hostname is provided, only returns headers that begin with that hostname +func (e *EmailMessage) GetAuthenticationResults() []string { allResults := e.Header[textproto.CanonicalMIMEHeaderKey("Authentication-Results")] // If no hostname specified, return all results - if receiverHostname == "" { + if hostname == "" { return allResults } // Filter results that begin with the specified hostname var filtered []string - prefix := receiverHostname + ";" + prefix := hostname + ";" for _, result := range allResults { // Trim whitespace and check if it starts with hostname; trimmed := strings.TrimSpace(result) @@ -249,33 +256,6 @@ func (e *EmailMessage) GetSpamAssassinHeaders() map[string]string { } for _, headerName := range saHeaders { - if values, ok := e.Header[headerName]; ok && len(values) > 0 { - for _, value := range values { - if strings.TrimSpace(value) != "" { - headers[headerName] = value - break - } - } - } else if value := e.Header.Get(headerName); value != "" { - headers[headerName] = value - } - } - - return headers -} - -// GetRspamdHeaders extracts rspamd-related headers -func (e *EmailMessage) GetRspamdHeaders() map[string]string { - headers := make(map[string]string) - - rspamdHeaders := []string{ - "X-Spamd-Result", - "X-Rspamd-Score", - "X-Rspamd-Action", - "X-Rspamd-Server", - } - - for _, headerName := range rspamdHeaders { if value := e.Header.Get(headerName); value != "" { headers[headerName] = value } diff --git a/pkg/analyzer/parser_test.go b/pkg/analyzer/parser_test.go index 196e8e2..eb1fc6a 100644 --- a/pkg/analyzer/parser_test.go +++ b/pkg/analyzer/parser_test.go @@ -106,6 +106,9 @@ Content-Type: text/html; charset=utf-8 } func TestGetAuthenticationResults(t *testing.T) { + // Force hostname + hostname = "example.com" + rawEmail := `From: sender@example.com To: recipient@example.com Subject: Test Email @@ -120,7 +123,7 @@ Body content. t.Fatalf("Failed to parse email: %v", err) } - authResults := email.GetAuthenticationResults("example.com") + authResults := email.GetAuthenticationResults() if len(authResults) != 2 { t.Errorf("Expected 2 Authentication-Results headers, got: %d", len(authResults)) } diff --git a/pkg/analyzer/rbl.go b/pkg/analyzer/rbl.go index 47e74e0..5fcb939 100644 --- a/pkg/analyzer/rbl.go +++ b/pkg/analyzer/rbl.go @@ -27,21 +27,17 @@ import ( "net" "regexp" "strings" - "sync" "time" "git.happydns.org/happyDeliver/internal/api" ) -// DNSListChecker checks IP addresses against DNS-based block/allow lists. -// It handles both RBL (blacklist) and DNSWL (whitelist) semantics via flags. -type DNSListChecker struct { - Timeout time.Duration - Lists []string - CheckAllIPs bool // Check all IPs found in headers, not just the first one - filterErrorCodes bool // When true (RBL mode), treat 127.255.255.253/254/255 as operational errors - resolver *net.Resolver - informationalSet map[string]bool // Lists whose hits don't count toward the score +// RBLChecker checks IP addresses against DNS-based blacklists +type RBLChecker struct { + Timeout time.Duration + RBLs []string + CheckAllIPs bool // Check all IPs found in headers, not just the first one + resolver *net.Resolver } // DefaultRBLs is a list of commonly used RBL providers @@ -52,83 +48,40 @@ var DefaultRBLs = []string{ "b.barracudacentral.org", // Barracuda "cbl.abuseat.org", // CBL (Composite Blocking List) "dnsbl-1.uceprotect.net", // UCEPROTECT Level 1 - "dnsbl-2.uceprotect.net", // UCEPROTECT Level 2 (informational) - "dnsbl-3.uceprotect.net", // UCEPROTECT Level 3 (informational) - "psbl.surriel.com", // PSBL - "dnsbl.dronebl.org", // DroneBL - "bl.mailspike.net", // Mailspike BL - "z.mailspike.net", // Mailspike Z - "bl.rbl-dns.com", // RBL-DNS - "bl.nszones.com", // NSZones -} - -// DefaultInformationalRBLs lists RBLs that are checked but not counted in the score. -// These are typically broader lists where being listed is less definitive. -var DefaultInformationalRBLs = []string{ - "dnsbl-2.uceprotect.net", // UCEPROTECT Level 2: entire netblocks, may cause false positives - "dnsbl-3.uceprotect.net", // UCEPROTECT Level 3: entire ASes, too broad for scoring -} - -// DefaultDNSWLs is a list of commonly used DNSWL providers -var DefaultDNSWLs = []string{ - "list.dnswl.org", // DNSWL.org — the main DNS whitelist - "swl.spamhaus.org", // Spamhaus Safe Whitelist } // NewRBLChecker creates a new RBL checker with configurable timeout and RBL list -func NewRBLChecker(timeout time.Duration, rbls []string, checkAllIPs bool) *DNSListChecker { +func NewRBLChecker(timeout time.Duration, rbls []string, checkAllIPs bool) *RBLChecker { if timeout == 0 { - timeout = 5 * time.Second + timeout = 5 * time.Second // Default timeout } if len(rbls) == 0 { rbls = DefaultRBLs } - informationalSet := make(map[string]bool, len(DefaultInformationalRBLs)) - for _, rbl := range DefaultInformationalRBLs { - informationalSet[rbl] = true - } - return &DNSListChecker{ - Timeout: timeout, - Lists: rbls, - CheckAllIPs: checkAllIPs, - filterErrorCodes: true, - resolver: &net.Resolver{PreferGo: true}, - informationalSet: informationalSet, + return &RBLChecker{ + Timeout: timeout, + RBLs: rbls, + CheckAllIPs: checkAllIPs, + resolver: &net.Resolver{ + PreferGo: true, + }, } } -// NewDNSWLChecker creates a new DNSWL checker with configurable timeout and DNSWL list -func NewDNSWLChecker(timeout time.Duration, dnswls []string, checkAllIPs bool) *DNSListChecker { - if timeout == 0 { - timeout = 5 * time.Second - } - if len(dnswls) == 0 { - dnswls = DefaultDNSWLs - } - return &DNSListChecker{ - Timeout: timeout, - Lists: dnswls, - CheckAllIPs: checkAllIPs, - filterErrorCodes: false, - resolver: &net.Resolver{PreferGo: true}, - informationalSet: make(map[string]bool), - } +// RBLResults represents the results of RBL checks +type RBLResults struct { + Checks map[string][]api.BlacklistCheck // Map of IP -> list of RBL checks for that IP + IPsChecked []string + ListedCount int } -// DNSListResults represents the results of DNS list checks -type DNSListResults struct { - Checks map[string][]api.BlacklistCheck // Map of IP -> list of checks for that IP - IPsChecked []string - ListedCount int // Total listings including informational entries - RelevantListedCount int // Listings on scoring (non-informational) lists only -} - -// CheckEmail checks all IPs found in the email headers against the configured lists -func (r *DNSListChecker) CheckEmail(email *EmailMessage) *DNSListResults { - results := &DNSListResults{ +// CheckEmail checks all IPs found in the email headers against RBLs +func (r *RBLChecker) CheckEmail(email *EmailMessage) *RBLResults { + results := &RBLResults{ Checks: make(map[string][]api.BlacklistCheck), } + // Extract IPs from Received headers ips := r.extractIPs(email) if len(ips) == 0 { return results @@ -136,18 +89,17 @@ func (r *DNSListChecker) CheckEmail(email *EmailMessage) *DNSListResults { results.IPsChecked = ips + // Check each IP against all RBLs for _, ip := range ips { - for _, list := range r.Lists { - check := r.checkIP(ip, list) + for _, rbl := range r.RBLs { + check := r.checkIP(ip, rbl) results.Checks[ip] = append(results.Checks[ip], check) if check.Listed { results.ListedCount++ - if !r.informationalSet[list] { - results.RelevantListedCount++ - } } } + // Only check the first IP unless CheckAllIPs is enabled if !r.CheckAllIPs { break } @@ -156,26 +108,20 @@ func (r *DNSListChecker) CheckEmail(email *EmailMessage) *DNSListResults { return results } -// CheckIP checks a single IP address against all configured lists in parallel -func (r *DNSListChecker) CheckIP(ip string) ([]api.BlacklistCheck, int, error) { +// CheckIP checks a single IP address against all configured RBLs +func (r *RBLChecker) CheckIP(ip string) ([]api.BlacklistCheck, int, error) { + // Validate that it's a valid IP address if !r.isPublicIP(ip) { return nil, 0, fmt.Errorf("invalid or non-public IP address: %s", ip) } - checks := make([]api.BlacklistCheck, len(r.Lists)) - var wg sync.WaitGroup - - for i, list := range r.Lists { - wg.Add(1) - go func(i int, list string) { - defer wg.Done() - checks[i] = r.checkIP(ip, list) - }(i, list) - } - wg.Wait() - + var checks []api.BlacklistCheck listedCount := 0 - for _, check := range checks { + + // Check the IP against all RBLs + for _, rbl := range r.RBLs { + check := r.checkIP(ip, rbl) + checks = append(checks, check) if check.Listed { listedCount++ } @@ -185,19 +131,27 @@ func (r *DNSListChecker) CheckIP(ip string) ([]api.BlacklistCheck, int, error) { } // extractIPs extracts IP addresses from Received headers -func (r *DNSListChecker) extractIPs(email *EmailMessage) []string { +func (r *RBLChecker) extractIPs(email *EmailMessage) []string { var ips []string seenIPs := make(map[string]bool) + // Get all Received headers receivedHeaders := email.Header["Received"] + + // Regex patterns for IP addresses + // Match IPv4: xxx.xxx.xxx.xxx ipv4Pattern := regexp.MustCompile(`\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b`) + // Look for IPs in Received headers for _, received := range receivedHeaders { + // Find all IPv4 addresses matches := ipv4Pattern.FindAllString(received, -1) for _, match := range matches { + // Skip private/reserved IPs if !r.isPublicIP(match) { continue } + // Avoid duplicates if !seenIPs[match] { ips = append(ips, match) seenIPs[match] = true @@ -205,10 +159,13 @@ func (r *DNSListChecker) extractIPs(email *EmailMessage) []string { } } + // If no IPs found in Received headers, try X-Originating-IP if len(ips) == 0 { originatingIP := email.Header.Get("X-Originating-IP") if originatingIP != "" { + // Extract IP from formats like "[192.0.2.1]" or "192.0.2.1" cleanIP := strings.TrimSuffix(strings.TrimPrefix(originatingIP, "["), "]") + // Remove any whitespace cleanIP = strings.TrimSpace(cleanIP) matches := ipv4Pattern.FindString(cleanIP) if matches != "" && r.isPublicIP(matches) { @@ -221,16 +178,19 @@ func (r *DNSListChecker) extractIPs(email *EmailMessage) []string { } // isPublicIP checks if an IP address is public (not private, loopback, or reserved) -func (r *DNSListChecker) isPublicIP(ipStr string) bool { +func (r *RBLChecker) isPublicIP(ipStr string) bool { ip := net.ParseIP(ipStr) if ip == nil { return false } + // Check if it's a private network if ip.IsPrivate() || ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() { return false } + // Additional checks for reserved ranges + // 0.0.0.0/8, 192.0.0.0/24, 192.0.2.0/24 (TEST-NET-1), 198.51.100.0/24 (TEST-NET-2), 203.0.113.0/24 (TEST-NET-3) if ip.IsUnspecified() { return false } @@ -238,43 +198,51 @@ func (r *DNSListChecker) isPublicIP(ipStr string) bool { return true } -// checkIP checks a single IP against a single DNS list -func (r *DNSListChecker) checkIP(ip, list string) api.BlacklistCheck { +// checkIP checks a single IP against a single RBL +func (r *RBLChecker) checkIP(ip, rbl string) api.BlacklistCheck { check := api.BlacklistCheck{ - Rbl: list, + Rbl: rbl, } + // Reverse the IP for DNSBL query reversedIP := r.reverseIP(ip) if reversedIP == "" { check.Error = api.PtrTo("Failed to reverse IP address") return check } - query := fmt.Sprintf("%s.%s", reversedIP, list) + // Construct DNSBL query: reversed-ip.rbl-domain + query := fmt.Sprintf("%s.%s", reversedIP, rbl) + // Perform DNS lookup with timeout ctx, cancel := context.WithTimeout(context.Background(), r.Timeout) defer cancel() addrs, err := r.resolver.LookupHost(ctx, query) if err != nil { + // Most likely not listed (NXDOMAIN) if dnsErr, ok := err.(*net.DNSError); ok { if dnsErr.IsNotFound { check.Listed = false return check } } + // Other DNS errors check.Error = api.PtrTo(fmt.Sprintf("DNS lookup failed: %v", err)) return check } + // If we got a response, check the return code if len(addrs) > 0 { - check.Response = api.PtrTo(addrs[0]) + check.Response = api.PtrTo(addrs[0]) // Return code (e.g., 127.0.0.2) - // In RBL mode, 127.255.255.253/254/255 indicate operational errors, not real listings. - if r.filterErrorCodes && (addrs[0] == "127.255.255.253" || addrs[0] == "127.255.255.254" || addrs[0] == "127.255.255.255") { + // Check for RBL error codes: 127.255.255.253, 127.255.255.254, 127.255.255.255 + // These indicate RBL operational issues, not actual listings + if addrs[0] == "127.255.255.253" || addrs[0] == "127.255.255.254" || addrs[0] == "127.255.255.255" { check.Listed = false - check.Error = api.PtrTo(fmt.Sprintf("RBL %s returned error code %s (RBL operational issue)", list, addrs[0])) + check.Error = api.PtrTo(fmt.Sprintf("RBL %s returned error code %s (RBL operational issue)", rbl, addrs[0])) } else { + // Normal listing response check.Listed = true } } @@ -282,58 +250,44 @@ func (r *DNSListChecker) checkIP(ip, list string) api.BlacklistCheck { return check } -// reverseIP reverses an IPv4 address for DNSBL/DNSWL queries +// reverseIP reverses an IPv4 address for DNSBL queries // Example: 192.0.2.1 -> 1.2.0.192 -func (r *DNSListChecker) reverseIP(ipStr string) string { +func (r *RBLChecker) reverseIP(ipStr string) string { ip := net.ParseIP(ipStr) if ip == nil { return "" } + // Convert to IPv4 ipv4 := ip.To4() if ipv4 == nil { return "" // IPv6 not supported yet } + // Reverse the octets return fmt.Sprintf("%d.%d.%d.%d", ipv4[3], ipv4[2], ipv4[1], ipv4[0]) } -// CalculateScore calculates the list contribution to deliverability. -// Informational lists are not counted in the score. -func (r *DNSListChecker) CalculateScore(results *DNSListResults, forWhitelist bool) (int, string) { - scoringListCount := len(r.Lists) - len(r.informationalSet) - - if forWhitelist { - if results.ListedCount >= scoringListCount { - return 100, "A++" - } else if results.ListedCount > 0 { - return 100, "A+" - } else { - return 95, "A" - } - } - +// CalculateRBLScore calculates the blacklist contribution to deliverability +func (r *RBLChecker) CalculateRBLScore(results *RBLResults) (int, string) { if results == nil || len(results.IPsChecked) == 0 { + // No IPs to check, give benefit of doubt return 100, "" } - if results.ListedCount <= 0 { - return 100, "A+" - } - - percentage := 100 - results.RelevantListedCount*100/scoringListCount + percentage := 100 - results.ListedCount*100/len(r.RBLs) return percentage, ScoreToGrade(percentage) } -// GetUniqueListedIPs returns a list of unique IPs that are listed on at least one entry -func (r *DNSListChecker) GetUniqueListedIPs(results *DNSListResults) []string { +// GetUniqueListedIPs returns a list of unique IPs that are listed on at least one RBL +func (r *RBLChecker) GetUniqueListedIPs(results *RBLResults) []string { var listedIPs []string - for ip, checks := range results.Checks { - for _, check := range checks { + for ip, rblChecks := range results.Checks { + for _, check := range rblChecks { if check.Listed { listedIPs = append(listedIPs, ip) - break + break // Only add the IP once } } } @@ -341,17 +295,17 @@ func (r *DNSListChecker) GetUniqueListedIPs(results *DNSListResults) []string { return listedIPs } -// GetListsForIP returns all lists that match a specific IP -func (r *DNSListChecker) GetListsForIP(results *DNSListResults, ip string) []string { - var lists []string +// GetRBLsForIP returns all RBLs that list a specific IP +func (r *RBLChecker) GetRBLsForIP(results *RBLResults, ip string) []string { + var rbls []string - if checks, exists := results.Checks[ip]; exists { - for _, check := range checks { + if rblChecks, exists := results.Checks[ip]; exists { + for _, check := range rblChecks { if check.Listed { - lists = append(lists, check.Rbl) + rbls = append(rbls, check.Rbl) } } } - return lists + return rbls } diff --git a/pkg/analyzer/rbl_test.go b/pkg/analyzer/rbl_test.go index 1dd1262..a1de270 100644 --- a/pkg/analyzer/rbl_test.go +++ b/pkg/analyzer/rbl_test.go @@ -59,8 +59,8 @@ func TestNewRBLChecker(t *testing.T) { if checker.Timeout != tt.expectedTimeout { t.Errorf("Timeout = %v, want %v", checker.Timeout, tt.expectedTimeout) } - if len(checker.Lists) != tt.expectedRBLs { - t.Errorf("RBLs count = %d, want %d", len(checker.Lists), tt.expectedRBLs) + if len(checker.RBLs) != tt.expectedRBLs { + t.Errorf("RBLs count = %d, want %d", len(checker.RBLs), tt.expectedRBLs) } if checker.resolver == nil { t.Error("Resolver should not be nil") @@ -265,7 +265,7 @@ func TestExtractIPs(t *testing.T) { func TestGetBlacklistScore(t *testing.T) { tests := []struct { name string - results *DNSListResults + results *RBLResults expectedScore int }{ { @@ -275,14 +275,14 @@ func TestGetBlacklistScore(t *testing.T) { }, { name: "No IPs checked", - results: &DNSListResults{ + results: &RBLResults{ IPsChecked: []string{}, }, expectedScore: 100, }, { name: "Not listed on any RBL", - results: &DNSListResults{ + results: &RBLResults{ IPsChecked: []string{"198.51.100.1"}, ListedCount: 0, }, @@ -290,7 +290,7 @@ func TestGetBlacklistScore(t *testing.T) { }, { name: "Listed on 1 RBL", - results: &DNSListResults{ + results: &RBLResults{ IPsChecked: []string{"198.51.100.1"}, ListedCount: 1, }, @@ -298,7 +298,7 @@ func TestGetBlacklistScore(t *testing.T) { }, { name: "Listed on 2 RBLs", - results: &DNSListResults{ + results: &RBLResults{ IPsChecked: []string{"198.51.100.1"}, ListedCount: 2, }, @@ -306,7 +306,7 @@ func TestGetBlacklistScore(t *testing.T) { }, { name: "Listed on 3 RBLs", - results: &DNSListResults{ + results: &RBLResults{ IPsChecked: []string{"198.51.100.1"}, ListedCount: 3, }, @@ -314,7 +314,7 @@ func TestGetBlacklistScore(t *testing.T) { }, { name: "Listed on 4+ RBLs", - results: &DNSListResults{ + results: &RBLResults{ IPsChecked: []string{"198.51.100.1"}, ListedCount: 4, }, @@ -326,7 +326,7 @@ func TestGetBlacklistScore(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - score, _ := checker.CalculateScore(tt.results) + score, _ := checker.CalculateRBLScore(tt.results) if score != tt.expectedScore { t.Errorf("GetBlacklistScore() = %v, want %v", score, tt.expectedScore) } @@ -335,7 +335,7 @@ func TestGetBlacklistScore(t *testing.T) { } func TestGetUniqueListedIPs(t *testing.T) { - results := &DNSListResults{ + results := &RBLResults{ Checks: map[string][]api.BlacklistCheck{ "198.51.100.1": { {Rbl: "zen.spamhaus.org", Listed: true}, @@ -363,7 +363,7 @@ func TestGetUniqueListedIPs(t *testing.T) { } func TestGetRBLsForIP(t *testing.T) { - results := &DNSListResults{ + results := &RBLResults{ Checks: map[string][]api.BlacklistCheck{ "198.51.100.1": { {Rbl: "zen.spamhaus.org", Listed: true}, @@ -402,7 +402,7 @@ func TestGetRBLsForIP(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - rbls := checker.GetListsForIP(results, tt.ip) + rbls := checker.GetRBLsForIP(results, tt.ip) if len(rbls) != len(tt.expectedRBLs) { t.Errorf("Got %d RBLs, want %d", len(rbls), len(tt.expectedRBLs)) diff --git a/pkg/analyzer/report.go b/pkg/analyzer/report.go index 7332307..39871fe 100644 --- a/pkg/analyzer/report.go +++ b/pkg/analyzer/report.go @@ -33,31 +33,24 @@ import ( type ReportGenerator struct { authAnalyzer *AuthenticationAnalyzer spamAnalyzer *SpamAssassinAnalyzer - rspamdAnalyzer *RspamdAnalyzer dnsAnalyzer *DNSAnalyzer - rblChecker *DNSListChecker - dnswlChecker *DNSListChecker + rblChecker *RBLChecker contentAnalyzer *ContentAnalyzer headerAnalyzer *HeaderAnalyzer } // NewReportGenerator creates a new report generator func NewReportGenerator( - receiverHostname string, dnsTimeout time.Duration, httpTimeout time.Duration, rbls []string, - dnswls []string, checkAllIPs bool, - rspamdAPIURL string, ) *ReportGenerator { return &ReportGenerator{ - authAnalyzer: NewAuthenticationAnalyzer(receiverHostname), + authAnalyzer: NewAuthenticationAnalyzer(), spamAnalyzer: NewSpamAssassinAnalyzer(), - rspamdAnalyzer: NewRspamdAnalyzer(LoadRspamdSymbols(rspamdAPIURL)), dnsAnalyzer: NewDNSAnalyzer(dnsTimeout), rblChecker: NewRBLChecker(dnsTimeout, rbls, checkAllIPs), - dnswlChecker: NewDNSWLChecker(dnsTimeout, dnswls, checkAllIPs), contentAnalyzer: NewContentAnalyzer(httpTimeout), headerAnalyzer: NewHeaderAnalyzer(), } @@ -70,10 +63,8 @@ type AnalysisResults struct { Content *ContentResults DNS *api.DNSResults Headers *api.HeaderAnalysis - RBL *DNSListResults - DNSWL *DNSListResults + RBL *RBLResults SpamAssassin *api.SpamAssassinResult - Rspamd *api.RspamdResult } // AnalyzeEmail performs complete email analysis @@ -85,11 +76,9 @@ func (r *ReportGenerator) AnalyzeEmail(email *EmailMessage) *AnalysisResults { // Run all analyzers results.Authentication = r.authAnalyzer.AnalyzeAuthentication(email) results.Headers = r.headerAnalyzer.GenerateHeaderAnalysis(email, results.Authentication) - results.DNS = r.dnsAnalyzer.AnalyzeDNS(email, results.Headers) + results.DNS = r.dnsAnalyzer.AnalyzeDNS(email, results.Authentication, results.Headers) results.RBL = r.rblChecker.CheckEmail(email) - results.DNSWL = r.dnswlChecker.CheckEmail(email) results.SpamAssassin = r.spamAnalyzer.AnalyzeSpamAssassin(email) - results.Rspamd = r.rspamdAnalyzer.AnalyzeRspamd(email) results.Content = r.contentAnalyzer.AnalyzeContent(email) return results @@ -141,32 +130,14 @@ func (r *ReportGenerator) GenerateReport(testID uuid.UUID, results *AnalysisResu blacklistScore := 0 var blacklistGrade string - var whitelistGrade string if results.RBL != nil { - blacklistScore, blacklistGrade = r.rblChecker.CalculateScore(results.RBL, false) - _, whitelistGrade = r.dnswlChecker.CalculateScore(results.DNSWL, true) + blacklistScore, blacklistGrade = r.rblChecker.CalculateRBLScore(results.RBL) } - saScore, saGrade := r.spamAnalyzer.CalculateSpamAssassinScore(results.SpamAssassin) - rspamdScore, rspamdGrade := r.rspamdAnalyzer.CalculateRspamdScore(results.Rspamd) - - // Combine SpamAssassin and rspamd scores 50/50. - // If only one filter ran (the other returns "" grade), use that filter's score alone. - var spamScore int + spamScore := 0 var spamGrade string - switch { - case saGrade == "" && rspamdGrade == "": - spamScore = 0 - spamGrade = "" - case saGrade == "": - spamScore = rspamdScore - spamGrade = rspamdGrade - case rspamdGrade == "": - spamScore = saScore - spamGrade = saGrade - default: - spamScore = (saScore + rspamdScore) / 2 - spamGrade = MinGrade(saGrade, rspamdGrade) + if results.SpamAssassin != nil { + spamScore, spamGrade = r.spamAnalyzer.CalculateSpamAssassinScore(results.SpamAssassin) } report.Summary = &api.ScoreSummary{ @@ -175,7 +146,7 @@ func (r *ReportGenerator) GenerateReport(testID uuid.UUID, results *AnalysisResu AuthenticationScore: authScore, AuthenticationGrade: api.ScoreSummaryAuthenticationGrade(authGrade), BlacklistScore: blacklistScore, - BlacklistGrade: api.ScoreSummaryBlacklistGrade(MinGrade(blacklistGrade, whitelistGrade)), + BlacklistGrade: api.ScoreSummaryBlacklistGrade(blacklistGrade), ContentScore: contentScore, ContentGrade: api.ScoreSummaryContentGrade(contentGrade), HeaderScore: headerScore, @@ -206,27 +177,9 @@ func (r *ReportGenerator) GenerateReport(testID uuid.UUID, results *AnalysisResu report.Blacklists = &results.RBL.Checks } - // Add whitelist checks as a map of IP -> array of BlacklistCheck (informational only) - if results.DNSWL != nil && len(results.DNSWL.Checks) > 0 { - report.Whitelists = &results.DNSWL.Checks - } - - // Add SpamAssassin result with individual deliverability score - if results.SpamAssassin != nil { - saGradeTyped := api.SpamAssassinResultDeliverabilityGrade(saGrade) - results.SpamAssassin.DeliverabilityScore = api.PtrTo(saScore) - results.SpamAssassin.DeliverabilityGrade = &saGradeTyped - } + // Add SpamAssassin result report.Spamassassin = results.SpamAssassin - // Add rspamd result with individual deliverability score - if results.Rspamd != nil { - rspamdGradeTyped := api.RspamdResultDeliverabilityGrade(rspamdGrade) - results.Rspamd.DeliverabilityScore = api.PtrTo(rspamdScore) - results.Rspamd.DeliverabilityGrade = &rspamdGradeTyped - } - report.Rspamd = results.Rspamd - // Add raw headers if results.Email != nil && results.Email.RawHeaders != "" { report.RawHeaders = &results.Email.RawHeaders diff --git a/pkg/analyzer/report_test.go b/pkg/analyzer/report_test.go index 5914737..5a325b1 100644 --- a/pkg/analyzer/report_test.go +++ b/pkg/analyzer/report_test.go @@ -32,7 +32,7 @@ import ( ) func TestNewReportGenerator(t *testing.T) { - gen := NewReportGenerator("", 10*time.Second, 10*time.Second, DefaultRBLs, DefaultDNSWLs, false, "") + gen := NewReportGenerator(10*time.Second, 10*time.Second, DefaultRBLs, false) if gen == nil { t.Fatal("Expected report generator, got nil") } @@ -55,7 +55,7 @@ func TestNewReportGenerator(t *testing.T) { } func TestAnalyzeEmail(t *testing.T) { - gen := NewReportGenerator("", 10*time.Second, 10*time.Second, DefaultRBLs, DefaultDNSWLs, false, "") + gen := NewReportGenerator(10*time.Second, 10*time.Second, DefaultRBLs, false) email := createTestEmail() @@ -75,7 +75,7 @@ func TestAnalyzeEmail(t *testing.T) { } func TestGenerateReport(t *testing.T) { - gen := NewReportGenerator("", 10*time.Second, 10*time.Second, DefaultRBLs, DefaultDNSWLs, false, "") + gen := NewReportGenerator(10*time.Second, 10*time.Second, DefaultRBLs, false) testID := uuid.New() email := createTestEmail() @@ -130,7 +130,7 @@ func TestGenerateReport(t *testing.T) { } func TestGenerateReportWithSpamAssassin(t *testing.T) { - gen := NewReportGenerator("", 10*time.Second, 10*time.Second, DefaultRBLs, DefaultDNSWLs, false, "") + gen := NewReportGenerator(10*time.Second, 10*time.Second, DefaultRBLs, false) testID := uuid.New() email := createTestEmailWithSpamAssassin() @@ -150,7 +150,7 @@ func TestGenerateReportWithSpamAssassin(t *testing.T) { } func TestGenerateRawEmail(t *testing.T) { - gen := NewReportGenerator("", 10*time.Second, 10*time.Second, DefaultRBLs, DefaultDNSWLs, false, "") + gen := NewReportGenerator(10*time.Second, 10*time.Second, DefaultRBLs, false) tests := []struct { name string diff --git a/pkg/analyzer/rspamd-symbols-README.md b/pkg/analyzer/rspamd-symbols-README.md deleted file mode 100644 index 882eab2..0000000 --- a/pkg/analyzer/rspamd-symbols-README.md +++ /dev/null @@ -1,21 +0,0 @@ -# rspamd-symbols.json - -This file contains rspamd symbol descriptions, embedded into the binary at compile time as a fallback when no rspamd API URL is configured. - -## How to update - -Fetch the latest symbols from a running rspamd instance: - -```sh -curl http://127.0.0.1:11334/symbols > rspamd-symbols.json -``` - -Or with docker: - -```sh -docker run --rm --name rspamd --pull always rspamd/rspamd -docker exec -u 0 rspamd apt install -y curl -docker exec rspamd curl http://127.0.0.1:11334/symbols > rspamd-symbols.json -``` - -Then rebuild the project. diff --git a/pkg/analyzer/rspamd-symbols.json b/pkg/analyzer/rspamd-symbols.json deleted file mode 100644 index 5538985..0000000 --- a/pkg/analyzer/rspamd-symbols.json +++ /dev/null @@ -1,6646 +0,0 @@ -[ - { - "group": "arc", - "rules": [ - { - "symbol": "ARC_ALLOW", - "weight": -1.0, - "description": "ARC checks success", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "ARC_REJECT", - "weight": 1.0, - "description": "ARC checks failed", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "ARC_NA", - "weight": 0.0, - "description": "ARC signature absent", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "ARC_INVALID", - "weight": 0.500000, - "description": "ARC structure invalid", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "ARC_CHECK", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "ARC_DNSFAIL", - "weight": 0.0, - "description": "ARC DNS error", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "ARC_SIGNED", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - } - ] - }, - { - "group": "rbl", - "rules": [ - { - "symbol": "RBL_SENDERSCORE_SUS_ATT_NA_BOT", - "weight": 1.500000, - "description": "From address is listed in SenderScore RPBL - suspect_attachments+noauth+botnet" - }, - { - "symbol": "RBL_SENDERSCORE_REPUT_0", - "weight": 4.0, - "description": "SenderScore Reputation: Very Bad (0-9).", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SENDERSCORE_REPUT_2", - "weight": 3.0, - "description": "SenderScore Reputation: Bad (20-29).", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "URIBL_RED", - "weight": 0.500000, - "description": "A domain in the message is listed in URIBL.com red", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RECEIVED_SPAMHAUS_BLOCKED", - "weight": 0.0, - "description": "You are exceeding the query limit, please see https://www.spamhaus.org/returnc/vol/", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SENDERSCORE_PRST_NA", - "weight": 2.0, - "description": "From address is listed in SenderScore RPBL - pristine+noauth" - }, - { - "symbol": "RECEIVED_SPAMHAUS", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RECEIVED_SPAMHAUS_CSS", - "weight": 1.0, - "description": "Received address is listed in Spamhaus CSS", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DWL_DNSWL_BLOCKED", - "weight": 0.0, - "description": "https://www.dnswl.org: Resolver blocked due to excessive queries (DWL)", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SENDERSCORE_UNKNOWN", - "weight": 0.0, - "description": "Unrecognised result from SenderScore RPBL" - }, - { - "symbol": "RBL_VIRUSFREE_BOTNET", - "weight": 2.0, - "description": "From address is listed in virusfree.cz BL", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DWL_DNSWL_HI", - "weight": -3.500000, - "description": "Message has a valid dkim signature originated from domain listed at https://www.dnswl.org, high trust", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_VIRUSFREE_UNKNOWN", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SPAMHAUS_BLOCKED", - "weight": 0.0, - "description": "You are exceeding the query limit, please see https://www.spamhaus.org/returnc/vol/", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_MAILSPIKE_BAD", - "weight": 1.0, - "description": "From address is listed in Mailspike RBL - bad reputation", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SPAMHAUS_SBL", - "weight": 4.0, - "description": "From address is listed in Spamhaus SBL", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RECEIVED_BLOCKLISTDE", - "weight": 3.0, - "description": "Received address is listed in Blocklist (https://www.blocklist.de/)", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DBL_BLOCKED_OPENRESOLVER", - "weight": 0.0, - "description": "You are querying Spamhaus from an open resolver, please see https://www.spamhaus.org/returnc/pub/", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "CRACKED_SURBL", - "weight": 5.0, - "description": "A domain in the message is listed in SURBL as cracked", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SURBL_HASHBL_CRACKED", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SENDERSCORE_BLOCKED", - "weight": 0.0, - "description": "Excessive number of queries to SenderScore RPBL, more info: https://knowledge.validity.com/hc/en-us/articles/20961730681243" - }, - { - "symbol": "RBL_SENDERSCORE_REPUT_4", - "weight": 2.0, - "description": "SenderScore Reputation: Bad (40-49).", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "PH_SURBL_MULTI", - "weight": 7.500000, - "description": "A domain in the message is listed in SURBL as phishing", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SENDERSCORE_SUS_ATT_PRST_NA_BOT", - "weight": 3.500000, - "description": "From address is listed in SenderScore RPBL - suspect_attachments+pristine+noauth+botnet" - }, - { - "symbol": "RBL_SENDERSCORE_SUS_ATT", - "weight": 1.0, - "description": "From address is listed in SenderScore RPBL - suspect_attachments" - }, - { - "symbol": "RBL_SENDERSCORE_REPUT_8", - "weight": 0.0, - "description": "SenderScore Reputation: Neutral (80-89).", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SPAMHAUS_BLOCKED_OPENRESOLVER", - "weight": 0.0, - "description": "You are querying Spamhaus from an open resolver, please see https://www.spamhaus.org/returnc/pub/", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RCVD_IN_DNSWL_MED", - "weight": -0.200000, - "description": "Sender listed at https://www.dnswl.org, medium trust", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DWL_DNSWL_NONE", - "weight": 0.0, - "description": "Message has a valid dkim signature originated from domain listed at https://www.dnswl.org, no trust", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MSBL_EBL", - "weight": 7.500000, - "description": "MSBL emailbl (https://www.msbl.org/)", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SPAMHAUS_XBL", - "weight": 4.0, - "description": "From address is listed in Spamhaus XBL", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SENDERSCORE_SUS_ATT_NA", - "weight": 1.0, - "description": "From address is listed in SenderScore RPBL - suspect_attachments+noauth" - }, - { - "symbol": "RBL_SENDERSCORE_PRST_BOT", - "weight": 3.0, - "description": "From address is listed in SenderScore RPBL - pristine+botnet" - }, - { - "symbol": "SURBL_HASHBL", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SENDERSCORE_PRST_NA_BOT", - "weight": 3.0, - "description": "From address is listed in SenderScore RPBL - pristine+noauth+botnet" - }, - { - "symbol": "RECEIVED_SPAMHAUS_SBL", - "weight": 3.0, - "description": "Received address is listed in Spamhaus SBL", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RWL_MAILSPIKE_POSSIBLE", - "weight": 0.0, - "description": "From address is listed in Mailspike RWL - possibly legit", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RCVD_IN_DNSWL_HI", - "weight": -0.500000, - "description": "Sender listed at https://www.dnswl.org, high trust", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SPAMHAUS_PBL", - "weight": 2.0, - "description": "From address is listed in Spamhaus PBL", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DWL_DNSWL_LOW", - "weight": -1.0, - "description": "Message has a valid dkim signature originated from domain listed at https://www.dnswl.org, low trust", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SENDERSCORE_REPUT_BLOCKED", - "weight": 0.0, - "description": "Excessive number of queries to SenderScore RPBL, more info: https://knowledge.validity.com/hc/en-us/articles/20961730681243", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SENDERSCORE_REPUT_7", - "weight": 0.500000, - "description": "SenderScore Reputation: Bad (70-79).", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SEM_URIBL_FRESH15_UNKNOWN", - "weight": 0.0, - "description": "Unrecognised result from Spameatingmonkey Fresh15 URIBL", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SURBL_HASHBL_MALWARE", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DBL_ABUSE_MALWARE", - "weight": 6.500000, - "description": "A domain in the message is listed in Spamhaus DBL as abused legit malware", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_BLOCKLISTDE", - "weight": 4.0, - "description": "From address is listed in Blocklist (https://www.blocklist.de/)", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DBL_SPAM", - "weight": 6.500000, - "description": "A domain in the message is listed in Spamhaus DBL as spam", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "ABUSE_SURBL", - "weight": 5.0, - "description": "A domain in the message is listed in SURBL as abused", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DBL_MALWARE", - "weight": 7.500000, - "description": "A domain in the message is listed in Spamhaus DBL as malware", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SURBL_HASHBL_PHISH", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RECEIVED_SPAMHAUS_DROP", - "weight": 6.0, - "description": "Received address is listed in Spamhaus DROP", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SENDERSCORE_SCORE_NA", - "weight": 2.0, - "description": "From address is listed in SenderScore RPBL - sender_score+noauth" - }, - { - "symbol": "DBL_ABUSE_REDIR", - "weight": 5.0, - "description": "A domain in the message is listed in Spamhaus DBL as spammed redirector domain", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "CT_SURBL", - "weight": 0.0, - "description": "A domain in the message is listed in SURBL as a clicktracker", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SURBL_HASHBL_EMAIL", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SENDERSCORE_SCORE_SUS_ATT_NA", - "weight": 3.0, - "description": "From address is listed in SenderScore RPBL - sender_score+suspect_attachments+noauth" - }, - { - "symbol": "RECEIVED_SPAMHAUS_XBL", - "weight": 1.0, - "description": "Received address is listed in Spamhaus XBL", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DBL_BLOCKED", - "weight": 0.0, - "description": "You are exceeding the query limit, please see https://www.spamhaus.org/returnc/vol/", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RWL_MAILSPIKE_GOOD", - "weight": -0.100000, - "description": "From address is listed in Mailspike RWL - good reputation", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SENDERSCORE_SCORE_PRST", - "weight": 4.0, - "description": "From address is listed in SenderScore RPBL - sender_score+pristine" - }, - { - "symbol": "RBL_MAILSPIKE_VERYBAD", - "weight": 1.500000, - "description": "From address is listed in Mailspike RBL - very bad reputation", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SEM_IPV6", - "weight": 1.0, - "description": "From address is listed in Spameatingmonkey RBL (IPv6)", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MW_SURBL_MULTI", - "weight": 7.500000, - "description": "A domain in the message is listed in SURBL as malware", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SENDERSCORE_NA", - "weight": 0.0, - "description": "From address is listed in SenderScore RPBL - noauth" - }, - { - "symbol": "RBL_SENDERSCORE_REPUT_9", - "weight": -1.0, - "description": "SenderScore Reputation: Good (90-100).", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "URIBL_BLOCKED", - "weight": 0.0, - "description": "URIBL.com: query refused, likely due to policy/overusage", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "URIBL_GREY", - "weight": 2.500000, - "description": "A domain in the message is listed in URIBL.com grey", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SURBL_BLOCKED", - "weight": 0.0, - "description": "SURBL: query blocked by policy/overusage", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RCVD_IN_DNSWL_LOW", - "weight": -0.100000, - "description": "Sender listed at https://www.dnswl.org, low trust", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DBL_ABUSE_PHISH", - "weight": 6.500000, - "description": "A domain in the message is listed in Spamhaus DBL as abused legit phish", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RCVD_IN_DNSWL_NONE", - "weight": 0.0, - "description": "Sender listed at https://www.dnswl.org, no trust", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SENDERSCORE_SCORE_PRST_NA", - "weight": 4.0, - "description": "From address is listed in SenderScore RPBL - sender_score+pristine+noauth" - }, - { - "symbol": "MSBL_EBL_GREY", - "weight": 0.500000, - "description": "MSBL emailbl grey list (https://www.msbl.org/)", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SENDERSCORE_REPUT_1", - "weight": 3.500000, - "description": "SenderScore Reputation: Bad (10-19).", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SENDERSCORE_BOT", - "weight": 2.0, - "description": "From address is listed in SenderScore RPBL - botnet" - }, - { - "symbol": "SEM_URIBL_UNKNOWN", - "weight": 0.0, - "description": "Unrecognised result from Spameatingmonkey URIBL", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RWL_MAILSPIKE_NEUTRAL", - "weight": 0.0, - "description": "Neutral result from Mailspike", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SURBL_HASHBL_ABUSE", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DBL_ABUSE", - "weight": 5.0, - "description": "A domain in the message is listed in Spamhaus DBL as abused legit spam", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SENDERSCORE_REPUT_6", - "weight": 1.0, - "description": "SenderScore Reputation: Bad (60-69).", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SEM_URIBL", - "weight": 3.500000, - "description": "A domain in the message is listed in Spameatingmonkey URIBL", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RECEIVED_SPAMHAUS_PBL", - "weight": 0.0, - "description": "Received address is listed in Spamhaus PBL", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DM_SURBL", - "weight": 0.0, - "description": "A domain in the message is listed in SURBL as belonging to a disposable email service", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SENDERSCORE_REPUT_5", - "weight": 1.500000, - "description": "SenderScore Reputation: Bad (50-59).", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_MAILSPIKE_WORST", - "weight": 2.0, - "description": "From address is listed in Mailspike RBL - worst possible reputation", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DBL_ABUSE_BOTNET", - "weight": 6.500000, - "description": "A domain in the message is listed in Spamhaus DBL as abused legit botnet C&C", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SENDERSCORE_SUS_ATT_PRST_NA", - "weight": 3.0, - "description": "From address is listed in SenderScore RPBL - suspect_attachments+pristine+noauth" - }, - { - "symbol": "DWL_DNSWL", - "weight": 0.0, - "description": "Unrecognised result from https://www.dnswl.org (DWL)", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SPAMHAUS_CSS", - "weight": 2.0, - "description": "From address is listed in Spamhaus CSS", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SENDERSCORE_PRST", - "weight": 2.0, - "description": "From address is listed in SenderScore RPBL - pristine" - }, - { - "symbol": "DWL_DNSWL_MED", - "weight": -2.0, - "description": "Message has a valid dkim signature originated from domain listed at https://www.dnswl.org, medium trust", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SPAMHAUS_DROP", - "weight": 7.0, - "description": "From address is listed in Spamhaus DROP", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SENDERSCORE_REPUT_UNKNOWN", - "weight": 0.0, - "description": "Unrecognized result from SenderScore Reputation list.", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DBL", - "weight": 0.0, - "description": "Unrecognised result from Spamhaus DBL", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MAILSPIKE", - "weight": 0.0, - "description": "Unrecognised result from Mailspike", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SENDERSCORE_SCORE", - "weight": 2.0, - "description": "From address is listed in SenderScore RPBL - sender_score" - }, - { - "symbol": "RBL_SPAMHAUS", - "weight": 0.0, - "description": "Unrecognised result from Spamhaus ZEN", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DNSWL_BLOCKED", - "weight": 0.0, - "description": "https://www.dnswl.org: Resolver blocked due to excessive queries", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RCVD_IN_DNSWL", - "weight": 0.0, - "description": "Unrecognised result from https://www.dnswl.org", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RWL_MAILSPIKE_VERYGOOD", - "weight": -0.200000, - "description": "From address is listed in Mailspike RWL - very good reputation", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RECEIVED_SPAMHAUS_BLOCKED_OPENRESOLVER", - "weight": 0.0, - "description": "You are querying Spamhaus from an open resolver, please see https://www.spamhaus.org/returnc/pub/", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SENDERSCORE_REPUT_3", - "weight": 2.500000, - "description": "SenderScore Reputation: Bad (30-39).", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "URIBL_MULTI", - "weight": 0.0, - "description": "Unrecognised result from URIBL.com", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SEM_URIBL_FRESH15", - "weight": 3.0, - "description": "A domain in the message is listed in Spameatingmonkey Fresh15 URIBL (registered in the past 15 days, .AERO,.BIZ,.COM,.INFO,.NAME,.NET,.PRO,.SK,.TEL,.US only)", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SEM", - "weight": 1.0, - "description": "From address is listed in Spameatingmonkey RBL", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RWL_MAILSPIKE_EXCELLENT", - "weight": -0.400000, - "description": "From address is listed in Mailspike RWL - excellent reputation", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RSPAMD_EMAILBL", - "weight": 2.500000, - "description": "Rspamd emailbl, bl.rspamd.com", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "URIBL_BLACK", - "weight": 7.500000, - "description": "A domain in the message is listed in URIBL.com black", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RSPAMD_URIBL", - "weight": 4.500000, - "description": "Rspamd uribl, bl.rspamd.com", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SURBL_MULTI", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SENDERSCORE_NA_BOT", - "weight": 1.0, - "description": "From address is listed in SenderScore RPBL - noauth+botnet" - }, - { - "symbol": "DBL_PROHIBIT", - "weight": 0.0, - "description": "DBL uribl IP queries prohibited!", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DBL_BOTNET", - "weight": 7.500000, - "description": "A domain in the message is listed in Spamhaus DBL as botnet C&C", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DBL_PHISH", - "weight": 7.500000, - "description": "A domain in the message is listed in Spamhaus DBL as phishing", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - } - ] - }, - { - "group": "dnswl", - "rules": [ - { - "symbol": "RCVD_IN_DNSWL_MED", - "weight": -0.200000, - "description": "Sender listed at https://www.dnswl.org, medium trust", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RCVD_IN_DNSWL_LOW", - "weight": -0.100000, - "description": "Sender listed at https://www.dnswl.org, low trust", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RCVD_IN_DNSWL_NONE", - "weight": 0.0, - "description": "Sender listed at https://www.dnswl.org, no trust", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DWL_DNSWL", - "weight": 0.0, - "description": "Unrecognised result from https://www.dnswl.org (DWL)", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RCVD_IN_DNSWL", - "weight": 0.0, - "description": "Unrecognised result from https://www.dnswl.org", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DNSWL_BLOCKED", - "weight": 0.0, - "description": "https://www.dnswl.org: Resolver blocked due to excessive queries", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DWL_DNSWL_BLOCKED", - "weight": 0.0, - "description": "https://www.dnswl.org: Resolver blocked due to excessive queries (DWL)", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RCVD_IN_DNSWL_HI", - "weight": -0.500000, - "description": "Sender listed at https://www.dnswl.org, high trust", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DWL_DNSWL_LOW", - "weight": -1.0, - "description": "Message has a valid dkim signature originated from domain listed at https://www.dnswl.org, low trust", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DWL_DNSWL_NONE", - "weight": 0.0, - "description": "Message has a valid dkim signature originated from domain listed at https://www.dnswl.org, no trust", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DWL_DNSWL_HI", - "weight": -3.500000, - "description": "Message has a valid dkim signature originated from domain listed at https://www.dnswl.org, high trust", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DWL_DNSWL_MED", - "weight": -2.0, - "description": "Message has a valid dkim signature originated from domain listed at https://www.dnswl.org, medium trust", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - } - ] - }, - { - "group": "dmarc", - "rules": [ - { - "symbol": "DMARC_POLICY_ALLOW", - "weight": -0.500000, - "description": "DMARC permit policy", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "BLACKLIST_DMARC", - "weight": 6.0, - "description": "Mail comes from the whitelisted domain and has failed DMARC and DKIM policies", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DMARC_POLICY_REJECT", - "weight": 2.0, - "description": "DMARC reject policy", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DMARC_POLICY_ALLOW_WITH_FAILURES", - "weight": -0.500000, - "description": "DMARC permit policy with DKIM/SPF failure", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DMARC_POLICY_SOFTFAIL", - "weight": 0.100000, - "description": "DMARC failed", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "WHITELIST_DMARC", - "weight": -7.0, - "description": "Mail comes from the whitelisted domain and has valid DMARC and DKIM policies", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DMARC_NA", - "weight": 0.0, - "description": "No DMARC record", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DMARC_POLICY_QUARANTINE", - "weight": 1.500000, - "description": "DMARC quarantine policy", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DMARC_DNSFAIL", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DMARC_BAD_POLICY", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - } - ] - }, - { - "group": "statistics", - "rules": [ - { - "symbol": "BAYES_SPAM", - "weight": 5.100000, - "description": "Message probably spam, probability: ", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "BAYES_HAM", - "weight": -3.0, - "description": "Message probably ham, probability: ", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - } - ] - }, - { - "group": "dkim", - "rules": [ - { - "symbol": "R_DKIM_ALLOW", - "weight": -0.200000, - "description": "DKIM verification succeed", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "WHITELIST_DKIM", - "weight": -1.0, - "description": "Mail comes from the whitelisted domain and has a valid DKIM signature", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "R_DKIM_REJECT", - "weight": 1.0, - "description": "DKIM verification failed", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "WHITELIST_SPF_DKIM", - "weight": -3.0, - "description": "Mail comes from the whitelisted domain and has valid SPF and DKIM policies", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "BLACKLIST_DMARC", - "weight": 6.0, - "description": "Mail comes from the whitelisted domain and has failed DMARC and DKIM policies", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "R_DKIM_TEMPFAIL", - "weight": 0.0, - "description": "DKIM verification soft-failed", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DKIM_CHECK", - "weight": 0.0, - "description": "DKIM check callback", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "BLACKLIST_DKIM", - "weight": 2.0, - "description": "Mail comes from the whitelisted domain and has non-valid DKIM signature", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "R_DKIM_PERMFAIL", - "weight": 0.0, - "description": "DKIM verification hard-failed (invalid)", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "BLACKLIST_SPF_DKIM", - "weight": 3.0, - "description": "Mail comes from the whitelisted domain and has no valid SPF policy or a bad DKIM signature", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "R_DKIM_NA", - "weight": 0.0, - "description": "Missing DKIM signature", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DKIM_SIGNED", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DKIM_TRACE", - "weight": 0.0, - "description": "DKIM trace symbol", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "WHITELIST_DMARC", - "weight": -7.0, - "description": "Mail comes from the whitelisted domain and has valid DMARC and DKIM policies", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - } - ] - }, - { - "group": "sem", - "rules": [ - { - "symbol": "SEM_URIBL_FRESH15_UNKNOWN", - "weight": 0.0, - "description": "Unrecognised result from Spameatingmonkey Fresh15 URIBL", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SEM_URIBL_FRESH15", - "weight": 3.0, - "description": "A domain in the message is listed in Spameatingmonkey Fresh15 URIBL (registered in the past 15 days, .AERO,.BIZ,.COM,.INFO,.NAME,.NET,.PRO,.SK,.TEL,.US only)", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SEM_URIBL", - "weight": 3.500000, - "description": "A domain in the message is listed in Spameatingmonkey URIBL", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SEM", - "weight": 1.0, - "description": "From address is listed in Spameatingmonkey RBL", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SEM_IPV6", - "weight": 1.0, - "description": "From address is listed in Spameatingmonkey RBL (IPv6)", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SEM_URIBL_UNKNOWN", - "weight": 0.0, - "description": "Unrecognised result from Spameatingmonkey URIBL", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - } - ] - }, - { - "group": "neural", - "rules": [] - }, - { - "group": "policies", - "rules": [ - { - "symbol": "R_SPF_NA", - "weight": 0.0, - "description": "Missing SPF record", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "R_DKIM_TEMPFAIL", - "weight": 0.0, - "description": "DKIM verification soft-failed", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DMARC_POLICY_SOFTFAIL", - "weight": 0.100000, - "description": "DMARC failed", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "ARC_ALLOW", - "weight": -1.0, - "description": "ARC checks success", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "ARC_SIGNED", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "R_SPF_ALLOW", - "weight": -0.200000, - "description": "SPF verification allows sending", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "R_DKIM_NA", - "weight": 0.0, - "description": "Missing DKIM signature", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DMARC_BAD_POLICY", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SPF_CHECK", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DMARC_NA", - "weight": 0.0, - "description": "No DMARC record", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DMARC_POLICY_ALLOW_WITH_FAILURES", - "weight": -0.500000, - "description": "DMARC permit policy with DKIM/SPF failure", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "R_SPF_PLUSALL", - "weight": 4.0, - "description": "SPF record allows to send from any IP", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "R_SPF_SOFTFAIL", - "weight": 0.0, - "description": "SPF verification soft-failed", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "ARC_INVALID", - "weight": 0.500000, - "description": "ARC structure invalid", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DMARC_DNSFAIL", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "R_DKIM_PERMFAIL", - "weight": 0.0, - "description": "DKIM verification hard-failed (invalid)", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DKIM_TRACE", - "weight": 0.0, - "description": "DKIM trace symbol", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DMARC_POLICY_ALLOW", - "weight": -0.500000, - "description": "DMARC permit policy", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DKIM_CHECK", - "weight": 0.0, - "description": "DKIM check callback", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "ARC_DNSFAIL", - "weight": 0.0, - "description": "ARC DNS error", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "ARC_REJECT", - "weight": 1.0, - "description": "ARC checks failed", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "R_SPF_PERMFAIL", - "weight": 0.0, - "description": "SPF record is malformed or persistent DNS error", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "ARC_NA", - "weight": 0.0, - "description": "ARC signature absent", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "R_SPF_NEUTRAL", - "weight": 0.0, - "description": "SPF policy is neutral", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DMARC_POLICY_QUARANTINE", - "weight": 1.500000, - "description": "DMARC quarantine policy", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "R_SPF_FAIL", - "weight": 1.0, - "description": "SPF verification failed", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "R_SPF_DNSFAIL", - "weight": 0.0, - "description": "SPF DNS failure", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DMARC_POLICY_REJECT", - "weight": 2.0, - "description": "DMARC reject policy", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "R_DKIM_ALLOW", - "weight": -0.200000, - "description": "DKIM verification succeed", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "R_DKIM_REJECT", - "weight": 1.0, - "description": "DKIM verification failed", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DKIM_SIGNED", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "ARC_CHECK", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - } - ] - }, - { - "group": "surbl", - "rules": [ - { - "symbol": "DBL_BLOCKED", - "weight": 0.0, - "description": "You are exceeding the query limit, please see https://www.spamhaus.org/returnc/vol/", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DBL_ABUSE_BOTNET", - "weight": 6.500000, - "description": "A domain in the message is listed in Spamhaus DBL as abused legit botnet C&C", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DBL_PROHIBIT", - "weight": 0.0, - "description": "DBL uribl IP queries prohibited!", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SPAMHAUS_ZEN_URIBL", - "weight": 0.0, - "description": "Unrecognised result from Spamhaus ZEN URIBL" - }, - { - "symbol": "MSBL_EBL", - "weight": 7.500000, - "description": "MSBL emailbl (https://www.msbl.org/)", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DBL_ABUSE", - "weight": 5.0, - "description": "A domain in the message is listed in Spamhaus DBL as abused legit spam", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "PH_SURBL_MULTI", - "weight": 7.500000, - "description": "A domain in the message is listed in SURBL as phishing", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DBL_BOTNET", - "weight": 7.500000, - "description": "A domain in the message is listed in Spamhaus DBL as botnet C&C", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RSPAMD_EMAILBL", - "weight": 2.500000, - "description": "Rspamd emailbl, bl.rspamd.com", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SEM_URIBL_UNKNOWN", - "weight": 0.0, - "description": "Unrecognised result from Spameatingmonkey URIBL", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DBL_BLOCKED_OPENRESOLVER", - "weight": 0.0, - "description": "You are querying Spamhaus from an open resolver, please see https://www.spamhaus.org/returnc/pub/", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "CT_SURBL", - "weight": 0.0, - "description": "A domain in the message is listed in SURBL as a clicktracker", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SEM_URIBL", - "weight": 3.500000, - "description": "A domain in the message is listed in Spameatingmonkey URIBL", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RSPAMD_URIBL", - "weight": 4.500000, - "description": "Rspamd uribl, bl.rspamd.com", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SEM_URIBL_FRESH15_UNKNOWN", - "weight": 0.0, - "description": "Unrecognised result from Spameatingmonkey Fresh15 URIBL", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "URIBL_SBL", - "weight": 6.500000, - "description": "A domain in the message body resolves to an IP listed in Spamhaus SBL" - }, - { - "symbol": "URIBL_BLACK", - "weight": 7.500000, - "description": "A domain in the message is listed in URIBL.com black", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "ABUSE_SURBL", - "weight": 5.0, - "description": "A domain in the message is listed in SURBL as abused", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DBL_ABUSE_REDIR", - "weight": 5.0, - "description": "A domain in the message is listed in Spamhaus DBL as spammed redirector domain", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "URIBL_PBL", - "weight": 0.010000, - "description": "A domain in the message body resolves to an IP listed in Spamhaus PBL" - }, - { - "symbol": "DBL_ABUSE_PHISH", - "weight": 6.500000, - "description": "A domain in the message is listed in Spamhaus DBL as abused legit phish", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MSBL_EBL_GREY", - "weight": 0.500000, - "description": "MSBL emailbl grey list (https://www.msbl.org/)", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DBL_SPAM", - "weight": 6.500000, - "description": "A domain in the message is listed in Spamhaus DBL as spam", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "CRACKED_SURBL", - "weight": 5.0, - "description": "A domain in the message is listed in SURBL as cracked", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "URIBL_GREY", - "weight": 2.500000, - "description": "A domain in the message is listed in URIBL.com grey", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "URIBL_RED", - "weight": 0.500000, - "description": "A domain in the message is listed in URIBL.com red", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "URIBL_DROP", - "weight": 5.0, - "description": "A domain in the message body resolves to an IP listed in Spamhaus DROP" - }, - { - "symbol": "DBL_PHISH", - "weight": 7.500000, - "description": "A domain in the message is listed in Spamhaus DBL as phishing", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "URIBL_MULTI", - "weight": 0.0, - "description": "Unrecognised result from URIBL.com", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DBL_ABUSE_MALWARE", - "weight": 6.500000, - "description": "A domain in the message is listed in Spamhaus DBL as abused legit malware", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DBL", - "weight": 0.0, - "description": "Unrecognised result from Spamhaus DBL", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DBL_MALWARE", - "weight": 7.500000, - "description": "A domain in the message is listed in Spamhaus DBL as malware", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MW_SURBL_MULTI", - "weight": 7.500000, - "description": "A domain in the message is listed in SURBL as malware", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "URIBL_XBL", - "weight": 3.0, - "description": "A domain in the message body resolves to an IP listed in Spamhaus XBL" - }, - { - "symbol": "SEM_URIBL_FRESH15", - "weight": 3.0, - "description": "A domain in the message is listed in Spameatingmonkey Fresh15 URIBL (registered in the past 15 days, .AERO,.BIZ,.COM,.INFO,.NAME,.NET,.PRO,.SK,.TEL,.US only)", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "URIBL_SBL_CSS", - "weight": 5.0, - "description": "A domain in the message body resolves to an IP listed in Spamhaus CSS" - }, - { - "symbol": "DM_SURBL", - "weight": 0.0, - "description": "A domain in the message is listed in SURBL as belonging to a disposable email service", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "URIBL_BLOCKED", - "weight": 0.0, - "description": "URIBL.com: query refused, likely due to policy/overusage", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SURBL_BLOCKED", - "weight": 0.0, - "description": "SURBL: query blocked by policy/overusage", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - } - ] - }, - { - "group": "mime", - "rules": [ - { - "symbol": "MIME_BASE64_TEXT_BOGUS", - "weight": 1.0, - "description": "Has text part encoded in base64 that does not contain any 8bit characters", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "CTYPE_MIXED_BOGUS", - "weight": 1.0, - "description": "multipart/mixed without non-textual part", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "CTYPE_MISSING_DISPOSITION", - "weight": 4.0, - "description": "Binary content-type not specified as an attachment", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MIME_BASE64_TEXT", - "weight": 0.100000, - "description": "Has text part encoded in base64", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - } - ] - }, - { - "group": "multimap", - "rules": [ - { - "symbol": "DISPOSABLE_FROM", - "weight": 0.0, - "description": "From a Disposable e-mail address", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DISPOSABLE_ENVFROM", - "weight": 0.0, - "description": "Envelope From is a Disposable e-mail address", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DISPOSABLE_TO", - "weight": 0.0, - "description": "To a disposable e-mail address", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DISPOSABLE_REPLYTO", - "weight": 0.0, - "description": "Reply-To a disposable e-mail address", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DISPOSABLE_CC", - "weight": 0.0, - "description": "To a disposable e-mail address", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FREEMAIL_TO", - "weight": 0.0, - "description": "To is a Freemail address", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FREEMAIL_ENVRCPT", - "weight": 0.0, - "description": "Envelope Recipient is a Freemail address", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FREEMAIL_ENVFROM", - "weight": 0.0, - "description": "Envelope From is a Freemail address", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DISPOSABLE_MDN", - "weight": 0.500000, - "description": "Disposition-Notification-To is a disposable e-mail address", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FREEMAIL_MDN", - "weight": 0.0, - "description": "Disposition-Notification-To is a Freemail address", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FREEMAIL_FROM", - "weight": 0.0, - "description": "From is a Freemail address", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FREEMAIL_REPLYTO", - "weight": 0.0, - "description": "Reply-To is a Freemail address", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DISPOSABLE_ENVRCPT", - "weight": 0.0, - "description": "Envelope Recipient is a Disposable e-mail address", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FREEMAIL_CC", - "weight": 0.0, - "description": "To is a Freemail address", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "REDIRECTOR_URL", - "weight": 0.0, - "description": "The presence of a redirector in the mail", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - } - ] - }, - { - "group": "excessqp", - "rules": [ - { - "symbol": "CC_EXCESS_QP", - "weight": 1.200000, - "description": "Cc header is unnecessarily encoded in quoted-printable", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SUBJ_EXCESS_QP", - "weight": 1.200000, - "description": "Subject header is unnecessarily encoded in quoted-printable", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "REPLYTO_EXCESS_QP", - "weight": 1.200000, - "description": "Reply-To header is unnecessarily encoded in quoted-printable", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FROM_EXCESS_QP", - "weight": 1.200000, - "description": "From header is unnecessarily encoded in quoted-printable", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "TO_EXCESS_QP", - "weight": 1.200000, - "description": "To header is unnecessarily encoded in quoted-printable", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - } - ] - }, - { - "group": "upstream_spam_filters", - "rules": [ - { - "symbol": "UNITEDINTERNET_SPAM", - "weight": 5.0, - "description": "United Internet says this message is spam", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "KLMS_SPAM", - "weight": 5.0, - "description": "Kaspersky Security for Mail Server says this message is spam", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MICROSOFT_SPAM", - "weight": 4.0, - "description": "Microsoft says the message is spam", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "PRECEDENCE_BULK", - "weight": 0.0, - "description": "Message marked as bulk", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SPAM_FLAG", - "weight": 5.0, - "description": "Message was already marked as spam", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - } - ] - }, - { - "group": "headers", - "rules": [ - { - "symbol": "FAKE_RECEIVED_smtp_yandex_ru", - "weight": 4.0, - "description": "Fake smtp.yandex.ru Received header", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HEADER_RCONFIRM_MISMATCH", - "weight": 2.0, - "description": "Read confirmation address is different to from address", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RCPT_COUNT_ZERO", - "weight": 0.0, - "description": "No recipients", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MAILER_1C_8", - "weight": 0.0, - "description": "Sent with 1C:Enterprise 8", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "REPTO_QUOTE_YAHOO", - "weight": 2.0, - "description": "Quoted Reply-To header from Yahoo (seems to be forged)", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RCVD_COUNT_SEVEN", - "weight": 0.0, - "description": "Message has 7-11 Received headers", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RCVD_COUNT_ZERO", - "weight": 0.0, - "description": "Message has no Received headers", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SPOOF_DISPLAY_NAME", - "weight": 8.0, - "description": "Display name is being used to spoof and trick the recipient", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "TO_DN_EQ_ADDR_ALL", - "weight": 0.0, - "description": "All of the recipients have display names that are the same as their address", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "CHECK_FROM", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SUBJECT_ENDS_EXCLAIM", - "weight": 0.0, - "description": "Subject ends with an exclamation mark", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FORGED_IMS", - "weight": 3.0, - "description": "Forged X-Mailer: Internet Mail Service", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FORGED_SENDER", - "weight": 0.300000, - "description": "Sender is forged (different From: header and smtp MAIL FROM: addresses)", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RCVD_COUNT_ONE", - "weight": 0.0, - "description": "Message has one Received header", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "INVALID_RCPT_8BIT", - "weight": 6.0, - "description": "Invalid 8bit character in recipients headers", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FORGED_MUA_THEBAT_BOUN", - "weight": 2.0, - "description": "Forged The Bat! MUA headers", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MAIL_RU_MAILER", - "weight": 0.0, - "description": "Sent with Mail.Ru webmail", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HEADER_CC_EMPTY_DELIMITER", - "weight": 1.0, - "description": "Cc header has no delimiter between header name and header value", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "OLD_X_MAILER", - "weight": 2.0, - "description": "X-Mailer header has a very old MUA version", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FORGED_GENERIC_RECEIVED4", - "weight": 3.600000, - "description": "Forged generic Received header", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FAKE_REPLY", - "weight": 1.0, - "description": "Fake reply", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "STRONGMAIL", - "weight": 6.0, - "description": "Sent via rogue \"strongmail\" MTA", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HAS_X_PRIO_FIVE", - "weight": 0.0, - "description": "Message has X-Priority header set to 5 or higher", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MISSING_MIME_VERSION", - "weight": 2.0, - "description": "MIME-Version header is missing in MIME message", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "CHECK_RCVD", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RCVD_DOUBLE_IP_SPAM", - "weight": 2.0, - "description": "Has two Received headers containing bare IP addresses", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HAS_REPLYTO", - "weight": 0.0, - "description": "Has Reply-To header", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MIME_MA_MISSING_HTML", - "weight": 1.0, - "description": "MIME multipart/alternative missing text/html part", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "REPLYTO_DN_EQ_FROM_DN", - "weight": 0.0, - "description": "Reply-To display name matches From", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "REPLYTO_DOM_EQ_TO_DOM", - "weight": 0.0, - "description": "Reply-To domain matches the To domain", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "X_PHPOS_FAKE", - "weight": 3.0, - "description": "Fake X-PHP-Originating-Script header", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "ENVFROM_VERP", - "weight": 0.0, - "description": "Envelope From is a VERP address", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FROM_EQ_ENVFROM", - "weight": 0.0, - "description": "From address is the same as the envelope", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HAS_ORG_HEADER", - "weight": 0.0, - "description": "Has Organization header", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MISSING_TO", - "weight": 2.0, - "description": "To header is missing", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "BROKEN_HEADERS", - "weight": 10.0, - "description": "Headers structure is likely broken", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FROM_DN_EQ_ADDR", - "weight": 1.0, - "description": "From header display name is the same as the address", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FREEMAIL_REPLYTO_NEQ_FROM_DOM", - "weight": 3.0, - "description": "The From and Reply-To addresses in the email are from different freemail services", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RECEIVED_HELO_LOCALHOST", - "weight": 0.0, - "description": "Localhost HELO seen in Received header", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "R_BAD_CTE_7BIT", - "weight": 3.500000, - "description": "Detects bad Content-Transfer-Encoding for text parts", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HEADER_FROM_EMPTY_DELIMITER", - "weight": 1.0, - "description": "From header has no delimiter between header name and header value", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SUBJECT_HAS_QUESTION", - "weight": 0.0, - "description": "Subject contains a question mark", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HAS_X_PRIO_ZERO", - "weight": 0.0, - "description": "Message has X-Priority header set to 0", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "TO_DN_SOME", - "weight": 0.0, - "description": "Some of the recipients have display names", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "ONCE_RECEIVED", - "weight": 0.100000, - "description": "One received header in a message", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "INFO_TO_INFO_LU", - "weight": 2.0, - "description": "info@ From/To address with List-Unsubscribe headers", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "REPLYTO_DOM_EQ_FROM_DOM", - "weight": 0.0, - "description": "Reply-To domain matches the From domain", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MIME_MA_MISSING_TEXT", - "weight": 2.0, - "description": "MIME multipart/alternative missing text/plain part", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RCPT_COUNT_TWO", - "weight": 0.0, - "description": "Two recipients", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RCPT_COUNT_THREE", - "weight": 0.0, - "description": "3-5 recipients", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HAS_X_PRIO", - "weight": 0.0, - "description": "X-Priority check callback rule", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "TO_DN_NONE", - "weight": 0.0, - "description": "None of the recipients have display names", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RCVD_COUNT_TWO", - "weight": 0.0, - "description": "Message has two Received headers", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "CTE_CASE", - "weight": 0.500000, - "description": "[78]Bit .vs. [78]bit", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SUBJECT_HAS_EXCLAIM", - "weight": 0.0, - "description": "Subject contains an exclamation mark", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MISSING_XM_UA", - "weight": 0.0, - "description": "Message has neither X-Mailer nor User-Agent header", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "X_PHP_FORGED_0X", - "weight": 4.0, - "description": "X-PHP-Originating-Script header appears forged", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "APPLE_IOS_MAILER", - "weight": 0.0, - "description": "Sent with Apple iPhone/iPad Mail", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HAS_LIST_UNSUB", - "weight": -0.010000, - "description": "Has List-Unsubscribe header", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "ENVFROM_INVALID", - "weight": 2.0, - "description": "Envelope from does not have a valid format", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FORGED_GENERIC_RECEIVED3", - "weight": 3.600000, - "description": "Forged generic Received header", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "R_MIXED_CHARSET", - "weight": 5.0, - "description": "Mixed characters in a message", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "INVALID_MSGID", - "weight": 1.700000, - "description": "Message-ID header is incorrect", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "REPLYTO_DOM_NEQ_FROM_DOM", - "weight": 0.0, - "description": "Reply-To domain does not match the From domain", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SUBJECT_ENDS_SPACES", - "weight": 0.500000, - "description": "Subject ends with space characters", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RCVD_COUNT_TWELVE", - "weight": 0.0, - "description": "Message has 12 or more Received headers", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FROM_NEQ_DISPLAY_NAME", - "weight": 4.0, - "description": "Display name contains an email address different to the From address", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "BROKEN_CONTENT_TYPE", - "weight": 1.500000, - "description": "Message has part with broken content type", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MISSING_DATE", - "weight": 1.0, - "description": "Date header is missing", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FORGED_MSGID_YAHOO", - "weight": 2.0, - "description": "Forged Yahoo Message-ID header", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "TO_DN_EQ_ADDR_SOME", - "weight": 0.0, - "description": "Some of the recipients have display names that are the same as their address", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "R_RCVD_SPAMBOTS", - "weight": 3.0, - "description": "Spambots signatures in received headers", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "R_MISSING_CHARSET", - "weight": 0.500000, - "description": "Charset header is missing", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MISSING_MID", - "weight": 2.500000, - "description": "Message-ID header is missing", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HEADER_FORGED_MDN", - "weight": 2.0, - "description": "Read confirmation address is different to return path", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SPOOF_REPLYTO", - "weight": 6.0, - "description": "Reply-To is being used to spoof and trick the recipient to send an off-domain reply", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HEADER_DATE_EMPTY_DELIMITER", - "weight": 1.0, - "description": "Date header has no delimiter between header name and header value", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "TO_MATCH_ENVRCPT_SOME", - "weight": 0.0, - "description": "Some of the recipients match the envelope", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FORGED_RECIPIENTS_MAILLIST", - "weight": 0.0, - "description": "Recipients are not the same as RCPT TO: mail command, but a message from a maillist", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MISSING_FROM", - "weight": 2.0, - "description": "Missing From header", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RCPT_COUNT_SEVEN", - "weight": 0.0, - "description": "7-11 recipients", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "REPLYTO_UNPARSEABLE", - "weight": 1.0, - "description": "Reply-To header could not be parsed", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HAS_X_PRIO_ONE", - "weight": 0.0, - "description": "Message has X-Priority header set to 1", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RCPT_COUNT_GT_50", - "weight": 0.0, - "description": "50+ recipients", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RCVD_TLS_LAST", - "weight": 0.0, - "description": "Last hop used encrypted transports", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FROM_NAME_HAS_TITLE", - "weight": 1.0, - "description": "From header display name has a title (Mr/Mrs/Dr)", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "PREVIOUSLY_DELIVERED", - "weight": 0.0, - "description": "Message either to a list or was forwarded", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RCVD_HELO_USER", - "weight": 3.0, - "description": "HELO User spam pattern", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FORGED_X_MAILER", - "weight": 4.500000, - "description": "Forged X-Mailer header", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "R_HTTP_URL_IN_FROM", - "weight": 5.0, - "description": "HTTP URL preceded by the start of a line, quote, or whitespace, with normal or URL-encoded colons in From header", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "TO_DOM_EQ_FROM_DOM", - "weight": 0.0, - "description": "To domain is the same as the From domain", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RCPT_COUNT_TWELVE", - "weight": 0.0, - "description": "12-50 recipients", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FORGED_OUTLOOK_TAGS", - "weight": 2.100000, - "description": "Message pretends to be send from Outlook but has 'strange' tags", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FROM_NO_DN", - "weight": 0.0, - "description": "From header does not have a display name", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "INVALID_DATE", - "weight": 1.500000, - "description": "Malformed Date header", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "R_NO_SPACE_IN_FROM", - "weight": 1.0, - "description": "No space in From header", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FORGED_OUTLOOK_HTML", - "weight": 5.0, - "description": "Forged Outlook HTML signature", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FROM_DISPLAY_CALLBACK", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "REPLYTO_ADDR_EQ_FROM", - "weight": 0.0, - "description": "Reply-To header is identical to SMTP From", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FORGED_SENDER_MAILLIST", - "weight": 0.0, - "description": "Sender is not the same as MAIL FROM: envelope, but a message is from a maillist", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "TO_WRAPPED_IN_SPACES", - "weight": 2.0, - "description": "To address is wrapped in spaces inside angle brackets (e.g. display-name < local-part@domain >)", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DIRECT_TO_MX", - "weight": 0.0, - "description": "Message has been directly delivered from MUA to local MX", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RCVD_COUNT_FIVE", - "weight": 0.0, - "description": "Message has 5-7 Received headers", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FORGED_GENERIC_RECEIVED", - "weight": 3.600000, - "description": "Forged generic Received header", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SUBJECT_ENDS_QUESTION", - "weight": 1.0, - "description": "Subject ends with a question mark", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FORGED_CALLBACK", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FORGED_RECIPIENTS", - "weight": 2.0, - "description": "Recipients are not the same as RCPT TO: mail command", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "TRACKER_ID", - "weight": 3.840000, - "description": "Spam string at the end of message to make statistics fault", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FROM_NEQ_ENVFROM", - "weight": 0.0, - "description": "From address is different to the envelope", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "CT_EXTRA_SEMI", - "weight": 1.0, - "description": "Content-Type header ends with a semi-colon", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MAILLIST", - "weight": -0.200000, - "description": "Message seems to be from maillist", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HAS_X_PRIO_TWO", - "weight": 0.0, - "description": "Message has X-Priority header set to 2", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RCPT_COUNT_FIVE", - "weight": 0.0, - "description": "5-7 recipients", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MISSING_SUBJECT", - "weight": 2.0, - "description": "Subject header is missing", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "CD_MM_BODY", - "weight": 2.0, - "description": "Content-Description header reads \"Mail message body\", commonly seen in spam", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "YANDEX_RU_MAILER", - "weight": 0.0, - "description": "Sent with Yandex webmail", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "GOOGLE_FORWARDING_MID_MISSING", - "weight": 2.500000, - "description": "Message was missing Message-ID pre-forwarding", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "TO_NEEDS_ENCODING", - "weight": 1.0, - "description": "To header needs encoding", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FROM_NEEDS_ENCODING", - "weight": 1.0, - "description": "From header needs encoding", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SUBJECT_NEEDS_ENCODING", - "weight": 1.0, - "description": "Subject needs encoding", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "REPLYTO_EQ_TO_ADDR", - "weight": 5.0, - "description": "Reply-To is the same as the To address", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "REPLYTO_EMAIL_HAS_TITLE", - "weight": 2.0, - "description": "Reply-To header has title", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RCPT_COUNT_ONE", - "weight": 0.0, - "description": "One recipient", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "TO_EQ_FROM", - "weight": 0.0, - "description": "To address matches the From address", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "CHECK_MIME", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SUSPICIOUS_RECIPS", - "weight": 1.500000, - "description": "Recipients seems to be autogenerated (works if recipients count is more than 5)", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FAKE_RECEIVED_mail_ru", - "weight": 4.0, - "description": "Fake HELO mail.ru in Received header from non-mail.ru sender address", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HAS_XOIP", - "weight": 0.0, - "description": "Has X-Originating-IP header", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "REPLYTO_DOM_NEQ_TO_DOM", - "weight": 0.0, - "description": "Reply-To domain does not match the To domain", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "EMPTY_SUBJECT", - "weight": 1.0, - "description": "Subject header is empty", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "STOX_REPLY_TYPE", - "weight": 1.0, - "description": "Reply-type in Content-Type header", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MIME_HEADER_CTYPE_ONLY", - "weight": 2.0, - "description": "Only Content-Type header without other MIME headers", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "BOUNCE", - "weight": -0.100000, - "description": "(Non) Delivery Status Notification", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SORTED_RECIPS", - "weight": 3.500000, - "description": "Recipients list seems to be sorted", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "INVALID_POSTFIX_RECEIVED", - "weight": 3.0, - "description": "Invalid Postfix Received header", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "ENVFROM_PRVS", - "weight": 0.0, - "description": "Envelope From is a PRVS address that matches the From address", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "CHECK_RECEIVED", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MISSING_MIMEOLE", - "weight": 2.0, - "description": "Mime-OLE is needed but absent (e.g. fake Outlook or fake Exchange)", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FROM_HAS_DN", - "weight": 0.0, - "description": "From header has a display name", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RCVD_NO_TLS_LAST", - "weight": 0.100000, - "description": "Last hop did not use encrypted transports", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "INVALID_FROM_8BIT", - "weight": 6.0, - "description": "Invalid 8bit character in From header", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RATWARE_MS_HASH", - "weight": 2.0, - "description": "Forged Exchange messages", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "ONCE_RECEIVED_STRICT", - "weight": 4.0, - "description": "One received header with 'bad' patterns inside", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "XM_CASE", - "weight": 0.500000, - "description": "X-mailer .vs. X-Mailer", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DATE_IN_PAST", - "weight": 1.0, - "description": "Message date is in the past", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MULTIPLE_UNIQUE_HEADERS", - "weight": 7.0, - "description": "Repeated unique headers", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HAS_X_PRIO_THREE", - "weight": 0.0, - "description": "Message has X-Priority header set to 3 or 4", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "CHECK_REPLYTO", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "R_MIXED_CHARSET_URL", - "weight": 7.0, - "description": "Mixed characters in a URL inside message", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MV_CASE", - "weight": 0.500000, - "description": "Mime-Version .vs. MIME-Version", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "R_UNDISC_RCPT", - "weight": 3.0, - "description": "Recipients are absent or undisclosed", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "APPLE_MAILER", - "weight": 0.0, - "description": "Sent with Apple Mail", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "TO_DN_ALL", - "weight": 0.0, - "description": "All the recipients have display names", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "GOOGLE_FORWARDING_MID_BROKEN", - "weight": 1.700000, - "description": "Message had invalid Message-ID pre-forwarding", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FROM_INVALID", - "weight": 2.0, - "description": "From header does not have a valid format", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DATE_IN_FUTURE", - "weight": 4.0, - "description": "Message date is in the future", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FROM_NAME_EXCESS_SPACE", - "weight": 1.0, - "description": "From header display name contains excess whitespace", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FORGED_GENERIC_RECEIVED2", - "weight": 3.600000, - "description": "Forged generic Received header", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RCVD_COUNT_THREE", - "weight": 0.0, - "description": "Message has 3-5 Received headers", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "REPLYTO_EQ_FROM", - "weight": 0.0, - "description": "Reply-To header is identical to From header", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MULTIPLE_FROM", - "weight": 8.0, - "description": "Multiple addresses in From header", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HAS_CD_HEADER", - "weight": 0.0, - "description": "Has Content-Description header", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RCVD_TLS_ALL", - "weight": 0.0, - "description": "All hops used encrypted transports", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "TO_MATCH_ENVRCPT_ALL", - "weight": 0.0, - "description": "All of the recipients match the envelope", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RCVD_VIA_SMTP_AUTH", - "weight": 0.0, - "description": "Authenticated hand-off was seen in Received headers", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "TO_DN_RECIPIENTS", - "weight": 2.0, - "description": "To header display name is \"Recipients\"", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MIME_HTML_ONLY", - "weight": 0.200000, - "description": "Message has only an HTML part", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HAS_INTERSPIRE_SIG", - "weight": 1.0, - "description": "Has Interspire fingerprint", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SUBJECT_HAS_CURRENCY", - "weight": 1.0, - "description": "Subject contains currency", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SUBJ_BOUNCE_WORDS", - "weight": 0.0, - "description": "Words/phrases typical for DSN", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HEADER_REPLYTO_EMPTY_DELIMITER", - "weight": 1.0, - "description": "Reply-To header has no delimiter between header name and header value", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HEADER_TO_EMPTY_DELIMITER", - "weight": 1.0, - "description": "To header has no delimiter between header name and header value", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - } - ] - }, - { - "group": "phishing", - "rules": [ - { - "symbol": "PH_SURBL_MULTI", - "weight": 7.500000, - "description": "A domain in the message is listed in SURBL as phishing", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HACKED_WP_PHISHING", - "weight": 4.500000, - "description": "Phish message sent by hacked Wordpress instance", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "URL_REDIRECTOR_NESTED", - "weight": 1.0, - "description": "URL redirector nested limit has been reached" - }, - { - "symbol": "REDIRECTOR_FALSE", - "weight": 0.0, - "description": "Phishing exclusion symbol for known redirectors", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "PHISHED_EXCLUDED", - "weight": 0.0, - "description": "Phished URL found in exclusions list", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "PHISHING", - "weight": 4.0, - "description": "Phished URL", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "PHISHED_OPENPHISH", - "weight": 7.0, - "description": "Phished URL found in openphish.com", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "PHISHED_GENERIC_SERVICE", - "weight": 0.0, - "description": "Phished URL found in generic service", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "PHISHED_WHITELISTED", - "weight": 0.0, - "description": "Phishing exclusion symbol for known exceptions", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "PHISHED_PHISHTANK", - "weight": 7.0, - "description": "Phished URL found in phishtank.com", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - } - ] - }, - { - "group": "excessb64", - "rules": [ - { - "symbol": "FROM_EXCESS_BASE64", - "weight": 1.500000, - "description": "From header is unnecessarily encoded in base64", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "REPLYTO_EXCESS_BASE64", - "weight": 1.500000, - "description": "Reply-To header is unnecessarily encoded in base64", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "TO_EXCESS_BASE64", - "weight": 1.500000, - "description": "To header is unnecessarily encoded in base64", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "CC_EXCESS_BASE64", - "weight": 1.500000, - "description": "Cc header is unnecessarily encoded in base64", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SUBJ_EXCESS_BASE64", - "weight": 1.500000, - "description": "Subject header is unnecessarily encoded in base64", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - } - ] - }, - { - "group": "forwarding", - "rules": [ - { - "symbol": "FWD_MAILRU", - "weight": 0.0, - "description": "Message was forwarded by Mail.ru", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FORWARDED", - "weight": 0.0, - "description": "Message was forwarded", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FWD_GOOGLE", - "weight": 0.0, - "description": "Message was forwarded by Google", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FWD_SIEVE", - "weight": 0.0, - "description": "Message was forwarded using Sieve", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FWD_CPANEL", - "weight": 0.0, - "description": "Message was forwarded using cPanel", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FWD_YANDEX", - "weight": 0.0, - "description": "Message was forwarded by Yandex", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FWD_SRS", - "weight": 0.0, - "description": "Message was forwarded using Sender Rewriting Scheme (SRS)", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - } - ] - }, - { - "group": "url", - "rules": [ - { - "symbol": "HAS_FILE_URL", - "weight": 2.0, - "description": "Contains file:// URL", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "URL_BAD_UNICODE", - "weight": 3.0, - "description": "URL contains invalid Unicode", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "URL_USER_PASSWORD", - "weight": 2.0, - "description": "URL contains user field", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "URL_OBFUSCATED_TEXT", - "weight": 5.0, - "description": "Obfuscated URL found in message text", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "URL_VERY_LONG", - "weight": 1.500000, - "description": "URL is very long", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "URL_HOMOGRAPH_ATTACK", - "weight": 5.0, - "description": "URL uses homograph attack (mixed scripts)", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "URL_SUSPICIOUS_TLD", - "weight": 3.0, - "description": "URL uses suspicious TLD", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HAS_GOOGLE_REDIR", - "weight": 1.0, - "description": "Has google.com/url or alike Google redirection URL", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "URI_COUNT_ODD", - "weight": 1.0, - "description": "Odd number of URIs in multipart/alternative message", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "URL_ZERO_WIDTH_SPACES", - "weight": 7.0, - "description": "URL contains zero-width spaces", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "URL_USER_LONG", - "weight": 3.0, - "description": "URL user field is long (>128 chars)", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HAS_GOOGLE_FIREBASE_URL", - "weight": 2.0, - "description": "Contains firebasestorage.googleapis.com URL", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HAS_IPFS_GATEWAY_URL", - "weight": 6.0, - "description": "Message contains InterPlanetary File System (IPFS) gateway URL, likely malicious", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "URL_RTL_OVERRIDE", - "weight": 6.0, - "description": "URL uses RTL override character", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "URL_NUMERIC_PRIVATE_IP", - "weight": 0.500000, - "description": "URL uses private IP range", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "URL_BACKSLASH_PATH", - "weight": 2.0, - "description": "URL uses backslashes", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "URL_NUMERIC_IP", - "weight": 1.500000, - "description": "URL uses numeric IP address", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "URL_USER_VERY_LONG", - "weight": 5.0, - "description": "URL user field is very long (>256 chars)", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HAS_ONION_URI", - "weight": 0.0, - "description": "Contains .onion hidden service URI", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "URL_EXCESSIVE_DOTS", - "weight": 2.0, - "description": "URL has excessive dots in hostname", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "URL_SUSPECT_CHECK", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "URL_NO_TLD", - "weight": 2.0, - "description": "URL has no TLD", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "OMOGRAPH_URL", - "weight": 5.0, - "description": "URL contains both latin and non-latin characters", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "URL_MULTIPLE_AT_SIGNS", - "weight": 3.0, - "description": "URL has multiple @ signs", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "URL_NUMERIC_IP_USER", - "weight": 4.0, - "description": "URL uses numeric IP with user field", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HAS_GUC_PROXY_URI", - "weight": 1.0, - "description": "Has googleusercontent.com proxy URL", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - } - ] - }, - { - "group": "rspamdbl", - "rules": [ - { - "symbol": "RSPAMD_URIBL", - "weight": 4.500000, - "description": "Rspamd uribl, bl.rspamd.com", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RSPAMD_EMAILBL", - "weight": 2.500000, - "description": "Rspamd emailbl, bl.rspamd.com", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - } - ] - }, - { - "group": "blocked", - "rules": [ - { - "symbol": "RECEIVED_SPAMHAUS_BLOCKED_OPENRESOLVER", - "weight": 0.0, - "description": "You are querying Spamhaus from an open resolver, please see https://www.spamhaus.org/returnc/pub/", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SURBL_BLOCKED", - "weight": 0.0, - "description": "SURBL: query blocked by policy/overusage", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SPAMHAUS_BLOCKED", - "weight": 0.0, - "description": "You are exceeding the query limit, please see https://www.spamhaus.org/returnc/vol/", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DBL_BLOCKED_OPENRESOLVER", - "weight": 0.0, - "description": "You are querying Spamhaus from an open resolver, please see https://www.spamhaus.org/returnc/pub/", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RECEIVED_SPAMHAUS_BLOCKED", - "weight": 0.0, - "description": "You are exceeding the query limit, please see https://www.spamhaus.org/returnc/vol/", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DNSWL_BLOCKED", - "weight": 0.0, - "description": "https://www.dnswl.org: Resolver blocked due to excessive queries", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DWL_DNSWL_BLOCKED", - "weight": 0.0, - "description": "https://www.dnswl.org: Resolver blocked due to excessive queries (DWL)", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SPAMHAUS_BLOCKED_OPENRESOLVER", - "weight": 0.0, - "description": "You are querying Spamhaus from an open resolver, please see https://www.spamhaus.org/returnc/pub/", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "URIBL_BLOCKED", - "weight": 0.0, - "description": "URIBL.com: query refused, likely due to policy/overusage", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SENDERSCORE_REPUT_BLOCKED", - "weight": 0.0, - "description": "Excessive number of queries to SenderScore RPBL, more info: https://knowledge.validity.com/hc/en-us/articles/20961730681243", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SENDERSCORE_BLOCKED", - "weight": 0.0, - "description": "Excessive number of queries to SenderScore RPBL, more info: https://knowledge.validity.com/hc/en-us/articles/20961730681243" - }, - { - "symbol": "DBL_BLOCKED", - "weight": 0.0, - "description": "You are exceeding the query limit, please see https://www.spamhaus.org/returnc/vol/", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - } - ] - }, - { - "group": "blocklistde", - "rules": [ - { - "symbol": "RECEIVED_BLOCKLISTDE", - "weight": 3.0, - "description": "Received address is listed in Blocklist (https://www.blocklist.de/)", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_BLOCKLISTDE", - "weight": 4.0, - "description": "From address is listed in Blocklist (https://www.blocklist.de/)", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - } - ] - }, - { - "group": "mime_types", - "rules": [ - { - "symbol": "MIME_DOUBLE_BAD_EXTENSION", - "weight": 3.0, - "description": "Bad extension cloaking", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MIME_TRACE", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MIME_ARCHIVE_IN_ARCHIVE", - "weight": 5.0, - "description": "Archive within another archive", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MIME_UNKNOWN", - "weight": 0.100000, - "description": "Missing or unknown content-type", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "ENCRYPTED_PGP", - "weight": -0.500000, - "description": "Message is encrypted with PGP", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MIME_GOOD", - "weight": -0.100000, - "description": "Known content-type", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "BOGUS_ENCRYPTED_AND_TEXT", - "weight": 10.0, - "description": "Bogus mix of encrypted and text/html payloads", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MIME_BAD_EXTENSION", - "weight": 2.0, - "description": "Bad extension", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MIME_EXE_IN_GEN_SPLIT_RAR", - "weight": 5.0, - "description": "EXE file in RAR archive with generic split extension (e.g. .001)", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MIME_ENCRYPTED_ARCHIVE", - "weight": 2.0, - "description": "Encrypted archive in a message", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MIME_BAD", - "weight": 1.0, - "description": "Known bad content-type", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SIGNED_SMIME", - "weight": -2.0, - "description": "Message is signed with S/MIME", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MIME_TYPES_CALLBACK", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MIME_BAD_UNICODE", - "weight": 2.0, - "description": "Filename with known obscured unicode characters", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SIGNED_PGP", - "weight": -2.0, - "description": "Message is signed with PGP", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MIME_OBFUSCATED_ARCHIVE", - "weight": 2.0, - "description": "Archive has files with clear obfuscation signs", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "ENCRYPTED_SMIME", - "weight": -0.500000, - "description": "Message is encrypted with S/MIME", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MIME_BAD_ATTACHMENT", - "weight": 4.0, - "description": "Invalid attachment mime type", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - } - ] - }, - { - "group": "antivirus", - "rules": [] - }, - { - "group": "spf", - "rules": [ - { - "symbol": "R_SPF_FAIL", - "weight": 1.0, - "description": "SPF verification failed", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SPF_CHECK", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "WHITELIST_SPF_DKIM", - "weight": -3.0, - "description": "Mail comes from the whitelisted domain and has valid SPF and DKIM policies", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "BLACKLIST_DMARC", - "weight": 6.0, - "description": "Mail comes from the whitelisted domain and has failed DMARC and DKIM policies", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "BLACKLIST_SPF_DKIM", - "weight": 3.0, - "description": "Mail comes from the whitelisted domain and has no valid SPF policy or a bad DKIM signature", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "R_SPF_PERMFAIL", - "weight": 0.0, - "description": "SPF record is malformed or persistent DNS error", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "R_SPF_ALLOW", - "weight": -0.200000, - "description": "SPF verification allows sending", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "R_SPF_SOFTFAIL", - "weight": 0.0, - "description": "SPF verification soft-failed", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "R_SPF_NEUTRAL", - "weight": 0.0, - "description": "SPF policy is neutral", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "R_SPF_PLUSALL", - "weight": 4.0, - "description": "SPF record allows to send from any IP", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "WHITELIST_DMARC", - "weight": -7.0, - "description": "Mail comes from the whitelisted domain and has valid DMARC and DKIM policies", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "R_SPF_DNSFAIL", - "weight": 0.0, - "description": "SPF DNS failure", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "R_SPF_NA", - "weight": 0.0, - "description": "Missing SPF record", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "BLACKLIST_SPF", - "weight": 1.0, - "description": "Mail comes from the whitelisted domain and has no valid SPF policy", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "WHITELIST_SPF", - "weight": -1.0, - "description": "Mail comes from the whitelisted domain and has a valid SPF policy", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - } - ] - }, - { - "group": "hfilter", - "rules": [ - { - "symbol": "HFILTER_URL_ONELINE", - "weight": 2.500000, - "description": "One line URL and text in body", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HFILTER_HELO_3", - "weight": 2.0, - "description": "Helo host checks (medium)", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HFILTER_HOSTNAME_1", - "weight": 0.500000, - "description": "Hostname checks (very low)", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HFILTER_HELO_4", - "weight": 2.500000, - "description": "Helo host checks (hard)", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HFILTER_HELO_BAREIP", - "weight": 3.0, - "description": "Helo host is bare ip", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HFILTER_HOSTNAME_4", - "weight": 2.500000, - "description": "Hostname checks (hard)", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HFILTER_HELO_1", - "weight": 0.500000, - "description": "Helo host checks (very low)", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HFILTER_HELO_5", - "weight": 3.0, - "description": "Helo host checks (very hard)", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HFILTER_HELO_NORESOLVE_MX", - "weight": 0.200000, - "description": "MX found in Helo and no resolve", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HFILTER_HOSTNAME_3", - "weight": 2.0, - "description": "Hostname checks (medium)", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HFILTER_RCPT_BOUNCEMOREONE", - "weight": 1.500000, - "description": "Message from bounce and over 1 recipient", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HFILTER_FROMHOST_NORES_A_OR_MX", - "weight": 1.500000, - "description": "FROM host no resolve to A or MX", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HFILTER_HELO_2", - "weight": 1.0, - "description": "Helo host checks (low)", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HFILTER_HELO_BADIP", - "weight": 4.500000, - "description": "Helo host is very bad ip", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HFILTER_HOSTNAME_2", - "weight": 1.0, - "description": "Hostname checks (low)", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HFILTER_HOSTNAME_5", - "weight": 3.0, - "description": "Hostname checks (very hard)", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HFILTER_FROM_BOUNCE", - "weight": 0.0, - "description": "Bounce message", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RDNS_DNSFAIL", - "weight": 0.0, - "description": "PTR verification DNS error", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HFILTER_HELO_NOT_FQDN", - "weight": 2.0, - "description": "Helo not FQDN", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HFILTER_HELO_NORES_A_OR_MX", - "weight": 0.300000, - "description": "Helo no resolve to A or MX", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HFILTER_FROMHOST_NORESOLVE_MX", - "weight": 0.500000, - "description": "MX found in FROM host and no resolve", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HFILTER_FROMHOST_NOT_FQDN", - "weight": 3.0, - "description": "FROM host not FQDN", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HFILTER_HOSTNAME_UNKNOWN", - "weight": 2.500000, - "description": "Unknown client hostname (PTR or FCrDNS verification failed)", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RDNS_NONE", - "weight": 2.0, - "description": "Cannot resolve reverse DNS for sender's IP", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HFILTER_HELO_IP_A", - "weight": 1.0, - "description": "Helo A IP != hostname IP", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HFILTER_URL_ONLY", - "weight": 2.200000, - "description": "URL only in body", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - } - ] - }, - { - "group": "spamhaus", - "rules": [ - { - "symbol": "RBL_SPAMHAUS_DROP", - "weight": 7.0, - "description": "From address is listed in Spamhaus DROP", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SPAMHAUS_PBL", - "weight": 2.0, - "description": "From address is listed in Spamhaus PBL", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DBL_BLOCKED", - "weight": 0.0, - "description": "You are exceeding the query limit, please see https://www.spamhaus.org/returnc/vol/", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SPAMHAUS_BLOCKED", - "weight": 0.0, - "description": "You are exceeding the query limit, please see https://www.spamhaus.org/returnc/vol/", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DBL_ABUSE_BOTNET", - "weight": 6.500000, - "description": "A domain in the message is listed in Spamhaus DBL as abused legit botnet C&C", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SPAMHAUS_BLOCKED_OPENRESOLVER", - "weight": 0.0, - "description": "You are querying Spamhaus from an open resolver, please see https://www.spamhaus.org/returnc/pub/", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DBL_PROHIBIT", - "weight": 0.0, - "description": "DBL uribl IP queries prohibited!", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DBL_ABUSE", - "weight": 5.0, - "description": "A domain in the message is listed in Spamhaus DBL as abused legit spam", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SPAMHAUS_ZEN_URIBL", - "weight": 0.0, - "description": "Unrecognised result from Spamhaus ZEN URIBL" - }, - { - "symbol": "RBL_SPAMHAUS", - "weight": 0.0, - "description": "Unrecognised result from Spamhaus ZEN", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RECEIVED_SPAMHAUS_BLOCKED", - "weight": 0.0, - "description": "You are exceeding the query limit, please see https://www.spamhaus.org/returnc/vol/", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DBL_BOTNET", - "weight": 7.500000, - "description": "A domain in the message is listed in Spamhaus DBL as botnet C&C", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RECEIVED_SPAMHAUS_PBL", - "weight": 0.0, - "description": "Received address is listed in Spamhaus PBL", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "URIBL_SBL", - "weight": 6.500000, - "description": "A domain in the message body resolves to an IP listed in Spamhaus SBL" - }, - { - "symbol": "RBL_SPAMHAUS_SBL", - "weight": 4.0, - "description": "From address is listed in Spamhaus SBL", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DBL_BLOCKED_OPENRESOLVER", - "weight": 0.0, - "description": "You are querying Spamhaus from an open resolver, please see https://www.spamhaus.org/returnc/pub/", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RECEIVED_SPAMHAUS_SBL", - "weight": 3.0, - "description": "Received address is listed in Spamhaus SBL", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DBL_ABUSE_REDIR", - "weight": 5.0, - "description": "A domain in the message is listed in Spamhaus DBL as spammed redirector domain", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SPAMHAUS_CSS", - "weight": 2.0, - "description": "From address is listed in Spamhaus CSS", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DBL_ABUSE_PHISH", - "weight": 6.500000, - "description": "A domain in the message is listed in Spamhaus DBL as abused legit phish", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RECEIVED_SPAMHAUS_XBL", - "weight": 1.0, - "description": "Received address is listed in Spamhaus XBL", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DBL_SPAM", - "weight": 6.500000, - "description": "A domain in the message is listed in Spamhaus DBL as spam", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "URIBL_PBL", - "weight": 0.010000, - "description": "A domain in the message body resolves to an IP listed in Spamhaus PBL" - }, - { - "symbol": "URIBL_DROP", - "weight": 5.0, - "description": "A domain in the message body resolves to an IP listed in Spamhaus DROP" - }, - { - "symbol": "RECEIVED_SPAMHAUS_CSS", - "weight": 1.0, - "description": "Received address is listed in Spamhaus CSS", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DBL_PHISH", - "weight": 7.500000, - "description": "A domain in the message is listed in Spamhaus DBL as phishing", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DBL_ABUSE_MALWARE", - "weight": 6.500000, - "description": "A domain in the message is listed in Spamhaus DBL as abused legit malware", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SPAMHAUS_XBL", - "weight": 4.0, - "description": "From address is listed in Spamhaus XBL", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DBL", - "weight": 0.0, - "description": "Unrecognised result from Spamhaus DBL", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DBL_MALWARE", - "weight": 7.500000, - "description": "A domain in the message is listed in Spamhaus DBL as malware", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RECEIVED_SPAMHAUS_BLOCKED_OPENRESOLVER", - "weight": 0.0, - "description": "You are querying Spamhaus from an open resolver, please see https://www.spamhaus.org/returnc/pub/", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "URIBL_XBL", - "weight": 3.0, - "description": "A domain in the message body resolves to an IP listed in Spamhaus XBL" - }, - { - "symbol": "URIBL_SBL_CSS", - "weight": 5.0, - "description": "A domain in the message body resolves to an IP listed in Spamhaus CSS" - }, - { - "symbol": "RECEIVED_SPAMHAUS_DROP", - "weight": 6.0, - "description": "Received address is listed in Spamhaus DROP", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - } - ] - }, - { - "group": "ebl", - "rules": [ - { - "symbol": "MSBL_EBL", - "weight": 7.500000, - "description": "MSBL emailbl (https://www.msbl.org/)", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MSBL_EBL_GREY", - "weight": 0.500000, - "description": "MSBL emailbl grey list (https://www.msbl.org/)", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - } - ] - }, - { - "group": "surblorg", - "rules": [ - { - "symbol": "CRACKED_SURBL", - "weight": 5.0, - "description": "A domain in the message is listed in SURBL as cracked", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SURBL_BLOCKED", - "weight": 0.0, - "description": "SURBL: query blocked by policy/overusage", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "PH_SURBL_MULTI", - "weight": 7.500000, - "description": "A domain in the message is listed in SURBL as phishing", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "ABUSE_SURBL", - "weight": 5.0, - "description": "A domain in the message is listed in SURBL as abused", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "CT_SURBL", - "weight": 0.0, - "description": "A domain in the message is listed in SURBL as a clicktracker", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MW_SURBL_MULTI", - "weight": 7.500000, - "description": "A domain in the message is listed in SURBL as malware", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DM_SURBL", - "weight": 0.0, - "description": "A domain in the message is listed in SURBL as belonging to a disposable email service", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - } - ] - }, - { - "group": "uribl", - "rules": [ - { - "symbol": "URIBL_GREY", - "weight": 2.500000, - "description": "A domain in the message is listed in URIBL.com grey", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "URIBL_MULTI", - "weight": 0.0, - "description": "Unrecognised result from URIBL.com", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "URIBL_BLOCKED", - "weight": 0.0, - "description": "URIBL.com: query refused, likely due to policy/overusage", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "URIBL_BLACK", - "weight": 7.500000, - "description": "A domain in the message is listed in URIBL.com black", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "URIBL_RED", - "weight": 0.500000, - "description": "A domain in the message is listed in URIBL.com red", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - } - ] - }, - { - "group": "external_services", - "rules": [] - }, - { - "group": "experimental", - "rules": [ - { - "symbol": "XM_UA_NO_VERSION", - "weight": 0.010000, - "description": "X-Mailer/User-Agent header has no version number", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - } - ] - }, - { - "group": "composite", - "rules": [ - { - "symbol": "SUSPICIOUS_AUTH_ORIGIN", - "weight": 0.0, - "description": "Message authenticated, but from a suspicios origin (potentially an injector)", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FORGED_RECIPIENTS_FORWARDING", - "weight": 0.0, - "description": "FORGED_RECIPIENTS & g:forwarding", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "UNDISC_RCPTS_BULK", - "weight": 3.0, - "description": "Missing or undisclosed recipients with a bulk signature", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SUSPICIOUS_URL_IN_SUSPICIOUS_MESSAGE", - "weight": 1.0, - "description": "Message contains redirector, anonymous or IPFS gateway URL and is marked by fuzzy/bayes/SURBL/RBL", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RCVD_UNAUTH_PBL", - "weight": 2.0, - "description": "Relayed through Spamhaus PBL IP without sufficient authentication (possibly indicating an open relay)", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "APPLE_MAILER_COMMON", - "weight": 0.0, - "description": "Message was sent by 'Apple Mail' and has common symbols in place", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FORGED_SENDER_MAILLIST", - "weight": 0.0, - "description": "Sender is not the same as MAIL FROM: envelope, but a message is from a maillist", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "PHISH_EMOTION", - "weight": 1.0, - "description": "Phish message with subject trying to address users emotion", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DMARC_POLICY_ALLOW_WITH_FAILURES", - "weight": -0.500000, - "description": "DMARC permit policy with DKIM/SPF failure", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "AUTH_NA_OR_FAIL", - "weight": 1.0, - "description": "No authenticating method SPF/DKIM/DMARC/ARC was successful", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "REDIRECTOR_URL_ONLY", - "weight": 1.0, - "description": "Message only contains a redirector URL", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FORGED_RECIPIENTS_MAILLIST", - "weight": 0.0, - "description": "Recipients are not the same as RCPT TO: mail command, but a message from a maillist", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FORGED_SENDER_VERP_SRS", - "weight": 0.0, - "description": "FORGED_SENDER & (ENVFROM_PRVS | ENVFROM_VERP)", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HAS_ANON_DOMAIN", - "weight": 0.100000, - "description": "Contains one or more domains trying to disguise owner/destination", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "BROKEN_HEADERS_MAILLIST", - "weight": 0.0, - "description": "Negate BROKEN_HEADERS when message comes via some mailing list", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "AUTOGEN_PHP_SPAMMY", - "weight": 1.0, - "description": "Message was generated by PHP script and contains some spam indicators", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "APPLE_IOS_MAILER_COMMON", - "weight": 0.0, - "description": "Message was sent by 'Apple iOS Mail' and has common symbols in place", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "IP_SCORE_FREEMAIL", - "weight": 0.0, - "description": "Negate IP_SCORE when message comes from FreeMail", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "VIOLATED_DIRECT_SPF", - "weight": 3.500000, - "description": "Has no Received (or no trusted received relays) and SPF policy fails or soft fails", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "AUTH_NA", - "weight": 1.0, - "description": "Authenticating message via SPF/DKIM/DMARC/ARC not available", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FREEMAIL_REPLYTO_NEQ_FROM", - "weight": 2.0, - "description": "Reply-To is a Freemail address and it not match From header or SMTP From, also From is not another Freemail", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MIME_BAD_EXT_IN_OBFUSCATED_ARCHIVE", - "weight": 8.0, - "description": "Attachment with bad extension and archive that has filename with clear obfuscation signs", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "BAD_REP_POLICIES", - "weight": 0.100000, - "description": "Contains valid policies but are also marked by fuzzy/bayes/SURBL/RBL", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MISSING_MID_ALLOWED", - "weight": 0.0, - "description": "MISSING_MID_ALLOWED", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FORGED_MUA_MAILLIST", - "weight": 0.0, - "description": "Avoid false positives for FORGED_MUA_* in maillist", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SPF_FAIL_FORWARDING", - "weight": 0.0, - "description": "g:forwarding & (R_SPF_SOFTFAIL | R_SPF_FAIL)", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "INVALID_MSGID_ALLOWED", - "weight": 0.0, - "description": "INVALID_MSGID_ALLOWED", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RCVD_DKIM_ARC_DNSWL_HI", - "weight": -1.0, - "description": "Sufficiently DKIM/ARC signed and received from IP with high trust at DNSWL", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FORGED_SENDER_FORWARDING", - "weight": 0.0, - "description": "Forged sender, but message is forwarded", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MIME_BAD_EXT_WITH_BAD_UNICODE", - "weight": 8.0, - "description": "Attachment with bad extension and filename that has known obscured unicode characters", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RCVD_DKIM_ARC_DNSWL_MED", - "weight": -0.500000, - "description": "Sufficiently DKIM/ARC signed and received from IP with medium trust at DNSWL", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DKIM_MIXED", - "weight": 0.0, - "description": "-R_DKIM_ALLOW & (R_DKIM_TEMPFAIL | R_DKIM_PERMFAIL | R_DKIM_REJECT)", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "BOUNCE_NO_AUTH", - "weight": 1.0, - "description": "(AUTH_NA | AUTH_NA_OR_FAIL) & (BOUNCE | SUBJ_BOUNCE_WORDS)", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - } - ] - }, - { - "group": "mid", - "rules": [ - { - "symbol": "MID_END_EQ_FROM_USER_PART", - "weight": 4.0, - "description": "Message-ID RHS (after @) and MIME from local part are the same", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "CHECK_MID", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "KNOWN_MID", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "KNOWN_NO_MID", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "KNOWN_MID_CALLBACK", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - } - ] - }, - { - "group": "fuzzy", - "rules": [ - { - "symbol": "FUZZY_DENIED", - "weight": 12.0, - "description": "Denied fuzzy hash, bl.rspamd.com", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FUZZY_PROB", - "weight": 5.0, - "description": "Probable fuzzy hash, bl.rspamd.com", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FUZZY_ENCRYPTION_REQUIRED", - "weight": 0.0, - "description": "Fuzzy encryption is required by a server", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FUZZY_WHITE", - "weight": -2.100000, - "description": "Whitelisted fuzzy hash, bl.rspamd.com", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FUZZY_FORBIDDEN", - "weight": 0.0, - "description": "Fuzzy access denied", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FUZZY_RATELIMITED", - "weight": 0.0, - "description": "Fuzzy rate limit is reached", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FUZZY_UNKNOWN", - "weight": 5.0, - "description": "Generic fuzzy hash match, bl.rspamd.com", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FUZZY_CALLBACK", - "weight": 0.0, - "description": "Fuzzy check callback", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - } - ] - }, - { - "group": "senderscore", - "rules": [ - { - "symbol": "RBL_SENDERSCORE_NA", - "weight": 0.0, - "description": "From address is listed in SenderScore RPBL - noauth" - }, - { - "symbol": "RBL_SENDERSCORE_REPUT_2", - "weight": 3.0, - "description": "SenderScore Reputation: Bad (20-29).", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SENDERSCORE_SUS_ATT_NA", - "weight": 1.0, - "description": "From address is listed in SenderScore RPBL - suspect_attachments+noauth" - }, - { - "symbol": "RBL_SENDERSCORE_SCORE", - "weight": 2.0, - "description": "From address is listed in SenderScore RPBL - sender_score" - }, - { - "symbol": "RBL_SENDERSCORE_REPUT_9", - "weight": -1.0, - "description": "SenderScore Reputation: Good (90-100).", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SENDERSCORE_REPUT_4", - "weight": 2.0, - "description": "SenderScore Reputation: Bad (40-49).", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SENDERSCORE_REPUT_1", - "weight": 3.500000, - "description": "SenderScore Reputation: Bad (10-19).", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SENDERSCORE_REPUT_UNKNOWN", - "weight": 0.0, - "description": "Unrecognized result from SenderScore Reputation list.", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SENDERSCORE_SCORE_NA", - "weight": 2.0, - "description": "From address is listed in SenderScore RPBL - sender_score+noauth" - }, - { - "symbol": "RBL_SENDERSCORE_BLOCKED", - "weight": 0.0, - "description": "Excessive number of queries to SenderScore RPBL, more info: https://knowledge.validity.com/hc/en-us/articles/20961730681243" - }, - { - "symbol": "RBL_SENDERSCORE_REPUT_8", - "weight": 0.0, - "description": "SenderScore Reputation: Neutral (80-89).", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SENDERSCORE_SCORE_PRST_NA", - "weight": 4.0, - "description": "From address is listed in SenderScore RPBL - sender_score+pristine+noauth" - }, - { - "symbol": "RBL_SENDERSCORE_PRST_NA", - "weight": 2.0, - "description": "From address is listed in SenderScore RPBL - pristine+noauth" - }, - { - "symbol": "RBL_SENDERSCORE_PRST_NA_BOT", - "weight": 3.0, - "description": "From address is listed in SenderScore RPBL - pristine+noauth+botnet" - }, - { - "symbol": "RBL_SENDERSCORE_PRST_BOT", - "weight": 3.0, - "description": "From address is listed in SenderScore RPBL - pristine+botnet" - }, - { - "symbol": "RBL_SENDERSCORE_SCORE_SUS_ATT_NA", - "weight": 3.0, - "description": "From address is listed in SenderScore RPBL - sender_score+suspect_attachments+noauth" - }, - { - "symbol": "RBL_SENDERSCORE_REPUT_6", - "weight": 1.0, - "description": "SenderScore Reputation: Bad (60-69).", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SENDERSCORE_PRST", - "weight": 2.0, - "description": "From address is listed in SenderScore RPBL - pristine" - }, - { - "symbol": "RBL_SENDERSCORE_REPUT_0", - "weight": 4.0, - "description": "SenderScore Reputation: Very Bad (0-9).", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SENDERSCORE_SUS_ATT", - "weight": 1.0, - "description": "From address is listed in SenderScore RPBL - suspect_attachments" - }, - { - "symbol": "RBL_SENDERSCORE_SCORE_PRST", - "weight": 4.0, - "description": "From address is listed in SenderScore RPBL - sender_score+pristine" - }, - { - "symbol": "RBL_SENDERSCORE_REPUT_3", - "weight": 2.500000, - "description": "SenderScore Reputation: Bad (30-39).", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SENDERSCORE_REPUT_5", - "weight": 1.500000, - "description": "SenderScore Reputation: Bad (50-59).", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SENDERSCORE_SUS_ATT_PRST_NA", - "weight": 3.0, - "description": "From address is listed in SenderScore RPBL - suspect_attachments+pristine+noauth" - }, - { - "symbol": "RBL_SENDERSCORE_NA_BOT", - "weight": 1.0, - "description": "From address is listed in SenderScore RPBL - noauth+botnet" - }, - { - "symbol": "RBL_SENDERSCORE_REPUT_7", - "weight": 0.500000, - "description": "SenderScore Reputation: Bad (70-79).", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SENDERSCORE_SUS_ATT_NA_BOT", - "weight": 1.500000, - "description": "From address is listed in SenderScore RPBL - suspect_attachments+noauth+botnet" - }, - { - "symbol": "RBL_SENDERSCORE_SUS_ATT_PRST_NA_BOT", - "weight": 3.500000, - "description": "From address is listed in SenderScore RPBL - suspect_attachments+pristine+noauth+botnet" - }, - { - "symbol": "RBL_SENDERSCORE_BOT", - "weight": 2.0, - "description": "From address is listed in SenderScore RPBL - botnet" - }, - { - "symbol": "RBL_SENDERSCORE_REPUT_BLOCKED", - "weight": 0.0, - "description": "Excessive number of queries to SenderScore RPBL, more info: https://knowledge.validity.com/hc/en-us/articles/20961730681243", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - } - ] - }, - { - "group": "aliases", - "rules": [ - { - "symbol": "TAGGED_RCPT", - "weight": 0.0, - "description": "Recipient has plus-tags", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "TAGGED_FROM", - "weight": 0.0, - "description": "From address has plus-tags", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "INTERNAL_MAIL", - "weight": 0.0, - "description": "Mail from local to local domain", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "ALIASES_CHECK", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "LOCAL_INBOUND", - "weight": 0.0, - "description": "Mail from external to local domain", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "ALIAS_RESOLVED", - "weight": 0.0, - "description": "Address was resolved through aliases", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "LOCAL_OUTBOUND", - "weight": 0.0, - "description": "Mail from local to external domain", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - } - ] - }, - { - "group": "malware", - "rules": [ - { - "symbol": "EXE_ARCHIVE_CLICKBAIT_FILENAME", - "weight": 9.0, - "description": "exe file in archive with clickbait filename", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "EXE_ARCHIVE_CLICKBAIT_SUBJECT", - "weight": 9.0, - "description": "exe file in archive with clickbait subject", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MISIDENTIFIED_RAR", - "weight": 4.0, - "description": "rar with wrong extension", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "EXE_IN_ARCHIVE", - "weight": 1.500000, - "description": "exe file in archive", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "EXE_IN_MISIDENTIFIED_RAR", - "weight": 5.0, - "description": "rar with wrong extension containing exe file", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SINGLE_FILE_ARCHIVE_WITH_EXE", - "weight": 5.0, - "description": "single file container bearing executable", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - } - ] - }, - { - "group": "mailspike", - "rules": [ - { - "symbol": "MAILSPIKE", - "weight": 0.0, - "description": "Unrecognised result from Mailspike", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_MAILSPIKE_BAD", - "weight": 1.0, - "description": "From address is listed in Mailspike RBL - bad reputation", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_MAILSPIKE_VERYBAD", - "weight": 1.500000, - "description": "From address is listed in Mailspike RBL - very bad reputation", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RWL_MAILSPIKE_GOOD", - "weight": -0.100000, - "description": "From address is listed in Mailspike RWL - good reputation", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RWL_MAILSPIKE_VERYGOOD", - "weight": -0.200000, - "description": "From address is listed in Mailspike RWL - very good reputation", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RWL_MAILSPIKE_POSSIBLE", - "weight": 0.0, - "description": "From address is listed in Mailspike RWL - possibly legit", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RWL_MAILSPIKE_EXCELLENT", - "weight": -0.400000, - "description": "From address is listed in Mailspike RWL - excellent reputation", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RWL_MAILSPIKE_NEUTRAL", - "weight": 0.0, - "description": "Neutral result from Mailspike", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_MAILSPIKE_WORST", - "weight": 2.0, - "description": "From address is listed in Mailspike RBL - worst possible reputation", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - } - ] - }, - { - "group": "compromised_hosts", - "rules": [ - { - "symbol": "URI_HIDDEN_PATH", - "weight": 1.0, - "description": "Message contains URI with a hidden path", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "XAW_SERVICE_ACCT", - "weight": 1.0, - "description": "Message originally from a service account", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HIDDEN_SOURCE_OBJ", - "weight": 2.0, - "description": "UNIX hidden file/directory in path", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HAS_PHPMAILER_SIG", - "weight": 0.0, - "description": "PHPMailer signature", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "WWW_DOT_DOMAIN", - "weight": 0.500000, - "description": "From/Sender/Reply-To or Envelope is @www.domain.com", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HAS_X_SOURCE", - "weight": 0.0, - "description": "Has X-Source headers", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HACKED_WP_PHISHING", - "weight": 4.500000, - "description": "Phish message sent by hacked Wordpress instance", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HAS_XAW", - "weight": 0.0, - "description": "Has X-Authentication-Warning header", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HAS_X_PHP_SCRIPT", - "weight": 0.0, - "description": "Has X-PHP-Script header", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "PHP_SCRIPT_ROOT", - "weight": 1.0, - "description": "PHP Script executed by root UID", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "PHP_XPS_PATTERN", - "weight": 0.0, - "description": "Message contains X-PHP-Script pattern", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HAS_X_AS", - "weight": 0.0, - "description": "Has X-Authenticated-Sender header", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "COMPROMISED_ACCT_BULK", - "weight": 3.0, - "description": "Likely to be from a compromised account", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "X_PHP_EVAL", - "weight": 4.0, - "description": "Message sent using eval'd PHP", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HAS_X_POS", - "weight": 0.0, - "description": "Has X-PHP-Originating-Script header", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HAS_WP_URI", - "weight": 0.0, - "description": "Contains WordPress URIs", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "ABUSE_FROM_INJECTOR", - "weight": 2.0, - "description": "Message is sent from a suspicios origin and showing signs of abuse, likely spam injected in compromised account", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HAS_X_GMSV", - "weight": 0.0, - "description": "Has X-Get-Message-Sender-Via: header", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FROM_SERVICE_ACCT", - "weight": 1.0, - "description": "Sender/From/Reply-To is a service account", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "ENVFROM_SERVICE_ACCT", - "weight": 1.0, - "description": "Envelope from is a service account", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HAS_X_ANTIABUSE", - "weight": 0.0, - "description": "Has X-AntiAbuse headers", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "WP_COMPROMISED", - "weight": 0.0, - "description": "URL that is pointing to a compromised WordPress installation", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MID_RHS_WWW", - "weight": 0.500000, - "description": "Message-ID from www host", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - } - ] - }, - { - "group": "html", - "rules": [ - { - "symbol": "ZERO_FONT", - "weight": 1.0, - "description": "Zero sized font used", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HTML_SHORT_LINK_IMG_1", - "weight": 2.0, - "description": "Short HTML part (0..1K) with a link to an image", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "R_WHITE_ON_WHITE", - "weight": 4.0, - "description": "Message contains low contrast text", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HTML_SHORT_LINK_IMG_2", - "weight": 1.0, - "description": "Short HTML part (1K..1.5K) with a link to an image", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HTML_VISIBLE_CHECKS", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HTML_SHORT_LINK_IMG_3", - "weight": 0.500000, - "description": "Short HTML part (1.5K..2K) with a link to an image", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HAS_DATA_URI", - "weight": 0.0, - "description": "Has Data URI encoding", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HTTP_TO_IP", - "weight": 1.0, - "description": "HTML anchor points to an IP address", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "R_EMPTY_IMAGE", - "weight": 2.0, - "description": "Message contains empty parts and image", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MANY_INVISIBLE_PARTS", - "weight": 1.0, - "description": "Many parts are visually hidden", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "R_SUSPICIOUS_IMAGES", - "weight": 5.0, - "description": "Message has high image to text ratio", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HTTP_TO_HTTPS", - "weight": 0.500000, - "description": "The anchor text contains a distinct scheme compared to the target URL", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "EXT_CSS", - "weight": 1.0, - "description": "Message contains external CSS reference", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DATA_URI_OBFU", - "weight": 2.0, - "description": "Uses Data URI encoding to obfuscate plain or HTML in base64", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "HTML_META_REFRESH_URL", - "weight": 5.0, - "description": "Has HTML Meta refresh URL", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - } - ] - }, - { - "group": "subject", - "rules": [ - { - "symbol": "SUBJ_ALL_CAPS", - "weight": 3.0, - "description": "Subject contains mostly capital letters", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "LONG_SUBJ", - "weight": 3.0, - "description": "Subject is very long", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "URL_IN_SUBJECT", - "weight": 4.0, - "description": "Subject contains URL", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - } - ] - }, - { - "group": "ungrouped", - "rules": [ - { - "symbol": "ARC_SIGNED", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "ASN", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DKIM_SIGNED", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "BLOCKLISTDE_FAIL", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DWL_DNSWL_FAIL", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MSBL_EBL_FAIL", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MAILSPIKE_FAIL", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SPAMHAUS_FAIL", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SEM_URIBL_FRESH15_UNKNOWN_FAIL", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SPF_CHECK", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SURBL_HASHBL_FAIL", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SEM_FAIL", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RCVD_IN_DNSWL_FAIL", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SINGLE_SHORT_PART", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SURBL_MULTI_FAIL", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "UDF_COMPRESSION_500PLUS", - "weight": 9.0, - "description": "very well compressed img file in archive", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "ASN_FAIL", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_VIRUSFREE_UNKNOWN_FAIL", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SENDERSCORE_REPUT_UNKNOWN_FAIL", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RSPAMD_EMAILBL_FAIL", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "URIBL_MULTI_FAIL", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "DBL_FAIL", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RSPAMD_URIBL_FAIL", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "RBL_SEM_IPV6_FAIL", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SEM_URIBL_UNKNOWN_FAIL", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - } - ] - }, - { - "group": "mua", - "rules": [ - { - "symbol": "FORGED_MUA_THEBAT_MSGID_UNKNOWN", - "weight": 3.0, - "description": "Message pretends to be send from The Bat! but has forged Message-ID", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FORGED_MUA_KMAIL_MSGID_UNKNOWN", - "weight": 2.500000, - "description": "Message pretends to be send from KMail but has forged Message-ID", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FORGED_MUA_OPERA_MSGID", - "weight": 4.0, - "description": "Message pretends to be send from Opera Mail but has forged Message-ID", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FORGED_MUA_SEAMONKEY_MSGID", - "weight": 4.0, - "description": "Forged mail pretending to be from Mozilla Seamonkey but has forged Message-ID", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FORGED_MUA_SEAMONKEY_MSGID_UNKNOWN", - "weight": 2.500000, - "description": "Forged mail pretending to be from Mozilla Seamonkey but has forged Message-ID", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FORGED_MUA_OUTLOOK", - "weight": 3.0, - "description": "Forged Outlook MUA", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SUSPICIOUS_BOUNDARY2", - "weight": 4.0, - "description": "Suspicious boundary in Content-Type header", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FORGED_MUA_THEBAT_MSGID", - "weight": 4.0, - "description": "Message pretends to be send from The Bat! but has forged Message-ID", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SUSPICIOUS_BOUNDARY3", - "weight": 3.0, - "description": "Suspicious boundary in Content-Type header", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SUSPICIOUS_BOUNDARY4", - "weight": 4.0, - "description": "Suspicious boundary in Content-Type header", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SUSPICIOUS_BOUNDARY", - "weight": 5.0, - "description": "Suspicious boundary in Content-Type header", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FORGED_MUA_POSTBOX_MSGID_UNKNOWN", - "weight": 2.500000, - "description": "Forged mail pretending to be from Postbox but has forged Message-ID", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FORGED_MUA_MOZILLA_MAIL_MSGID", - "weight": 4.0, - "description": "Message pretends to be send from Mozilla Mail but has forged Message-ID", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FORGED_MUA_THUNDERBIRD_MSGID_UNKNOWN", - "weight": 2.500000, - "description": "Forged mail pretending to be from Mozilla Thunderbird but has forged Message-ID", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FORGED_MUA_MAILLIST", - "weight": 0.0, - "description": "Avoid false positives for FORGED_MUA_* in maillist", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FORGED_MUA_THUNDERBIRD_MSGID", - "weight": 4.0, - "description": "Forged mail pretending to be from Mozilla Thunderbird but has forged Message-ID", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FORGED_MUA_MOZILLA_MAIL_MSGID_UNKNOWN", - "weight": 2.500000, - "description": "Message pretends to be send from Mozilla Mail but has forged Message-ID", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FORGED_MUA_POSTBOX_MSGID", - "weight": 4.0, - "description": "Forged mail pretending to be from Postbox but has forged Message-ID", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - } - ] - }, - { - "group": "whitelist", - "rules": [ - { - "symbol": "WHITELIST_DKIM", - "weight": -1.0, - "description": "Mail comes from the whitelisted domain and has a valid DKIM signature", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "WHITELIST_SPF_DKIM", - "weight": -3.0, - "description": "Mail comes from the whitelisted domain and has valid SPF and DKIM policies", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "BLACKLIST_DMARC", - "weight": 6.0, - "description": "Mail comes from the whitelisted domain and has failed DMARC and DKIM policies", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "WHITELIST_DMARC", - "weight": -7.0, - "description": "Mail comes from the whitelisted domain and has valid DMARC and DKIM policies", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "BLACKLIST_SPF_DKIM", - "weight": 3.0, - "description": "Mail comes from the whitelisted domain and has no valid SPF policy or a bad DKIM signature", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "BLACKLIST_DKIM", - "weight": 2.0, - "description": "Mail comes from the whitelisted domain and has non-valid DKIM signature", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "WHITELIST_SPF", - "weight": -1.0, - "description": "Mail comes from the whitelisted domain and has a valid SPF policy", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "BLACKLIST_SPF", - "weight": 1.0, - "description": "Mail comes from the whitelisted domain and has no valid SPF policy", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - } - ] - }, - { - "group": "blankspam", - "rules": [ - { - "symbol": "COMPLETELY_EMPTY", - "weight": 15.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SHORT_PART_BAD_HEADERS", - "weight": 7.0, - "description": "MISSING_ESSENTIAL_HEADERS & SINGLE_SHORT_PART", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MISSING_ESSENTIAL_HEADERS", - "weight": 7.0, - "description": "Common headers were entirely absent", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - } - ] - }, - { - "group": "content", - "rules": [ - { - "symbol": "PDF_TIMEOUT", - "weight": 0.0, - "description": "There is a PDF in the message that caused timeout in processing", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "PDF_LONG_TRAILER", - "weight": 0.200000, - "description": "There is an PDF with a long trailer in the message", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "PDF_JAVASCRIPT", - "weight": 0.100000, - "description": "There is an PDF with JavaScript in the message", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "PDF_MANY_OBJECTS", - "weight": 0.0, - "description": "There is a PDF with too many objects in the message", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "PDF_ENCRYPTED", - "weight": 0.300000, - "description": "There is an encrypted PDF in the message", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "PDF_SUSPICIOUS", - "weight": 4.500000, - "description": "There is an PDF with suspicious properties in the message", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - } - ] - }, - { - "group": "Message ID", - "rules": [ - { - "symbol": "MID_CONTAINS_TO", - "weight": 1.0, - "description": "Message-ID contains To address", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MID_MISSING_BRACKETS", - "weight": 0.500000, - "description": "Message-ID is missing <>'s", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MID_RHS_MATCH_TO", - "weight": 1.0, - "description": "Message-ID RHS matches To domain", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MID_RHS_NOT_FQDN", - "weight": 0.500000, - "description": "Message-ID RHS is not a fully-qualified domain name", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MID_RHS_MATCH_FROM", - "weight": 0.0, - "description": "Message-ID RHS matches From domain", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MID_CONTAINS_FROM", - "weight": 1.0, - "description": "Message-ID contains From address", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MID_BARE_IP", - "weight": 2.0, - "description": "Message-ID RHS is a bare IP address", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MID_RHS_IP_LITERAL", - "weight": 0.500000, - "description": "Message-ID RHS is an IP-literal", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "MID_RHS_MATCH_FROMTLD", - "weight": 0.0, - "description": "Message-ID RHS matches From domain tld", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - } - ] - }, - { - "group": "headers,mime", - "rules": [ - { - "symbol": "CHECK_TO_CC", - "weight": 0.0, - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - } - ] - }, - { - "group": "scams", - "rules": [ - { - "symbol": "LEAKED_PASSWORD_SCAM_RE", - "weight": 0.0, - "description": "Contains BTC wallet address and malicious regexps", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "FREEMAIL_AFF", - "weight": 4.0, - "description": "Message exhibits strong characteristics of advance fee fraud (AFF a/k/a '419' spam) involving freemail addresses", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "INTRODUCTION", - "weight": 2.0, - "description": "Sender introduces themselves", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "SUSPICIOUS_MDN", - "weight": 2.0, - "description": "Message delivery notification should go to freemail or disposable e-mail, but message was not sent from a freemail address", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "BITCOIN_ADDR", - "weight": 0.0, - "description": "Message has a valid bitcoin wallet address", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "LEAKED_PASSWORD_SCAM", - "weight": 7.0, - "description": "Contains BTC wallet address and scam patterns", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - } - ] - }, - { - "group": "body", - "rules": [ - { - "symbol": "HAS_ATTACHMENT", - "weight": 0.0, - "description": "Message contains attachments", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - }, - { - "symbol": "R_PARTS_DIFFER", - "weight": 1.0, - "description": "Text and HTML parts differ", - "frequency": 0.0, - "frequency_stddev": 0.0, - "time": 0.0 - } - ] - } -] diff --git a/pkg/analyzer/rspamd.go b/pkg/analyzer/rspamd.go deleted file mode 100644 index 9780f17..0000000 --- a/pkg/analyzer/rspamd.go +++ /dev/null @@ -1,174 +0,0 @@ -// This file is part of the happyDeliver (R) project. -// Copyright (c) 2026 happyDomain -// Authors: Pierre-Olivier Mercier, et al. -// -// This program is offered under a commercial and under the AGPL license. -// For commercial licensing, contact us at . -// -// For AGPL licensing: -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -package analyzer - -import ( - "math" - "regexp" - "strconv" - "strings" - - "git.happydns.org/happyDeliver/internal/api" -) - -// Default rspamd action thresholds (rspamd built-in defaults) -const ( - rspamdDefaultRejectThreshold float32 = 15 - rspamdDefaultAddHeaderThreshold float32 = 6 -) - -// RspamdAnalyzer analyzes rspamd results from email headers -type RspamdAnalyzer struct { - symbols map[string]string -} - -// NewRspamdAnalyzer creates a new rspamd analyzer with optional symbol descriptions -func NewRspamdAnalyzer(symbols map[string]string) *RspamdAnalyzer { - return &RspamdAnalyzer{symbols: symbols} -} - -// AnalyzeRspamd extracts and analyzes rspamd results from email headers -func (a *RspamdAnalyzer) AnalyzeRspamd(email *EmailMessage) *api.RspamdResult { - headers := email.GetRspamdHeaders() - if len(headers) == 0 { - return nil - } - - // Require at least X-Spamd-Result or X-Rspamd-Score to produce a meaningful report - _, hasSpamdResult := headers["X-Spamd-Result"] - _, hasRspamdScore := headers["X-Rspamd-Score"] - if !hasSpamdResult && !hasRspamdScore { - return nil - } - - result := &api.RspamdResult{ - Symbols: make(map[string]api.SpamTestDetail), - } - - // Parse X-Spamd-Result header (primary source for score, threshold, and symbols) - // Format: "default: False [-3.91 / 15.00];\n\tSYMBOL(score)[params]; ..." - if spamdResult, ok := headers["X-Spamd-Result"]; ok { - report := strings.ReplaceAll(spamdResult, "; ", ";\n") - result.Report = &report - a.parseSpamdResult(spamdResult, result) - } - - // Parse X-Rspamd-Score as override/fallback for score - if scoreHeader, ok := headers["X-Rspamd-Score"]; ok { - if score, err := strconv.ParseFloat(strings.TrimSpace(scoreHeader), 64); err == nil { - result.Score = float32(score) - } - } - - // Parse X-Rspamd-Server - if serverHeader, ok := headers["X-Rspamd-Server"]; ok { - server := strings.TrimSpace(serverHeader) - result.Server = &server - } - - // Populate symbol descriptions from the lookup map - if a.symbols != nil { - for name, sym := range result.Symbols { - if desc, ok := a.symbols[name]; ok { - sym.Description = &desc - result.Symbols[name] = sym - } - } - } - - // Derive IsSpam from score vs reject threshold. - if result.Threshold > 0 { - result.IsSpam = result.Score >= result.Threshold - } else { - result.IsSpam = result.Score >= rspamdDefaultAddHeaderThreshold - } - - return result -} - -// parseSpamdResult parses the X-Spamd-Result header -// Format: "default: False [-3.91 / 15.00];\n\tSYMBOL(score)[params]; ..." -func (a *RspamdAnalyzer) parseSpamdResult(header string, result *api.RspamdResult) { - // Extract score and threshold from the first line - // e.g. "default: False [-3.91 / 15.00]" - scoreRe := regexp.MustCompile(`\[\s*(-?\d+\.?\d*)\s*/\s*(-?\d+\.?\d*)\s*\]`) - if matches := scoreRe.FindStringSubmatch(header); len(matches) > 2 { - if score, err := strconv.ParseFloat(matches[1], 64); err == nil { - result.Score = float32(score) - } - if threshold, err := strconv.ParseFloat(matches[2], 64); err == nil { - result.Threshold = float32(threshold) - - // No threshold? use default AddHeaderThreshold - if result.Threshold <= 0 { - result.Threshold = rspamdDefaultAddHeaderThreshold - } - } - } - - // Parse is_spam from header (before we may get action from X-Rspamd-Action) - firstLine := strings.SplitN(header, ";", 2)[0] - if strings.Contains(firstLine, ": True") || strings.Contains(firstLine, ": true") { - result.IsSpam = true - } - - // Parse symbols: SYMBOL(score)[params] - // Each symbol entry is separated by ";", so within each part we use a - // greedy match to capture params that may contain nested brackets. - symbolRe := regexp.MustCompile(`(\w+)\((-?\d+\.?\d*)\)(?:\[(.*)\])?`) - for _, part := range strings.Split(header, ";") { - part = strings.TrimSpace(part) - matches := symbolRe.FindStringSubmatch(part) - if len(matches) > 2 { - name := matches[1] - score, _ := strconv.ParseFloat(matches[2], 64) - sym := api.SpamTestDetail{ - Name: name, - Score: float32(score), - } - if len(matches) > 3 && matches[3] != "" { - params := matches[3] - sym.Params = ¶ms - } - result.Symbols[name] = sym - } - } -} - -// CalculateRspamdScore calculates the rspamd contribution to deliverability (0-100 scale) -func (a *RspamdAnalyzer) CalculateRspamdScore(result *api.RspamdResult) (int, string) { - if result == nil { - return 100, "" // rspamd not installed - } - - threshold := result.Threshold - percentage := 100 - int(math.Round(float64(result.Score*100/(2*threshold)))) - - if percentage > 100 { - return 100, "A+" - } else if percentage < 0 { - return 0, "F" - } - - // Linear scale between 0 and threshold - return percentage, ScoreToGrade(percentage) -} diff --git a/pkg/analyzer/rspamd_symbols.go b/pkg/analyzer/rspamd_symbols.go deleted file mode 100644 index e50a452..0000000 --- a/pkg/analyzer/rspamd_symbols.go +++ /dev/null @@ -1,105 +0,0 @@ -// This file is part of the happyDeliver (R) project. -// Copyright (c) 2026 happyDomain -// Authors: Pierre-Olivier Mercier, et al. -// -// This program is offered under a commercial and under the AGPL license. -// For commercial licensing, contact us at . -// -// For AGPL licensing: -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -package analyzer - -import ( - _ "embed" - "encoding/json" - "io" - "log" - "net/http" - "strings" - "time" -) - -//go:embed rspamd-symbols.json -var embeddedRspamdSymbols []byte - -// rspamdSymbolGroup represents a group of rspamd symbols from the API/embedded JSON. -type rspamdSymbolGroup struct { - Group string `json:"group"` - Rules []rspamdSymbolEntry `json:"rules"` -} - -// rspamdSymbolEntry represents a single rspamd symbol entry. -type rspamdSymbolEntry struct { - Symbol string `json:"symbol"` - Description string `json:"description"` - Weight float64 `json:"weight"` -} - -// parseRspamdSymbolsJSON parses the rspamd symbols JSON into a name->description map. -func parseRspamdSymbolsJSON(data []byte) map[string]string { - var groups []rspamdSymbolGroup - if err := json.Unmarshal(data, &groups); err != nil { - log.Printf("Failed to parse rspamd symbols JSON: %v", err) - return nil - } - - symbols := make(map[string]string, len(groups)*10) - for _, g := range groups { - for _, r := range g.Rules { - if r.Description != "" { - symbols[r.Symbol] = r.Description - } - } - } - return symbols -} - -// LoadRspamdSymbols loads rspamd symbol descriptions. -// If apiURL is non-empty, it fetches from the rspamd API first, falling back to the embedded list on error. -func LoadRspamdSymbols(apiURL string) map[string]string { - if apiURL != "" { - if symbols := fetchRspamdSymbols(apiURL); symbols != nil { - return symbols - } - log.Printf("Failed to fetch rspamd symbols from %s, using embedded list", apiURL) - } - return parseRspamdSymbolsJSON(embeddedRspamdSymbols) -} - -// fetchRspamdSymbols fetches symbol descriptions from the rspamd API. -func fetchRspamdSymbols(apiURL string) map[string]string { - url := strings.TrimRight(apiURL, "/") + "/symbols" - - client := &http.Client{Timeout: 10 * time.Second} - resp, err := client.Get(url) - if err != nil { - log.Printf("Error fetching rspamd symbols: %v", err) - return nil - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - log.Printf("rspamd API returned status %d", resp.StatusCode) - return nil - } - - body, err := io.ReadAll(resp.Body) - if err != nil { - log.Printf("Error reading rspamd symbols response: %v", err) - return nil - } - - return parseRspamdSymbolsJSON(body) -} diff --git a/pkg/analyzer/rspamd_test.go b/pkg/analyzer/rspamd_test.go deleted file mode 100644 index 0eeca85..0000000 --- a/pkg/analyzer/rspamd_test.go +++ /dev/null @@ -1,414 +0,0 @@ -// This file is part of the happyDeliver (R) project. -// Copyright (c) 2026 happyDomain -// Authors: Pierre-Olivier Mercier, et al. -// -// This program is offered under a commercial and under the AGPL license. -// For commercial licensing, contact us at . -// -// For AGPL licensing: -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -package analyzer - -import ( - "bytes" - "net/mail" - "testing" - - "git.happydns.org/happyDeliver/internal/api" -) - -func TestAnalyzeRspamdNoHeaders(t *testing.T) { - analyzer := NewRspamdAnalyzer(nil) - email := &EmailMessage{Header: make(mail.Header)} - - result := analyzer.AnalyzeRspamd(email) - - if result != nil { - t.Errorf("Expected nil for email without rspamd headers, got %+v", result) - } -} - -func TestParseSpamdResult(t *testing.T) { - tests := []struct { - name string - header string - expectedScore float32 - expectedThreshold float32 - expectedIsSpam bool - expectedSymbols map[string]float32 - expectedSymParams map[string]string - }{ - { - name: "Clean email negative score", - header: "default: False [-3.91 / 15.00];\n\tDATE_IN_PAST(0.10); ALL_TRUSTED(-1.00)[trusted]", - expectedScore: -3.91, - expectedThreshold: 15.00, - expectedIsSpam: false, - expectedSymbols: map[string]float32{ - "DATE_IN_PAST": 0.10, - "ALL_TRUSTED": -1.00, - }, - expectedSymParams: map[string]string{ - "ALL_TRUSTED": "trusted", - }, - }, - { - name: "Spam email True flag", - header: "default: True [16.50 / 15.00];\n\tBAYES_99(5.00)[1.00]; SPOOFED_SENDER(3.50)", - expectedScore: 16.50, - expectedThreshold: 15.00, - expectedIsSpam: true, - expectedSymbols: map[string]float32{ - "BAYES_99": 5.00, - "SPOOFED_SENDER": 3.50, - }, - expectedSymParams: map[string]string{ - "BAYES_99": "1.00", - }, - }, - { - name: "Zero threshold uses default", - header: "default: False [1.00 / 0.00]", - expectedScore: 1.00, - expectedThreshold: rspamdDefaultAddHeaderThreshold, - expectedIsSpam: false, - expectedSymbols: map[string]float32{}, - }, - { - name: "Symbol without params", - header: "default: False [2.00 / 15.00];\n\tMISSING_DATE(1.00)", - expectedScore: 2.00, - expectedThreshold: 15.00, - expectedIsSpam: false, - expectedSymbols: map[string]float32{ - "MISSING_DATE": 1.00, - }, - }, - { - name: "Case-insensitive true flag", - header: "default: true [8.00 / 6.00]", - expectedScore: 8.00, - expectedThreshold: 6.00, - expectedIsSpam: true, - expectedSymbols: map[string]float32{}, - }, - { - name: "Zero threshold with symbols containing nested brackets in params", - header: "default: False [0.90 / 0.00];\n" + - "\tARC_REJECT(1.00)[cannot verify 1 of 1 signatures: {[1] = sig:mail-tester.local:signature has incorrect length: 12}];\n" + - "\tMIME_GOOD(-0.10)[multipart/alternative,text/plain];\n" + - "\tMIME_TRACE(0.00)[0:+,1:+,2:~]", - expectedScore: 0.90, - expectedThreshold: rspamdDefaultAddHeaderThreshold, - expectedIsSpam: false, - expectedSymbols: map[string]float32{ - "ARC_REJECT": 1.00, - "MIME_GOOD": -0.10, - "MIME_TRACE": 0.00, - }, - expectedSymParams: map[string]string{ - "ARC_REJECT": "cannot verify 1 of 1 signatures: {[1] = sig:mail-tester.local:signature has incorrect length: 12}", - "MIME_GOOD": "multipart/alternative,text/plain", - "MIME_TRACE": "0:+,1:+,2:~", - }, - }, - } - - analyzer := NewRspamdAnalyzer(nil) - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result := &api.RspamdResult{ - Symbols: make(map[string]api.SpamTestDetail), - } - analyzer.parseSpamdResult(tt.header, result) - - if result.Score != tt.expectedScore { - t.Errorf("Score = %v, want %v", result.Score, tt.expectedScore) - } - if result.Threshold != tt.expectedThreshold { - t.Errorf("Threshold = %v, want %v", result.Threshold, tt.expectedThreshold) - } - if result.IsSpam != tt.expectedIsSpam { - t.Errorf("IsSpam = %v, want %v", result.IsSpam, tt.expectedIsSpam) - } - for symName, expectedScore := range tt.expectedSymbols { - sym, ok := result.Symbols[symName] - if !ok { - t.Errorf("Symbol %s not found", symName) - continue - } - if sym.Score != expectedScore { - t.Errorf("Symbol %s score = %v, want %v", symName, sym.Score, expectedScore) - } - } - for symName, expectedParam := range tt.expectedSymParams { - sym, ok := result.Symbols[symName] - if !ok { - t.Errorf("Symbol %s not found for params check", symName) - continue - } - if sym.Params == nil { - t.Errorf("Symbol %s params = nil, want %q", symName, expectedParam) - } else if *sym.Params != expectedParam { - t.Errorf("Symbol %s params = %q, want %q", symName, *sym.Params, expectedParam) - } - } - }) - } -} - -func TestAnalyzeRspamd(t *testing.T) { - tests := []struct { - name string - headers map[string]string - expectedScore float32 - expectedThreshold float32 - expectedIsSpam bool - expectedServer *string - expectedSymCount int - }{ - { - name: "Full headers clean email", - headers: map[string]string{ - "X-Spamd-Result": "default: False [-3.91 / 15.00];\n\tALL_TRUSTED(-1.00)[local]", - "X-Rspamd-Score": "-3.91", - "X-Rspamd-Server": "mail.example.com", - }, - expectedScore: -3.91, - expectedThreshold: 15.00, - expectedIsSpam: false, - expectedServer: func() *string { s := "mail.example.com"; return &s }(), - expectedSymCount: 1, - }, - { - name: "X-Rspamd-Score overrides spamd result score", - headers: map[string]string{ - "X-Spamd-Result": "default: False [2.00 / 15.00]", - "X-Rspamd-Score": "3.50", - }, - expectedScore: 3.50, - expectedThreshold: 15.00, - expectedIsSpam: false, - }, - { - name: "Spam email above threshold", - headers: map[string]string{ - "X-Spamd-Result": "default: True [16.00 / 15.00];\n\tBAYES_99(5.00)", - "X-Rspamd-Score": "16.00", - }, - expectedScore: 16.00, - expectedThreshold: 15.00, - expectedIsSpam: true, - expectedSymCount: 1, - }, - { - name: "No X-Spamd-Result, only X-Rspamd-Score below default threshold", - headers: map[string]string{ - "X-Rspamd-Score": "2.00", - }, - expectedScore: 2.00, - expectedIsSpam: false, - }, - { - name: "No X-Spamd-Result, X-Rspamd-Score above default add-header threshold", - headers: map[string]string{ - "X-Rspamd-Score": "7.00", - }, - expectedScore: 7.00, - expectedIsSpam: true, - }, - { - name: "Server header is trimmed", - headers: map[string]string{ - "X-Rspamd-Score": "1.00", - "X-Rspamd-Server": " rspamd-01 ", - }, - expectedScore: 1.00, - expectedServer: func() *string { s := "rspamd-01"; return &s }(), - }, - } - - analyzer := NewRspamdAnalyzer(nil) - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - email := &EmailMessage{Header: make(mail.Header)} - for k, v := range tt.headers { - email.Header[k] = []string{v} - } - - result := analyzer.AnalyzeRspamd(email) - - if result == nil { - t.Fatal("Expected non-nil result") - } - if result.Score != tt.expectedScore { - t.Errorf("Score = %v, want %v", result.Score, tt.expectedScore) - } - if tt.expectedThreshold > 0 && result.Threshold != tt.expectedThreshold { - t.Errorf("Threshold = %v, want %v", result.Threshold, tt.expectedThreshold) - } - if result.IsSpam != tt.expectedIsSpam { - t.Errorf("IsSpam = %v, want %v", result.IsSpam, tt.expectedIsSpam) - } - if tt.expectedServer != nil { - if result.Server == nil { - t.Errorf("Server = nil, want %q", *tt.expectedServer) - } else if *result.Server != *tt.expectedServer { - t.Errorf("Server = %q, want %q", *result.Server, *tt.expectedServer) - } - } - if tt.expectedSymCount > 0 && len(result.Symbols) != tt.expectedSymCount { - t.Errorf("Symbol count = %d, want %d", len(result.Symbols), tt.expectedSymCount) - } - }) - } -} - -func TestCalculateRspamdScore(t *testing.T) { - tests := []struct { - name string - result *api.RspamdResult - expectedScore int - expectedGrade string - }{ - { - name: "Nil result (rspamd not installed)", - result: nil, - expectedScore: 100, - expectedGrade: "", - }, - { - name: "Score well below threshold", - result: &api.RspamdResult{ - Score: -3.91, - Threshold: 15.00, - }, - expectedScore: 100, - expectedGrade: "A+", - }, - { - name: "Score at zero", - result: &api.RspamdResult{ - Score: 0, - Threshold: 15.00, - }, - // 100 - round(0*100/30) = 100 → hits ScoreToGrade(100) = "A" - expectedScore: 100, - expectedGrade: "A", - }, - { - name: "Score at threshold (half of 2*threshold)", - result: &api.RspamdResult{ - Score: 15.00, - Threshold: 15.00, - }, - // 100 - round(15*100/(2*15)) = 100 - 50 = 50 - expectedScore: 50, - }, - { - name: "Score above 2*threshold", - result: &api.RspamdResult{ - Score: 31.00, - Threshold: 15.00, - }, - expectedScore: 0, - expectedGrade: "F", - }, - { - name: "Score exactly at 2*threshold", - result: &api.RspamdResult{ - Score: 30.00, - Threshold: 15.00, - }, - // 100 - round(30*100/30) = 100 - 100 = 0 - expectedScore: 0, - expectedGrade: "F", - }, - } - - analyzer := NewRspamdAnalyzer(nil) - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - score, grade := analyzer.CalculateRspamdScore(tt.result) - - if score != tt.expectedScore { - t.Errorf("Score = %d, want %d", score, tt.expectedScore) - } - if tt.expectedGrade != "" && grade != tt.expectedGrade { - t.Errorf("Grade = %q, want %q", grade, tt.expectedGrade) - } - }) - } -} - -const sampleEmailWithRspamdHeaders = `X-Spamd-Result: default: False [-3.91 / 15.00]; - BAYES_HAM(-3.00)[99%]; - RCVD_IN_DNSWL_MED(-0.01)[1.2.3.4:from]; - R_DKIM_ALLOW(-0.20)[example.com:s=dkim]; - FROM_HAS_DN(0.00)[]; - MIME_GOOD(-0.10)[text/plain]; -X-Rspamd-Score: -3.91 -X-Rspamd-Server: rspamd-01.example.com -Date: Mon, 09 Mar 2026 10:00:00 +0000 -From: sender@example.com -To: test@happydomain.org -Subject: Test email -Message-ID: -MIME-Version: 1.0 -Content-Type: text/plain - -Hello world` - -func TestAnalyzeRspamdRealEmail(t *testing.T) { - email, err := ParseEmail(bytes.NewBufferString(sampleEmailWithRspamdHeaders)) - if err != nil { - t.Fatalf("Failed to parse email: %v", err) - } - - analyzer := NewRspamdAnalyzer(nil) - result := analyzer.AnalyzeRspamd(email) - - if result == nil { - t.Fatal("Expected non-nil result") - } - if result.IsSpam { - t.Error("Expected IsSpam=false") - } - if result.Score != -3.91 { - t.Errorf("Score = %v, want -3.91", result.Score) - } - if result.Threshold != 15.00 { - t.Errorf("Threshold = %v, want 15.00", result.Threshold) - } - if result.Server == nil || *result.Server != "rspamd-01.example.com" { - t.Errorf("Server = %v, want \"rspamd-01.example.com\"", result.Server) - } - - expectedSymbols := []string{"BAYES_HAM", "RCVD_IN_DNSWL_MED", "R_DKIM_ALLOW", "FROM_HAS_DN", "MIME_GOOD"} - for _, sym := range expectedSymbols { - if _, ok := result.Symbols[sym]; !ok { - t.Errorf("Symbol %s not found", sym) - } - } - - score, _ := analyzer.CalculateRspamdScore(result) - if score != 100 { - t.Errorf("CalculateRspamdScore = %d, want 100", score) - } -} - diff --git a/pkg/analyzer/scoring.go b/pkg/analyzer/scoring.go index 5568c8e..0a23388 100644 --- a/pkg/analyzer/scoring.go +++ b/pkg/analyzer/scoring.go @@ -69,33 +69,3 @@ func ScoreToGradeKind(score int) string { func ScoreToReportGrade(score int) api.ReportGrade { return api.ReportGrade(ScoreToGrade(score)) } - -// gradeRank returns a numeric rank for a grade (lower = worse) -func gradeRank(grade string) int { - switch grade { - case "A++": - return 7 - case "A+": - return 6 - case "A": - return 5 - case "B": - return 4 - case "C": - return 3 - case "D": - return 2 - case "E": - return 1 - default: - return 0 - } -} - -// MinGrade returns the minimal (worse) grade between the two given grades -func MinGrade(a, b string) string { - if gradeRank(a) <= gradeRank(b) { - return a - } - return b -} diff --git a/pkg/analyzer/spamassassin.go b/pkg/analyzer/spamassassin.go index d6ae961..cb80fe6 100644 --- a/pkg/analyzer/spamassassin.go +++ b/pkg/analyzer/spamassassin.go @@ -45,20 +45,12 @@ func (a *SpamAssassinAnalyzer) AnalyzeSpamAssassin(email *EmailMessage) *api.Spa return nil } - // Require at least X-Spam-Status, X-Spam-Score, or X-Spam-Flag to produce a meaningful report - _, hasStatus := headers["X-Spam-Status"] - _, hasScore := headers["X-Spam-Score"] - _, hasFlag := headers["X-Spam-Flag"] - if !hasStatus && !hasScore && !hasFlag { - return nil - } - result := &api.SpamAssassinResult{ TestDetails: make(map[string]api.SpamTestDetail), } // Parse X-Spam-Status header - if statusHeader, ok := headers["X-Spam-Status"]; ok && statusHeader != "" { + if statusHeader, ok := headers["X-Spam-Status"]; ok { a.parseSpamStatus(statusHeader, result) } diff --git a/web/package-lock.json b/web/package-lock.json index 6951a71..6f88380 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -14,13 +14,13 @@ }, "devDependencies": { "@eslint/compat": "^2.0.0", - "@eslint/js": "^10.0.0", + "@eslint/js": "^9.36.0", "@hey-api/openapi-ts": "0.86.10", "@sveltejs/adapter-static": "^3.0.9", "@sveltejs/kit": "^2.43.2", - "@sveltejs/vite-plugin-svelte": "^7.0.0", + "@sveltejs/vite-plugin-svelte": "^6.2.0", "@types/node": "^24.0.0", - "eslint": "^10.0.0", + "eslint": "^9.38.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-svelte": "^3.12.4", "globals": "^17.0.0", @@ -30,48 +30,14 @@ "svelte-check": "^4.3.2", "typescript": "^5.9.2", "typescript-eslint": "^8.44.1", - "vite": "^8.0.0", + "vite": "^7.1.10", "vitest": "^3.2.4" } }, - "node_modules/@emnapi/core": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.1.tgz", - "integrity": "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.0", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz", - "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", - "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", - "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", "cpu": [ "ppc64" ], @@ -86,9 +52,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz", - "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", "cpu": [ "arm" ], @@ -103,9 +69,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz", - "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", "cpu": [ "arm64" ], @@ -120,9 +86,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz", - "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", "cpu": [ "x64" ], @@ -137,9 +103,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz", - "integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", "cpu": [ "arm64" ], @@ -154,9 +120,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz", - "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", "cpu": [ "x64" ], @@ -171,9 +137,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz", - "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", "cpu": [ "arm64" ], @@ -188,9 +154,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz", - "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", "cpu": [ "x64" ], @@ -205,9 +171,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz", - "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", "cpu": [ "arm" ], @@ -222,9 +188,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz", - "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", "cpu": [ "arm64" ], @@ -239,9 +205,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz", - "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", "cpu": [ "ia32" ], @@ -256,9 +222,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz", - "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", "cpu": [ "loong64" ], @@ -273,9 +239,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz", - "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", "cpu": [ "mips64el" ], @@ -290,9 +256,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz", - "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", "cpu": [ "ppc64" ], @@ -307,9 +273,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz", - "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", "cpu": [ "riscv64" ], @@ -324,9 +290,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz", - "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", "cpu": [ "s390x" ], @@ -341,9 +307,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz", - "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", "cpu": [ "x64" ], @@ -358,9 +324,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz", - "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", "cpu": [ "arm64" ], @@ -375,9 +341,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz", - "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", "cpu": [ "x64" ], @@ -392,9 +358,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz", - "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", "cpu": [ "arm64" ], @@ -409,9 +375,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz", - "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", "cpu": [ "x64" ], @@ -426,9 +392,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz", - "integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", "cpu": [ "arm64" ], @@ -443,9 +409,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz", - "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", "cpu": [ "x64" ], @@ -460,9 +426,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz", - "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", "cpu": [ "arm64" ], @@ -477,9 +443,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz", - "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", "cpu": [ "ia32" ], @@ -494,9 +460,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz", - "integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", "cpu": [ "x64" ], @@ -553,19 +519,19 @@ } }, "node_modules/@eslint/compat": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-2.0.3.tgz", - "integrity": "sha512-SjIJhGigp8hmd1YGIBwh7Ovri7Kisl42GYFjrOyHhtfYGGoLW6teYi/5p8W50KSsawUPpuLOSmsq1bD0NGQLBw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-2.0.0.tgz", + "integrity": "sha512-T9AfE1G1uv4wwq94ozgTGio5EUQBqAVe1X9qsQtSNVEYW6j3hvtZVm8Smr4qL1qDPFg+lOB2cL5RxTRMzq4CTA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^1.1.1" + "@eslint/core": "^1.0.0" }, "engines": { "node": "^20.19.0 || ^22.13.0 || >=24" }, "peerDependencies": { - "eslint": "^8.40 || 9 || 10" + "eslint": "^8.40 || 9" }, "peerDependenciesMeta": { "eslint": { @@ -574,37 +540,50 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.23.3", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.3.tgz", - "integrity": "sha512-j+eEWmB6YYLwcNOdlwQ6L2OsptI/LO6lNBuLIqe5R7RetD658HLoF+Mn7LzYmAWWNNzdC6cqP+L6r8ujeYXWLw==", + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^3.0.3", + "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", - "minimatch": "^10.2.4" + "minimatch": "^3.1.2" }, "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/config-helpers": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.3.tgz", - "integrity": "sha512-lzGN0onllOZCGroKJmRwY6QcEHxbjBw1gwB8SgRSqK8YbbtEXMvKynsXc3553ckIEBxsbMBU7oOZXKIPGZNeZw==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^1.1.1" + "@eslint/core": "^0.17.0" }, "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers/node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/core": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.1.tgz", - "integrity": "sha512-QUPblTtE51/7/Zhfv8BDwO0qkkzQL7P/aWWbqcf4xWLEYn1oKjdO0gglQBB4GAsu7u6wjijbCmzsUTy6mnk6oQ==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.0.0.tgz", + "integrity": "sha512-PRfWP+8FOldvbApr6xL7mNCw4cJcSTq4GA7tYbgq15mRb0kWKO/wEB2jr+uwjFH3sZvEZneZyCUGTxsv4Sahyw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -614,49 +593,91 @@ "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@eslint/js": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", - "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "node_modules/@eslint/eslintrc": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, "license": "MIT", "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "eslint": "^10.0.0" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } } }, "node_modules/@eslint/object-schema": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.3.tgz", - "integrity": "sha512-iM869Pugn9Nsxbh/YHRqYiqd23AmIbxJOcpUMOuWCVNdoQJ5ZtwL6h3t0bcZzJUlC3Dq9jCFCESBZnX0GTv7iQ==", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", "dev": true, "license": "Apache-2.0", "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/plugin-kit": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.6.1.tgz", - "integrity": "sha512-iH1B076HoAshH1mLpHMgwdGeTs0CYwL0SPMkGuSebZrwBp16v415e9NZXg2jtrqPVQjf6IANe2Vtlr5KswtcZQ==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^1.1.1", + "@eslint/core": "^0.17.0", "levn": "^0.4.1" }, "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@hey-api/codegen-core": { @@ -833,33 +854,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", - "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1", - "@tybys/wasm-util": "^0.10.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - } - }, - "node_modules/@oxc-project/types": { - "version": "0.120.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.120.0.tgz", - "integrity": "sha512-k1YNu55DuvAip/MGE1FTsIuU3FUCn6v/ujG9V7Nq5Df/kX2CWb13hhwD0lmJGMGqE+bE1MXvv9SZVnMzEXlWcg==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" - } - }, "node_modules/@polka/url": { "version": "1.0.0-next.29", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", @@ -878,272 +872,10 @@ "url": "https://opencollective.com/popperjs" } }, - "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-rc.10", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.10.tgz", - "integrity": "sha512-jOHxwXhxmFKuXztiu1ORieJeTbx5vrTkcOkkkn2d35726+iwhrY1w/+nYY/AGgF12thg33qC3R1LMBF5tHTZHg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-rc.10", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.10.tgz", - "integrity": "sha512-gED05Teg/vtTZbIJBc4VNMAxAFDUPkuO/rAIyyxZjTj1a1/s6z5TII/5yMGZ0uLRCifEtwUQn8OlYzuYc0m70w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-rc.10", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.10.tgz", - "integrity": "sha512-rI15NcM1mA48lqrIxVkHfAqcyFLcQwyXWThy+BQ5+mkKKPvSO26ir+ZDp36AgYoYVkqvMcdS8zOE6SeBsR9e8A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-rc.10", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.10.tgz", - "integrity": "sha512-XZRXHdTa+4ME1MuDVp021+doQ+z6Ei4CCFmNc5/sKbqb8YmkiJdj8QKlV3rCI0AJtAeSB5n0WGPuJWNL9p/L2w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-rc.10", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.10.tgz", - "integrity": "sha512-R0SQMRluISSLzFE20sPWYHVmJdDQnRyc/FzSCN72BqQmh2SOZUFG+N3/vBZpR4C6WpEUVYJLrYUXaj43sJsNLA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-rc.10", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.10.tgz", - "integrity": "sha512-Y1reMrV/o+cwpduYhJuOE3OMKx32RMYCidf14y+HssARRmhDuWXJ4yVguDg2R/8SyyGNo+auzz64LnPK9Hq6jg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-rc.10", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.10.tgz", - "integrity": "sha512-vELN+HNb2IzuzSBUOD4NHmP9yrGwl1DVM29wlQvx1OLSclL0NgVWnVDKl/8tEks79EFek/kebQKnNJkIAA4W2g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.0-rc.10", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.10.tgz", - "integrity": "sha512-ZqrufYTgzxbHwpqOjzSsb0UV/aV2TFIY5rP8HdsiPTv/CuAgCRjM6s9cYFwQ4CNH+hf9Y4erHW1GjZuZ7WoI7w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.0-rc.10", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.10.tgz", - "integrity": "sha512-gSlmVS1FZJSRicA6IyjoRoKAFK7IIHBs7xJuHRSmjImqk3mPPWbR7RhbnfH2G6bcmMEllCt2vQ/7u9e6bBnByg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-rc.10", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.10.tgz", - "integrity": "sha512-eOCKUpluKgfObT2pHjztnaWEIbUabWzk3qPZ5PuacuPmr4+JtQG4k2vGTY0H15edaTnicgU428XW/IH6AimcQw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-rc.10", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.10.tgz", - "integrity": "sha512-Xdf2jQbfQowJnLcgYfD/m0Uu0Qj5OdxKallD78/IPPfzaiaI4KRAwZzHcKQ4ig1gtg1SuzC7jovNiM2TzQsBXA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-rc.10", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.10.tgz", - "integrity": "sha512-o1hYe8hLi1EY6jgPFyxQgQ1wcycX+qz8eEbVmot2hFkgUzPxy9+kF0u0NIQBeDq+Mko47AkaFFaChcvZa9UX9Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-rc.10", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.10.tgz", - "integrity": "sha512-Ugv9o7qYJudqQO5Y5y2N2SOo6S4WiqiNOpuQyoPInnhVzCY+wi/GHltcLHypG9DEUYMB0iTB/huJrpadiAcNcA==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@napi-rs/wasm-runtime": "^1.1.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-rc.10", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.10.tgz", - "integrity": "sha512-7UODQb4fQUNT/vmgDZBl3XOBAIOutP5R3O/rkxg0aLfEGQ4opbCgU5vOw/scPe4xOqBwL9fw7/RP1vAMZ6QlAQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-rc.10", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.10.tgz", - "integrity": "sha512-PYxKHMVHOb5NJuDL53vBUl1VwUjymDcYI6rzpIni0C9+9mTiJedvUxSk7/RPp7OOAm3v+EjgMu9bIy3N6b408w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.10", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.10.tgz", - "integrity": "sha512-UkVDEFk1w3mveXeKgaTuYfKWtPbvgck1dT8TUG3bnccrH0XtLTuAyfCoks4Q/M5ZGToSVJTIQYCzy2g/atAOeg==", - "dev": true, - "license": "MIT" - }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.0.tgz", - "integrity": "sha512-WOhNW9K8bR3kf4zLxbfg6Pxu2ybOUbB2AjMDHSQx86LIF4rH4Ft7vmMwNt0loO0eonglSNy4cpD3MKXXKQu0/A==", + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.54.0.tgz", + "integrity": "sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng==", "cpu": [ "arm" ], @@ -1155,9 +887,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.0.tgz", - "integrity": "sha512-u6JHLll5QKRvjciE78bQXDmqRqNs5M/3GVqZeMwvmjaNODJih/WIrJlFVEihvV0MiYFmd+ZyPr9wxOVbPAG2Iw==", + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.54.0.tgz", + "integrity": "sha512-Skx39Uv+u7H224Af+bDgNinitlmHyQX1K/atIA32JP3JQw6hVODX5tkbi2zof/E69M1qH2UoN3Xdxgs90mmNYw==", "cpu": [ "arm64" ], @@ -1169,9 +901,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.0.tgz", - "integrity": "sha512-qEF7CsKKzSRc20Ciu2Zw1wRrBz4g56F7r/vRwY430UPp/nt1x21Q/fpJ9N5l47WWvJlkNCPJz3QRVw008fi7yA==", + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.54.0.tgz", + "integrity": "sha512-k43D4qta/+6Fq+nCDhhv9yP2HdeKeP56QrUUTW7E6PhZP1US6NDqpJj4MY0jBHlJivVJD5P8NxrjuobZBJTCRw==", "cpu": [ "arm64" ], @@ -1183,9 +915,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.0.tgz", - "integrity": "sha512-WADYozJ4QCnXCH4wPB+3FuGmDPoFseVCUrANmA5LWwGmC6FL14BWC7pcq+FstOZv3baGX65tZ378uT6WG8ynTw==", + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.54.0.tgz", + "integrity": "sha512-cOo7biqwkpawslEfox5Vs8/qj83M/aZCSSNIWpVzfU2CYHa2G3P1UN5WF01RdTHSgCkri7XOlTdtk17BezlV3A==", "cpu": [ "x64" ], @@ -1197,9 +929,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.0.tgz", - "integrity": "sha512-6b8wGHJlDrGeSE3aH5mGNHBjA0TTkxdoNHik5EkvPHCt351XnigA4pS7Wsj/Eo9Y8RBU6f35cjN9SYmCFBtzxw==", + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.54.0.tgz", + "integrity": "sha512-miSvuFkmvFbgJ1BevMa4CPCFt5MPGw094knM64W9I0giUIMMmRYcGW/JWZDriaw/k1kOBtsWh1z6nIFV1vPNtA==", "cpu": [ "arm64" ], @@ -1211,9 +943,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.0.tgz", - "integrity": "sha512-h25Ga0t4jaylMB8M/JKAyrvvfxGRjnPQIR8lnCayyzEjEOx2EJIlIiMbhpWxDRKGKF8jbNH01NnN663dH638mA==", + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.54.0.tgz", + "integrity": "sha512-KGXIs55+b/ZfZsq9aR026tmr/+7tq6VG6MsnrvF4H8VhwflTIuYh+LFUlIsRdQSgrgmtM3fVATzEAj4hBQlaqQ==", "cpu": [ "x64" ], @@ -1225,9 +957,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.0.tgz", - "integrity": "sha512-RzeBwv0B3qtVBWtcuABtSuCzToo2IEAIQrcyB/b2zMvBWVbjo8bZDjACUpnaafaxhTw2W+imQbP2BD1usasK4g==", + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.54.0.tgz", + "integrity": "sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ==", "cpu": [ "arm" ], @@ -1239,9 +971,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.0.tgz", - "integrity": "sha512-Sf7zusNI2CIU1HLzuu9Tc5YGAHEZs5Lu7N1ssJG4Tkw6e0MEsN7NdjUDDfGNHy2IU+ENyWT+L2obgWiguWibWQ==", + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.54.0.tgz", + "integrity": "sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA==", "cpu": [ "arm" ], @@ -1253,9 +985,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.0.tgz", - "integrity": "sha512-DX2x7CMcrJzsE91q7/O02IJQ5/aLkVtYFryqCjduJhUfGKG6yJV8hxaw8pZa93lLEpPTP/ohdN4wFz7yp/ry9A==", + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.54.0.tgz", + "integrity": "sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng==", "cpu": [ "arm64" ], @@ -1267,9 +999,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.0.tgz", - "integrity": "sha512-09EL+yFVbJZlhcQfShpswwRZ0Rg+z/CsSELFCnPt3iK+iqwGsI4zht3secj5vLEs957QvFFXnzAT0FFPIxSrkQ==", + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.54.0.tgz", + "integrity": "sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg==", "cpu": [ "arm64" ], @@ -1281,23 +1013,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.0.tgz", - "integrity": "sha512-i9IcCMPr3EXm8EQg5jnja0Zyc1iFxJjZWlb4wr7U2Wx/GrddOuEafxRdMPRYVaXjgbhvqalp6np07hN1w9kAKw==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.0.tgz", - "integrity": "sha512-DGzdJK9kyJ+B78MCkWeGnpXJ91tK/iKA6HwHxF4TAlPIY7GXEvMe8hBFRgdrR9Ly4qebR/7gfUs9y2IoaVEyog==", + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.54.0.tgz", + "integrity": "sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw==", "cpu": [ "loong64" ], @@ -1309,23 +1027,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.0.tgz", - "integrity": "sha512-RwpnLsqC8qbS8z1H1AxBA1H6qknR4YpPR9w2XX0vo2Sz10miu57PkNcnHVaZkbqyw/kUWfKMI73jhmfi9BRMUQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.0.tgz", - "integrity": "sha512-Z8pPf54Ly3aqtdWC3G4rFigZgNvd+qJlOE52fmko3KST9SoGfAdSRCwyoyG05q1HrrAblLbk1/PSIV+80/pxLg==", + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.54.0.tgz", + "integrity": "sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA==", "cpu": [ "ppc64" ], @@ -1337,9 +1041,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.0.tgz", - "integrity": "sha512-3a3qQustp3COCGvnP4SvrMHnPQ9d1vzCakQVRTliaz8cIp/wULGjiGpbcqrkv0WrHTEp8bQD/B3HBjzujVWLOA==", + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.54.0.tgz", + "integrity": "sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ==", "cpu": [ "riscv64" ], @@ -1351,9 +1055,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.0.tgz", - "integrity": "sha512-pjZDsVH/1VsghMJ2/kAaxt6dL0psT6ZexQVrijczOf+PeP2BUqTHYejk3l6TlPRydggINOeNRhvpLa0AYpCWSQ==", + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.54.0.tgz", + "integrity": "sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A==", "cpu": [ "riscv64" ], @@ -1365,9 +1069,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.0.tgz", - "integrity": "sha512-3ObQs0BhvPgiUVZrN7gqCSvmFuMWvWvsjG5ayJ3Lraqv+2KhOsp+pUbigqbeWqueGIsnn+09HBw27rJ+gYK4VQ==", + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.54.0.tgz", + "integrity": "sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ==", "cpu": [ "s390x" ], @@ -1379,9 +1083,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.0.tgz", - "integrity": "sha512-EtylprDtQPdS5rXvAayrNDYoJhIz1/vzN2fEubo3yLE7tfAw+948dO0g4M0vkTVFhKojnF+n6C8bDNe+gDRdTg==", + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.54.0.tgz", + "integrity": "sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ==", "cpu": [ "x64" ], @@ -1393,9 +1097,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.0.tgz", - "integrity": "sha512-k09oiRCi/bHU9UVFqD17r3eJR9bn03TyKraCrlz5ULFJGdJGi7VOmm9jl44vOJvRJ6P7WuBi/s2A97LxxHGIdw==", + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.54.0.tgz", + "integrity": "sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw==", "cpu": [ "x64" ], @@ -1406,24 +1110,10 @@ "linux" ] }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.0.tgz", - "integrity": "sha512-1o/0/pIhozoSaDJoDcec+IVLbnRtQmHwPV730+AOD29lHEEo4F5BEUB24H0OBdhbBBDwIOSuf7vgg0Ywxdfiiw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] - }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.0.tgz", - "integrity": "sha512-pESDkos/PDzYwtyzB5p/UoNU/8fJo68vcXM9ZW2V0kjYayj1KaaUfi1NmTUTUpMn4UhU4gTuK8gIaFO4UGuMbA==", + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.54.0.tgz", + "integrity": "sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg==", "cpu": [ "arm64" ], @@ -1435,9 +1125,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.0.tgz", - "integrity": "sha512-hj1wFStD7B1YBeYmvY+lWXZ7ey73YGPcViMShYikqKT1GtstIKQAtfUI6yrzPjAy/O7pO0VLXGmUVWXQMaYgTQ==", + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.54.0.tgz", + "integrity": "sha512-c2V0W1bsKIKfbLMBu/WGBz6Yci8nJ/ZJdheE0EwB73N3MvHYKiKGs3mVilX4Gs70eGeDaMqEob25Tw2Gb9Nqyw==", "cpu": [ "arm64" ], @@ -1449,9 +1139,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.0.tgz", - "integrity": "sha512-SyaIPFoxmUPlNDq5EHkTbiKzmSEmq/gOYFI/3HHJ8iS/v1mbugVa7dXUzcJGQfoytp9DJFLhHH4U3/eTy2Bq4w==", + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.54.0.tgz", + "integrity": "sha512-woEHgqQqDCkAzrDhvDipnSirm5vxUXtSKDYTVpZG3nUdW/VVB5VdCYA2iReSj/u3yCZzXID4kuKG7OynPnB3WQ==", "cpu": [ "ia32" ], @@ -1463,9 +1153,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.0.tgz", - "integrity": "sha512-RdcryEfzZr+lAr5kRm2ucN9aVlCCa2QNq4hXelZxb8GG0NJSazq44Z3PCCc8wISRuCVnGs0lQJVX5Vp6fKA+IA==", + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.54.0.tgz", + "integrity": "sha512-dzAc53LOuFvHwbCEOS0rPbXp6SIhAf2txMP5p6mGyOXXw5mWY8NGGbPMPrs4P1WItkfApDathBj/NzMLUZ9rtQ==", "cpu": [ "x64" ], @@ -1477,9 +1167,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.0.tgz", - "integrity": "sha512-PrsWNQ8BuE00O3Xsx3ALh2Df8fAj9+cvvX9AIA6o4KpATR98c9mud4XtDWVvsEuyia5U4tVSTKygawyJkjm60w==", + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.54.0.tgz", + "integrity": "sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg==", "cpu": [ "x64" ], @@ -1498,9 +1188,9 @@ "license": "MIT" }, "node_modules/@sveltejs/acorn-typescript": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.9.tgz", - "integrity": "sha512-lVJX6qEgs/4DOcRTpo56tmKzVPtoWAaVbL4hfO7t7NVwl9AAXzQR6cihesW1BmNMPl+bK6dreu2sOKBP2Q9CIA==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.8.tgz", + "integrity": "sha512-esgN+54+q0NjB0Y/4BomT9samII7jGwNy/2a3wNZbT2A2RpmXsXwUt24LvLhx6jUq2gVk4cWEvcRO6MFQbOfNA==", "dev": true, "license": "MIT", "peerDependencies": { @@ -1518,23 +1208,25 @@ } }, "node_modules/@sveltejs/kit": { - "version": "2.55.0", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.55.0.tgz", - "integrity": "sha512-MdFRjevVxmAknf2NbaUkDF16jSIzXMWd4Nfah0Qp8TtQVoSp3bV4jKt8mX7z7qTUTWvgSaxtR0EG5WJf53gcuA==", + "version": "2.49.2", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.49.2.tgz", + "integrity": "sha512-Vp3zX/qlwerQmHMP6x0Ry1oY7eKKRcOWGc2P59srOp4zcqyn+etJyQpELgOi4+ZSUgteX8Y387NuwruLgGXLUQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@standard-schema/spec": "^1.0.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/cookie": "^0.6.0", "acorn": "^8.14.1", "cookie": "^0.6.0", - "devalue": "^5.6.4", + "devalue": "^5.3.2", "esm-env": "^1.2.2", "kleur": "^4.1.5", "magic-string": "^0.30.5", "mrmime": "^2.0.0", - "set-cookie-parser": "^3.0.0", + "sade": "^1.8.1", + "set-cookie-parser": "^2.6.0", "sirv": "^3.0.0" }, "bin": { @@ -1545,49 +1237,54 @@ }, "peerDependencies": { "@opentelemetry/api": "^1.0.0", - "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 || ^7.0.0", + "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0", "svelte": "^4.0.0 || ^5.0.0-next.0", - "typescript": "^5.3.3", - "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 || ^8.0.0" + "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0" }, "peerDependenciesMeta": { "@opentelemetry/api": { "optional": true - }, - "typescript": { - "optional": true } } }, "node_modules/@sveltejs/vite-plugin-svelte": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-7.0.0.tgz", - "integrity": "sha512-ILXmxC7HAsnkK2eslgPetrqqW1BKSL7LktsFgqzNj83MaivMGZzluWq32m25j2mDOjmSKX7GGWahePhuEs7P/g==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.2.1.tgz", + "integrity": "sha512-YZs/OSKOQAQCnJvM/P+F1URotNnYNeU3P2s4oIpzm1uFaqUEqRxUB0g5ejMjEb5Gjb9/PiBI5Ktrq4rUUF8UVQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { + "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", + "debug": "^4.4.1", "deepmerge": "^4.3.1", - "magic-string": "^0.30.21", - "obug": "^2.1.0", - "vitefu": "^1.1.2" + "magic-string": "^0.30.17", + "vitefu": "^1.1.1" }, "engines": { "node": "^20.19 || ^22.12 || >=24" }, "peerDependencies": { - "svelte": "^5.46.4", - "vite": "^8.0.0-beta.7 || ^8.0.0" + "svelte": "^5.0.0", + "vite": "^6.3.0 || ^7.0.0" } }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "node_modules/@sveltejs/vite-plugin-svelte-inspector": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-5.0.1.tgz", + "integrity": "sha512-ubWshlMk4bc8mkwWbg6vNvCeT7lGQojE3ijDh3QTR6Zr/R+GXxsGbyH4PExEPpiFmqPhYiVSVmHBjUcVc1JIrA==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "tslib": "^2.4.0" + "debug": "^4.4.1" + }, + "engines": { + "node": "^20.19 || ^22.12 || >=24" + }, + "peerDependencies": { + "@sveltejs/vite-plugin-svelte": "^6.0.0-next.0", + "svelte": "^5.0.0", + "vite": "^6.3.0 || ^7.0.0" } }, "node_modules/@types/chai": { @@ -1615,13 +1312,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/esrecurse": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", - "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -1637,37 +1327,31 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.12.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.0.tgz", - "integrity": "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==", + "version": "24.10.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.4.tgz", + "integrity": "sha512-vnDVpYPMzs4wunl27jHrfmwojOGKya0xyM3sH+UE5iv5uPS6vX7UIoh6m+vQc5LGBq52HBKPIn/zcSZVzeDEZg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.16.0" } }, - "node_modules/@types/trusted-types": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", - "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", - "dev": true, - "license": "MIT" - }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.1.tgz", - "integrity": "sha512-Gn3aqnvNl4NGc6x3/Bqk1AOn0thyTU9bqDRhiRnUWezgvr2OnhYCWCgC8zXXRVqBsIL1pSDt7T9nJUe0oM0kDQ==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.51.0.tgz", + "integrity": "sha512-XtssGWJvypyM2ytBnSnKtHYOGT+4ZwTnBVl36TA4nRO2f4PRNGz5/1OszHzcZCvcBMh+qb7I06uoCmLTRdR9og==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.57.1", - "@typescript-eslint/type-utils": "8.57.1", - "@typescript-eslint/utils": "8.57.1", - "@typescript-eslint/visitor-keys": "8.57.1", - "ignore": "^7.0.5", + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.51.0", + "@typescript-eslint/type-utils": "8.51.0", + "@typescript-eslint/utils": "8.51.0", + "@typescript-eslint/visitor-keys": "8.51.0", + "ignore": "^7.0.0", "natural-compare": "^1.4.0", - "ts-api-utils": "^2.4.0" + "ts-api-utils": "^2.2.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1677,8 +1361,8 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.57.1", - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "@typescript-eslint/parser": "^8.51.0", + "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, @@ -1693,17 +1377,18 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.57.1.tgz", - "integrity": "sha512-k4eNDan0EIMTT/dUKc/g+rsJ6wcHYhNPdY19VoX/EOtaAG8DLtKCykhrUnuHPYvinn5jhAPgD2Qw9hXBwrahsw==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.51.0.tgz", + "integrity": "sha512-3xP4XzzDNQOIqBMWogftkwxhg5oMKApqY0BAflmLZiFYHqyhSOxv/cd/zPQLTcCXr4AkaKb25joocY0BD1WC6A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.57.1", - "@typescript-eslint/types": "8.57.1", - "@typescript-eslint/typescript-estree": "8.57.1", - "@typescript-eslint/visitor-keys": "8.57.1", - "debug": "^4.4.3" + "@typescript-eslint/scope-manager": "8.51.0", + "@typescript-eslint/types": "8.51.0", + "@typescript-eslint/typescript-estree": "8.51.0", + "@typescript-eslint/visitor-keys": "8.51.0", + "debug": "^4.3.4" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1713,20 +1398,20 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.57.1.tgz", - "integrity": "sha512-vx1F37BRO1OftsYlmG9xay1TqnjNVlqALymwWVuYTdo18XuKxtBpCj1QlzNIEHlvlB27osvXFWptYiEWsVdYsg==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.51.0.tgz", + "integrity": "sha512-Luv/GafO07Z7HpiI7qeEW5NW8HUtZI/fo/kE0YbtQEFpJRUuR0ajcWfCE5bnMvL7QQFrmT/odMe8QZww8X2nfQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.57.1", - "@typescript-eslint/types": "^8.57.1", - "debug": "^4.4.3" + "@typescript-eslint/tsconfig-utils": "^8.51.0", + "@typescript-eslint/types": "^8.51.0", + "debug": "^4.3.4" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1740,14 +1425,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.57.1.tgz", - "integrity": "sha512-hs/QcpCwlwT2L5S+3fT6gp0PabyGk4Q0Rv2doJXA0435/OpnSR3VRgvrp8Xdoc3UAYSg9cyUjTeFXZEPg/3OKg==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.51.0.tgz", + "integrity": "sha512-JhhJDVwsSx4hiOEQPeajGhCWgBMBwVkxC/Pet53EpBVs7zHHtayKefw1jtPaNRXpI9RA2uocdmpdfE7T+NrizA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.57.1", - "@typescript-eslint/visitor-keys": "8.57.1" + "@typescript-eslint/types": "8.51.0", + "@typescript-eslint/visitor-keys": "8.51.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1758,9 +1443,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.1.tgz", - "integrity": "sha512-0lgOZB8cl19fHO4eI46YUx2EceQqhgkPSuCGLlGi79L2jwYY1cxeYc1Nae8Aw1xjgW3PKVDLlr3YJ6Bxx8HkWg==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.51.0.tgz", + "integrity": "sha512-Qi5bSy/vuHeWyir2C8u/uqGMIlIDu8fuiYWv48ZGlZ/k+PRPHtaAu7erpc7p5bzw2WNNSniuxoMSO4Ar6V9OXw==", "dev": true, "license": "MIT", "engines": { @@ -1775,17 +1460,17 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.57.1.tgz", - "integrity": "sha512-+Bwwm0ScukFdyoJsh2u6pp4S9ktegF98pYUU0hkphOOqdMB+1sNQhIz8y5E9+4pOioZijrkfNO/HUJVAFFfPKA==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.51.0.tgz", + "integrity": "sha512-0XVtYzxnobc9K0VU7wRWg1yiUrw4oQzexCG2V2IDxxCxhqBMSMbjB+6o91A+Uc0GWtgjCa3Y8bi7hwI0Tu4n5Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.57.1", - "@typescript-eslint/typescript-estree": "8.57.1", - "@typescript-eslint/utils": "8.57.1", - "debug": "^4.4.3", - "ts-api-utils": "^2.4.0" + "@typescript-eslint/types": "8.51.0", + "@typescript-eslint/typescript-estree": "8.51.0", + "@typescript-eslint/utils": "8.51.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.2.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1795,14 +1480,14 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.1.tgz", - "integrity": "sha512-S29BOBPJSFUiblEl6RzPPjJt6w25A6XsBqRVDt53tA/tlL8q7ceQNZHTjPeONt/3S7KRI4quk+yP9jK2WjBiPQ==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.51.0.tgz", + "integrity": "sha512-TizAvWYFM6sSscmEakjY3sPqGwxZRSywSsPEiuZF6d5GmGD9Gvlsv0f6N8FvAAA0CD06l3rIcWNbsN1e5F/9Ag==", "dev": true, "license": "MIT", "engines": { @@ -1814,21 +1499,21 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.1.tgz", - "integrity": "sha512-ybe2hS9G6pXpqGtPli9Gx9quNV0TWLOmh58ADlmZe9DguLq0tiAKVjirSbtM1szG6+QH6rVXyU6GTLQbWnMY+g==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.51.0.tgz", + "integrity": "sha512-1qNjGqFRmlq0VW5iVlcyHBbCjPB7y6SxpBkrbhNWMy/65ZoncXCEPJxkRZL8McrseNH6lFhaxCIaX+vBuFnRng==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.57.1", - "@typescript-eslint/tsconfig-utils": "8.57.1", - "@typescript-eslint/types": "8.57.1", - "@typescript-eslint/visitor-keys": "8.57.1", - "debug": "^4.4.3", - "minimatch": "^10.2.2", - "semver": "^7.7.3", + "@typescript-eslint/project-service": "8.51.0", + "@typescript-eslint/tsconfig-utils": "8.51.0", + "@typescript-eslint/types": "8.51.0", + "@typescript-eslint/visitor-keys": "8.51.0", + "debug": "^4.3.4", + "minimatch": "^9.0.4", + "semver": "^7.6.0", "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.4.0" + "ts-api-utils": "^2.2.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1841,30 +1526,43 @@ "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.57.1.tgz", - "integrity": "sha512-XUNSJ/lEVFttPMMoDVA2r2bwrl8/oPx8cURtczkSEswY5T3AeLmCy+EKWQNdL4u0MmAHOjcWrqJp2cdvgjn8dQ==", + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.57.1", - "@typescript-eslint/types": "8.57.1", - "@typescript-eslint/typescript-estree": "8.57.1" + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.51.0.tgz", + "integrity": "sha512-11rZYxSe0zabiKaCP2QAwRf/dnmgFgvTmeDTtZvUvXG3UuAdg/GU02NExmmIXzz3vLGgMdtrIosI84jITQOxUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.51.0", + "@typescript-eslint/types": "8.51.0", + "@typescript-eslint/typescript-estree": "8.51.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1874,19 +1572,19 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.1.tgz", - "integrity": "sha512-YWnmJkXbofiz9KbnbbwuA2rpGkFPLbAIetcCNO6mJ8gdhdZ/v7WDXsoGFAJuM6ikUFKTlSQnjWnVO4ux+UzS6A==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.51.0.tgz", + "integrity": "sha512-mM/JRQOzhVN1ykejrvwnBRV3+7yTKK8tVANVN3o1O0t0v7o+jqdVu9crPy5Y9dov15TJk/FTIgoUGHrTOVL3Zg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.57.1", - "eslint-visitor-keys": "^5.0.0" + "@typescript-eslint/types": "8.51.0", + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1913,6 +1611,33 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/@vitest/mocker": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", + "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, "node_modules/@vitest/pretty-format": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", @@ -1985,11 +1710,12 @@ } }, "node_modules/acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2008,9 +1734,9 @@ } }, "node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", "dependencies": { @@ -2034,6 +1760,22 @@ "node": ">=6" } }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -2042,9 +1784,9 @@ "license": "Python-2.0" }, "node_modules/aria-query": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz", - "integrity": "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -2072,14 +1814,11 @@ } }, "node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } + "license": "MIT" }, "node_modules/bootstrap": { "version": "5.3.8", @@ -2117,16 +1856,14 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, "node_modules/bundle-name": { @@ -2184,6 +1921,16 @@ "node": ">=8" } }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/chai": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", @@ -2201,6 +1948,23 @@ "node": ">=18" } }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/check-error": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", @@ -2247,6 +2011,26 @@ "node": ">=6" } }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, "node_modules/color-support": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", @@ -2267,10 +2051,17 @@ "node": ">=20" } }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, "node_modules/confbox": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", - "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", + "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", "dev": true, "license": "MIT" }, @@ -2368,9 +2159,9 @@ } }, "node_modules/default-browser": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", - "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.4.0.tgz", + "integrity": "sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg==", "dev": true, "license": "MIT", "dependencies": { @@ -2424,27 +2215,17 @@ "dev": true, "license": "MIT" }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, "node_modules/devalue": { - "version": "5.6.4", - "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.4.tgz", - "integrity": "sha512-Gp6rDldRsFh/7XuouDbxMH3Mx8GMCcgzIb1pDTvNyn8pZGQ22u+Wa+lGV9dQCltFQ7uVw0MhRyb8XDskNFOReA==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.1.tgz", + "integrity": "sha512-jDwizj+IlEZBunHcOuuFVBnIMPAEHvTsJj0BcIp94xYguLRVBcXO853px/MyIJvbVzWdsGvrRweIUWJw8hBP7A==", "dev": true, "license": "MIT" }, "node_modules/dotenv": { - "version": "17.3.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz", - "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==", + "version": "17.2.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", + "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -2462,9 +2243,9 @@ "license": "MIT" }, "node_modules/esbuild": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz", - "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -2475,32 +2256,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.4", - "@esbuild/android-arm": "0.27.4", - "@esbuild/android-arm64": "0.27.4", - "@esbuild/android-x64": "0.27.4", - "@esbuild/darwin-arm64": "0.27.4", - "@esbuild/darwin-x64": "0.27.4", - "@esbuild/freebsd-arm64": "0.27.4", - "@esbuild/freebsd-x64": "0.27.4", - "@esbuild/linux-arm": "0.27.4", - "@esbuild/linux-arm64": "0.27.4", - "@esbuild/linux-ia32": "0.27.4", - "@esbuild/linux-loong64": "0.27.4", - "@esbuild/linux-mips64el": "0.27.4", - "@esbuild/linux-ppc64": "0.27.4", - "@esbuild/linux-riscv64": "0.27.4", - "@esbuild/linux-s390x": "0.27.4", - "@esbuild/linux-x64": "0.27.4", - "@esbuild/netbsd-arm64": "0.27.4", - "@esbuild/netbsd-x64": "0.27.4", - "@esbuild/openbsd-arm64": "0.27.4", - "@esbuild/openbsd-x64": "0.27.4", - "@esbuild/openharmony-arm64": "0.27.4", - "@esbuild/sunos-x64": "0.27.4", - "@esbuild/win32-arm64": "0.27.4", - "@esbuild/win32-ia32": "0.27.4", - "@esbuild/win32-x64": "0.27.4" + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" } }, "node_modules/escape-string-regexp": { @@ -2517,30 +2298,34 @@ } }, "node_modules/eslint": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.1.0.tgz", - "integrity": "sha512-S9jlY/ELKEUwwQnqWDO+f+m6sercqOPSqXM5Go94l7DOmxHVDgmSFGWEzeE/gwgTAr0W103BWt0QLe/7mabIvA==", + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.2", - "@eslint/config-array": "^0.23.3", - "@eslint/config-helpers": "^0.5.3", - "@eslint/core": "^1.1.1", - "@eslint/plugin-kit": "^0.6.1", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.2", + "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", - "ajv": "^6.14.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^9.1.2", - "eslint-visitor-keys": "^5.0.1", - "espree": "^11.2.0", - "esquery": "^1.7.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", @@ -2550,7 +2335,8 @@ "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "minimatch": "^10.2.4", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, @@ -2558,7 +2344,7 @@ "eslint": "bin/eslint.js" }, "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://eslint.org/donate" @@ -2589,9 +2375,9 @@ } }, "node_modules/eslint-plugin-svelte": { - "version": "3.16.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-3.16.0.tgz", - "integrity": "sha512-DJXxqpYZUxcE0SfYo8EJzV2ZC+zAD7fJp1n1HwcEMRR1cOEUYvjT9GuzJeNghMjgb7uxuK3IJAzI+x6zzUxO5A==", + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-3.13.1.tgz", + "integrity": "sha512-Ng+kV/qGS8P/isbNYVE3sJORtubB+yLEcYICMkUWNaDTb0SwZni/JhAYXh/Dz/q2eThUwWY0VMPZ//KYD1n3eQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2613,7 +2399,7 @@ "url": "https://github.com/sponsors/ota-meshi" }, "peerDependencies": { - "eslint": "^8.57.1 || ^9.0.0 || ^10.0.0", + "eslint": "^8.57.1 || ^9.0.0", "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" }, "peerDependenciesMeta": { @@ -2636,37 +2422,48 @@ } }, "node_modules/eslint-scope": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", - "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@types/esrecurse": "^4.3.1", - "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" }, "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/eslint-visitor-keys": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", - "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/esm-env": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", @@ -2675,18 +2472,18 @@ "license": "MIT" }, "node_modules/espree": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", - "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.16.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^5.0.1" + "eslint-visitor-keys": "^4.2.1" }, "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -2706,14 +2503,13 @@ } }, "node_modules/esrap": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.4.tgz", - "integrity": "sha512-suICpxAmZ9A8bzJjEl/+rLJiDKC0X4gYWUxT6URAWBLvlXmtbZd5ySMu/N2ZGEtMCAmflUDPSehrP9BQcsGcSg==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.1.tgz", + "integrity": "sha512-GiYWG34AN/4CUyaWAgunGt0Rxvr1PTMlGC0vvEov/uOQYWne2bpN03Um+k8jT+q3op33mKouP2zeJ6OlM+qeUg==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15", - "@typescript-eslint/types": "^8.2.0" + "@jridgewell/sourcemap-codec": "^1.4.15" } }, "node_modules/esrecurse": { @@ -2860,9 +2656,9 @@ } }, "node_modules/flatted": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", - "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "dev": true, "license": "ISC" }, @@ -2913,9 +2709,9 @@ } }, "node_modules/globals": { - "version": "17.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.4.0.tgz", - "integrity": "sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==", + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.0.0.tgz", + "integrity": "sha512-gv5BeD2EssA793rlFWVPMMCqefTlpusw6/2TbAVMy0FzcG8wKJn4O+NqJ4+XWmmwrayJgw5TzrmWjFgmz1XPqw==", "dev": true, "license": "MIT", "engines": { @@ -2947,6 +2743,16 @@ "uglify-js": "^3.1.4" } }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -2957,6 +2763,23 @@ "node": ">= 4" } }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -3036,9 +2859,9 @@ } }, "node_modules/is-wsl": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", - "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", "dev": true, "license": "MIT", "dependencies": { @@ -3150,267 +2973,6 @@ "node": ">= 0.8.0" } }, - "node_modules/lightningcss": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", - "dev": true, - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.32.0", - "lightningcss-darwin-arm64": "1.32.0", - "lightningcss-darwin-x64": "1.32.0", - "lightningcss-freebsd-x64": "1.32.0", - "lightningcss-linux-arm-gnueabihf": "1.32.0", - "lightningcss-linux-arm64-gnu": "1.32.0", - "lightningcss-linux-arm64-musl": "1.32.0", - "lightningcss-linux-x64-gnu": "1.32.0", - "lightningcss-linux-x64-musl": "1.32.0", - "lightningcss-win32-arm64-msvc": "1.32.0", - "lightningcss-win32-x64-msvc": "1.32.0" - } - }, - "node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, "node_modules/lilconfig": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", @@ -3445,9 +3007,16 @@ } }, "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true, "license": "MIT" }, @@ -3469,19 +3038,16 @@ } }, "node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "license": "BlueOak-1.0.0", + "license": "ISC", "dependencies": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^1.1.7" }, "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": "*" } }, "node_modules/minimist": { @@ -3562,41 +3128,25 @@ "license": "MIT" }, "node_modules/nypm": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.5.tgz", - "integrity": "sha512-K6AJy1GMVyfyMXRVB88700BJqNUkByijGJM8kEHpLdcAt+vSQAVfkWWHYzuRXHSY6xA2sNc5RjTj0p9rE2izVQ==", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.2.tgz", + "integrity": "sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g==", "dev": true, "license": "MIT", "dependencies": { - "citty": "^0.2.0", + "citty": "^0.1.6", + "consola": "^3.4.2", "pathe": "^2.0.3", - "tinyexec": "^1.0.2" + "pkg-types": "^2.3.0", + "tinyexec": "^1.0.1" }, "bin": { "nypm": "dist/cli.mjs" }, "engines": { - "node": ">=18" + "node": "^14.16.0 || >=16.10.0" } }, - "node_modules/nypm/node_modules/citty": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/citty/-/citty-0.2.1.tgz", - "integrity": "sha512-kEV95lFBhQgtogAPlQfJJ0WGVSokvLr/UEoFPiKKOXF7pl98HfUVUD0ejsuTCld/9xH9vogSywZ5KqHzXrZpqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/obug": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", - "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", - "dev": true, - "funding": [ - "https://github.com/sponsors/sxzz", - "https://opencollective.com/debug" - ], - "license": "MIT" - }, "node_modules/ohash": { "version": "2.0.11", "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", @@ -3673,6 +3223,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -3711,9 +3274,9 @@ } }, "node_modules/perfect-debounce": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz", - "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.0.0.tgz", + "integrity": "sha512-fkEH/OBiKrqqI/yIgjR92lMfs2K8105zt/VT6+7eTjNwisrsh47CeIED9z58zI7DfKdH3uHAn25ziRZn3kgAow==", "dev": true, "license": "MIT" }, @@ -3730,6 +3293,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -3750,9 +3314,9 @@ } }, "node_modules/postcss": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", - "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "dev": true, "funding": [ { @@ -3769,6 +3333,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -3809,9 +3374,9 @@ } }, "node_modules/postcss-load-config/node_modules/yaml": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", - "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", "dev": true, "license": "ISC", "engines": { @@ -3897,11 +3462,12 @@ } }, "node_modules/prettier": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", - "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz", + "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -3913,9 +3479,9 @@ } }, "node_modules/prettier-plugin-svelte": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.5.1.tgz", - "integrity": "sha512-65+fr5+cgIKWKiqM1Doum4uX6bY8iFCdztvvp2RcF+AJoieaw9kJOFMNcJo/bkmKYsxFaM9OsVZK/gWauG/5mg==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.4.1.tgz", + "integrity": "sha512-xL49LCloMoZRvSwa6IEdN2GV6cq2IqpYGstYtMT+5wmml1/dClEoI0MZR78MiVPpu6BdQFfN0/y73yO6+br5Pg==", "dev": true, "license": "MIT", "peerDependencies": { @@ -3958,44 +3524,20 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/rolldown": { - "version": "1.0.0-rc.10", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.10.tgz", - "integrity": "sha512-q7j6vvarRFmKpgJUT8HCAUljkgzEp4LAhPlJUvQhA5LA1SUL36s5QCysMutErzL3EbNOZOkoziSx9iZC4FddKA==", + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, "license": "MIT", - "dependencies": { - "@oxc-project/types": "=0.120.0", - "@rolldown/pluginutils": "1.0.0-rc.10" - }, - "bin": { - "rolldown": "bin/cli.mjs" - }, "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-rc.10", - "@rolldown/binding-darwin-arm64": "1.0.0-rc.10", - "@rolldown/binding-darwin-x64": "1.0.0-rc.10", - "@rolldown/binding-freebsd-x64": "1.0.0-rc.10", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.10", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.10", - "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.10", - "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.10", - "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.10", - "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.10", - "@rolldown/binding-linux-x64-musl": "1.0.0-rc.10", - "@rolldown/binding-openharmony-arm64": "1.0.0-rc.10", - "@rolldown/binding-wasm32-wasi": "1.0.0-rc.10", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.10", - "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.10" + "node": ">=4" } }, "node_modules/rollup": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.0.tgz", - "integrity": "sha512-yqjxruMGBQJ2gG4HtjZtAfXArHomazDHoFwFFmZZl0r7Pdo7qCIXKqKHZc8yeoMgzJJ+pO6pEEHa+V7uzWlrAQ==", + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.54.0.tgz", + "integrity": "sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==", "dev": true, "license": "MIT", "dependencies": { @@ -4009,31 +3551,28 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.60.0", - "@rollup/rollup-android-arm64": "4.60.0", - "@rollup/rollup-darwin-arm64": "4.60.0", - "@rollup/rollup-darwin-x64": "4.60.0", - "@rollup/rollup-freebsd-arm64": "4.60.0", - "@rollup/rollup-freebsd-x64": "4.60.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.60.0", - "@rollup/rollup-linux-arm-musleabihf": "4.60.0", - "@rollup/rollup-linux-arm64-gnu": "4.60.0", - "@rollup/rollup-linux-arm64-musl": "4.60.0", - "@rollup/rollup-linux-loong64-gnu": "4.60.0", - "@rollup/rollup-linux-loong64-musl": "4.60.0", - "@rollup/rollup-linux-ppc64-gnu": "4.60.0", - "@rollup/rollup-linux-ppc64-musl": "4.60.0", - "@rollup/rollup-linux-riscv64-gnu": "4.60.0", - "@rollup/rollup-linux-riscv64-musl": "4.60.0", - "@rollup/rollup-linux-s390x-gnu": "4.60.0", - "@rollup/rollup-linux-x64-gnu": "4.60.0", - "@rollup/rollup-linux-x64-musl": "4.60.0", - "@rollup/rollup-openbsd-x64": "4.60.0", - "@rollup/rollup-openharmony-arm64": "4.60.0", - "@rollup/rollup-win32-arm64-msvc": "4.60.0", - "@rollup/rollup-win32-ia32-msvc": "4.60.0", - "@rollup/rollup-win32-x64-gnu": "4.60.0", - "@rollup/rollup-win32-x64-msvc": "4.60.0", + "@rollup/rollup-android-arm-eabi": "4.54.0", + "@rollup/rollup-android-arm64": "4.54.0", + "@rollup/rollup-darwin-arm64": "4.54.0", + "@rollup/rollup-darwin-x64": "4.54.0", + "@rollup/rollup-freebsd-arm64": "4.54.0", + "@rollup/rollup-freebsd-x64": "4.54.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.54.0", + "@rollup/rollup-linux-arm-musleabihf": "4.54.0", + "@rollup/rollup-linux-arm64-gnu": "4.54.0", + "@rollup/rollup-linux-arm64-musl": "4.54.0", + "@rollup/rollup-linux-loong64-gnu": "4.54.0", + "@rollup/rollup-linux-ppc64-gnu": "4.54.0", + "@rollup/rollup-linux-riscv64-gnu": "4.54.0", + "@rollup/rollup-linux-riscv64-musl": "4.54.0", + "@rollup/rollup-linux-s390x-gnu": "4.54.0", + "@rollup/rollup-linux-x64-gnu": "4.54.0", + "@rollup/rollup-linux-x64-musl": "4.54.0", + "@rollup/rollup-openharmony-arm64": "4.54.0", + "@rollup/rollup-win32-arm64-msvc": "4.54.0", + "@rollup/rollup-win32-ia32-msvc": "4.54.0", + "@rollup/rollup-win32-x64-gnu": "4.54.0", + "@rollup/rollup-win32-x64-msvc": "4.54.0", "fsevents": "~2.3.2" } }, @@ -4077,9 +3616,9 @@ } }, "node_modules/set-cookie-parser": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.1.0.tgz", - "integrity": "sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw==", + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", "dev": true, "license": "MIT" }, @@ -4162,6 +3701,19 @@ "dev": true, "license": "MIT" }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/strip-literal": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", @@ -4175,25 +3727,38 @@ "url": "https://github.com/sponsors/antfu" } }, - "node_modules/svelte": { - "version": "5.54.1", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.54.1.tgz", - "integrity": "sha512-ow8tncN097Ty8U1H+C3bM1xNlsCbnO2UZeN0lWBnv8f3jKho7QTTQ2LWbMXrPQDodLjH91n4kpNnLolyRhVE6A==", + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/svelte": { + "version": "5.46.1", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.46.1.tgz", + "integrity": "sha512-ynjfCHD3nP2el70kN5Pmg37sSi0EjOm9FgHYQdC4giWG/hzO3AatzXXJJgP305uIhGQxSufJLuYWtkY8uK/8RA==", + "dev": true, + "license": "MIT", + "peer": true, "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", - "@types/trusted-types": "^2.0.7", "acorn": "^8.12.1", - "aria-query": "5.3.1", + "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", - "devalue": "^5.6.4", + "devalue": "^5.5.0", "esm-env": "^1.2.1", - "esrap": "^2.2.2", + "esrap": "^2.2.1", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", @@ -4204,9 +3769,9 @@ } }, "node_modules/svelte-check": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.4.5.tgz", - "integrity": "sha512-1bSwIRCvvmSHrlK52fOlZmVtUZgil43jNL/2H18pRpa+eQjzGt6e3zayxhp1S7GajPFKNM/2PMCG+DZFHlG9fw==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.3.5.tgz", + "integrity": "sha512-e4VWZETyXaKGhpkxOXP+B/d0Fp/zKViZoJmneZWe/05Y2aqSKj3YN2nLfYPJBQ87WEiY4BQCQ9hWGu9mPT1a1Q==", "dev": true, "license": "MIT", "dependencies": { @@ -4228,9 +3793,9 @@ } }, "node_modules/svelte-eslint-parser": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-1.6.0.tgz", - "integrity": "sha512-qoB1ehychT6OxEtQAqc/guSqLS20SlA53Uijl7x375s8nlUT0lb9ol/gzraEEatQwsyPTJo87s2CmKL9Xab+Uw==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-1.4.1.tgz", + "integrity": "sha512-1eqkfQ93goAhjAXxZiu1SaKI9+0/sxp4JIWQwUpsz7ybehRE5L8dNuz7Iry7K22R47p5/+s9EM+38nHV2OlgXA==", "dev": true, "license": "MIT", "dependencies": { @@ -4239,12 +3804,11 @@ "espree": "^10.0.0", "postcss": "^8.4.49", "postcss-scss": "^4.0.9", - "postcss-selector-parser": "^7.0.0", - "semver": "^7.7.2" + "postcss-selector-parser": "^7.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0", - "pnpm": "10.30.3" + "pnpm": "10.24.0" }, "funding": { "url": "https://github.com/sponsors/ota-meshi" @@ -4258,54 +3822,6 @@ } } }, - "node_modules/svelte-eslint-parser/node_modules/eslint-scope": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/svelte-eslint-parser/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/svelte-eslint-parser/node_modules/espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.15.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -4314,9 +3830,9 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz", - "integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", "dev": true, "license": "MIT", "engines": { @@ -4381,9 +3897,9 @@ } }, "node_modules/ts-api-utils": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", - "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", "dev": true, "license": "MIT", "engines": { @@ -4393,14 +3909,6 @@ "typescript": ">=4.8.4" } }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -4420,6 +3928,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -4429,16 +3938,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.57.1.tgz", - "integrity": "sha512-fLvZWf+cAGw3tqMCYzGIU6yR8K+Y9NT2z23RwOjlNFF2HwSB3KhdEFI5lSBv8tNmFkkBShSjsCjzx1vahZfISA==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.51.0.tgz", + "integrity": "sha512-jh8ZuM5oEh2PSdyQG9YAEM1TCGuWenLSuSUhf/irbVUNW9O5FhbFVONviN2TgMTBnUmyHv7E56rYnfLZK6TkiA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.57.1", - "@typescript-eslint/parser": "8.57.1", - "@typescript-eslint/typescript-estree": "8.57.1", - "@typescript-eslint/utils": "8.57.1" + "@typescript-eslint/eslint-plugin": "8.51.0", + "@typescript-eslint/parser": "8.51.0", + "@typescript-eslint/typescript-estree": "8.51.0", + "@typescript-eslint/utils": "8.51.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4448,7 +3957,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, @@ -4491,112 +4000,12 @@ "license": "MIT" }, "node_modules/vite": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.1.tgz", - "integrity": "sha512-wt+Z2qIhfFt85uiyRt5LPU4oVEJBXj8hZNWKeqFG4gRG/0RaRGJ7njQCwzFVjO+v4+Ipmf5CY7VdmZRAYYBPHw==", - "dev": true, - "license": "MIT", - "dependencies": { - "lightningcss": "^1.32.0", - "picomatch": "^4.0.3", - "postcss": "^8.5.8", - "rolldown": "1.0.0-rc.10", - "tinyglobby": "^0.2.15" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.0", - "esbuild": "^0.27.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "@vitejs/devtools": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vite-node": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", - "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cac": "^6.7.14", - "debug": "^4.4.1", - "es-module-lexer": "^1.7.0", - "pathe": "^2.0.3", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" - }, - "bin": { - "vite-node": "vite-node.mjs" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/vite-node/node_modules/vite": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", - "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.0.tgz", + "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -4666,10 +4075,33 @@ } } }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/vitefu": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.2.tgz", - "integrity": "sha512-zpKATdUbzbsycPFBN71nS2uzBUQiVnFoOrr2rvqv34S1lcAgMKKkjWleLGeiJlZ8lwCXvtWaRn7R3ZC16SYRuw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.1.tgz", + "integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==", "dev": true, "license": "MIT", "workspaces": [ @@ -4678,7 +4110,7 @@ "tests/projects/workspace/packages/*" ], "peerDependencies": { - "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-beta.0" + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" }, "peerDependenciesMeta": { "vite": { @@ -4759,33 +4191,6 @@ } } }, - "node_modules/vitest/node_modules/@vitest/mocker": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", - "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "3.2.4", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.17" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, "node_modules/vitest/node_modules/tinyexec": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", @@ -4793,81 +4198,6 @@ "dev": true, "license": "MIT" }, - "node_modules/vitest/node_modules/vite": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", - "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.27.0", - "fdir": "^6.5.0", - "picomatch": "^4.0.3", - "postcss": "^8.5.6", - "rollup": "^4.43.0", - "tinyglobby": "^0.2.15" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "lightningcss": "^1.21.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -4935,11 +4265,13 @@ } }, "node_modules/yaml": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", - "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", - "extraneous": true, + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "dev": true, "license": "ISC", + "optional": true, + "peer": true, "bin": { "yaml": "bin.mjs" }, diff --git a/web/package.json b/web/package.json index fce3e61..e5b88f2 100644 --- a/web/package.json +++ b/web/package.json @@ -17,13 +17,13 @@ }, "devDependencies": { "@eslint/compat": "^2.0.0", - "@eslint/js": "^10.0.0", + "@eslint/js": "^9.36.0", "@hey-api/openapi-ts": "0.86.10", "@sveltejs/adapter-static": "^3.0.9", "@sveltejs/kit": "^2.43.2", - "@sveltejs/vite-plugin-svelte": "^7.0.0", + "@sveltejs/vite-plugin-svelte": "^6.2.0", "@types/node": "^24.0.0", - "eslint": "^10.0.0", + "eslint": "^9.38.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-svelte": "^3.12.4", "globals": "^17.0.0", @@ -33,7 +33,7 @@ "svelte-check": "^4.3.2", "typescript": "^5.9.2", "typescript-eslint": "^8.44.1", - "vite": "^8.0.0", + "vite": "^7.1.10", "vitest": "^3.2.4" }, "dependencies": { diff --git a/web/src/lib/components/AuthenticationCard.svelte b/web/src/lib/components/AuthenticationCard.svelte index 46a4d2d..097dff1 100644 --- a/web/src/lib/components/AuthenticationCard.svelte +++ b/web/src/lib/components/AuthenticationCard.svelte @@ -13,19 +13,12 @@ let { authentication, authenticationGrade, authenticationScore, dnsResults }: Props = $props(); - let allRequiredMissing = $derived( - !authentication.spf && - (!authentication.dkim || authentication.dkim.length === 0) && - !authentication.dmarc, - ); - function getAuthResultClass(result: string, noneIsFail: boolean): string { switch (result) { case "pass": case "domain_pass": case "orgdomain_pass": return "text-success"; - case "permerror": case "error": case "fail": case "missing": @@ -58,7 +51,6 @@ case "neutral": case "invalid": case "null": - case "permerror": case "error": case "null_smtp": case "null_header": @@ -103,28 +95,6 @@ - {#if allRequiredMissing} -
-
- - No authentication results found. -

- This usually means either: -

-
    -
  • - The receiving mail server is not configured to verify email authentication - (no Authentication-Results header was found in the message). -
  • -
  • - The Authentication-Results header exists but the receiver - hostname does not match the configured - --receiver-hostname value. -
  • -
-
-
- {/if}
{#if authentication.iprev} diff --git a/web/src/lib/components/BlacklistCard.svelte b/web/src/lib/components/BlacklistCard.svelte index bb80acb..7f9b7f2 100644 --- a/web/src/lib/components/BlacklistCard.svelte +++ b/web/src/lib/components/BlacklistCard.svelte @@ -1,21 +1,23 @@
-

+

Blacklist Checks @@ -33,7 +35,11 @@

-
+ {#if receivedChain} + + {/if} + +
{#each Object.entries(blacklists) as [ip, checks]}
diff --git a/web/src/lib/components/EmailPathCard.svelte b/web/src/lib/components/EmailPathCard.svelte index a4fda45..8dc57b0 100644 --- a/web/src/lib/components/EmailPathCard.svelte +++ b/web/src/lib/components/EmailPathCard.svelte @@ -1,6 +1,5 @@ {#if receivedChain && receivedChain.length > 0} -
-
-

- - Email Path -

-
-
+
+
Email Path (Received Chain)
+
{#each receivedChain as hop, i}
@@ -40,7 +30,7 @@ : "-"}
- {#if hop.with || hop.id || hop.from} + {#if hop.with || hop.id}

{#if hop.with} diff --git a/web/src/lib/components/PtrForwardRecordsDisplay.svelte b/web/src/lib/components/PtrForwardRecordsDisplay.svelte index 8ed723b..77ce6c8 100644 --- a/web/src/lib/components/PtrForwardRecordsDisplay.svelte +++ b/web/src/lib/components/PtrForwardRecordsDisplay.svelte @@ -21,11 +21,6 @@ ); const hasForwardRecords = $derived(ptrForwardRecords && ptrForwardRecords.length > 0); - - let showDifferent = $state(false); - const differentCount = $derived( - ptrForwardRecords ? ptrForwardRecords.filter((ip) => ip !== senderIp).length : 0, - ); {#if ptrRecords && ptrRecords.length > 0} @@ -68,31 +63,15 @@

Forward Resolution (A/AAAA): {#each ptrForwardRecords as ip} - {#if ip === senderIp || !fcrDnsIsValid || showDifferent} -
- {#if senderIp && ip === senderIp} - Match - {:else} - Different - {/if} - {ip} -
- {/if} - {/each} - {#if fcrDnsIsValid && differentCount > 0} -
- +
+ {#if senderIp && ip === senderIp} + Match + {:else} + Different + {/if} + {ip}
- {/if} + {/each}
{#if fcrDnsIsValid}
diff --git a/web/src/lib/components/RspamdCard.svelte b/web/src/lib/components/RspamdCard.svelte deleted file mode 100644 index 4c2795b..0000000 --- a/web/src/lib/components/RspamdCard.svelte +++ /dev/null @@ -1,153 +0,0 @@ - - -
-
-

- - - rspamd Analysis - - - {#if rspamd.deliverability_score !== undefined} - - {rspamd.deliverability_score}% - - {/if} - {#if rspamd.deliverability_grade !== undefined} - - {/if} - -

-
-
-
-
- Score: - - {rspamd.score.toFixed(2)} / {rspamd.threshold.toFixed(1)} - -
-
- Classified as: - - {rspamd.is_spam ? "SPAM" : "HAM"} - -
-
- Action: - - {effectiveAction.label} - -
-
- - {#if rspamd.symbols && Object.keys(rspamd.symbols).length > 0} -
-
- - - - - - - - - - {#each Object.entries(rspamd.symbols).sort(([, a], [, b]) => b.score - a.score) as [symbolName, symbol]} - 0 - ? "table-warning" - : symbol.score < 0 - ? "table-success" - : ""} - > - - - - - {/each} - -
SymbolScoreDescription
- {symbolName} - {#if symbol.params} - - {symbol.params} - - {/if} - - 0 - ? "text-danger fw-bold" - : symbol.score < 0 - ? "text-success fw-bold" - : "text-muted"} - > - {symbol.score > 0 ? "+" : ""}{symbol.score.toFixed(2)} - - {symbol.description ?? ""}
-
-
- {/if} - - {#if rspamd.report} -
- Raw Report -
{rspamd.report}
-
- {/if} -
-
- - diff --git a/web/src/lib/components/SpamAssassinCard.svelte b/web/src/lib/components/SpamAssassinCard.svelte index cc88c23..2da105e 100644 --- a/web/src/lib/components/SpamAssassinCard.svelte +++ b/web/src/lib/components/SpamAssassinCard.svelte @@ -6,9 +6,11 @@ interface Props { spamassassin: SpamAssassinResult; + spamGrade?: string; + spamScore?: number; } - let { spamassassin }: Props = $props(); + let { spamassassin, spamGrade, spamScore }: Props = $props();
@@ -19,13 +21,13 @@ SpamAssassin Analysis - {#if spamassassin.deliverability_score !== undefined} - - {spamassassin.deliverability_score}% + {#if spamScore !== undefined} + + {spamScore}% {/if} - {#if spamassassin.deliverability_grade !== undefined} - + {#if spamGrade !== undefined} + {/if}
diff --git a/web/src/lib/components/SummaryCard.svelte b/web/src/lib/components/SummaryCard.svelte index 518e996..199bc94 100644 --- a/web/src/lib/components/SummaryCard.svelte +++ b/web/src/lib/components/SummaryCard.svelte @@ -25,32 +25,16 @@ // Email sender information const mailFrom = report.header_analysis?.headers?.from?.value || "an unknown sender"; - const hasDkim = - report.dns_results?.dkim_records && report.dns_results?.dkim_records?.length > 0; - const dkimPassed = - report.authentication?.dkim && - report.authentication?.dkim.length > 0 && - report.authentication?.dkim?.some((d) => d.result === "pass"); + const hasDkim = report.authentication?.dkim && report.authentication?.dkim.length > 0; + const dkimPassed = hasDkim && report.authentication?.dkim?.some((d) => d.result === "pass"); segments.push({ text: "Received a " }); segments.push({ - text: hasDkim ? "DKIM-signed" : "non-DKIM-signed", - highlight: { - color: hasDkim ? (dkimPassed ? "good" : "warning") : "danger", - bold: true, - }, - link: hasDkim && dkimPassed ? "#authentication-dkim" : "#dns-details", + text: dkimPassed ? "DKIM-signed" : "non-DKIM-signed", + highlight: { color: dkimPassed ? "good" : "danger", bold: true }, + link: "#authentication-dkim", }); - segments.push({ text: " email" }); - if (hasDkim && !dkimPassed) { - segments.push({ text: " with " }); - segments.push({ - text: "an invalid signature", - highlight: { color: "danger", bold: true }, - link: "#authentication-dkim", - }); - } - segments.push({ text: " from " }); + segments.push({ text: " email from " }); segments.push({ text: mailFrom, highlight: { emphasis: true }, @@ -129,7 +113,7 @@ } else if (spfResult === "temperror" || spfResult === "permerror") { segments.push({ text: "encountered an error", - highlight: { color: "danger", bold: true }, + highlight: { color: "warning", bold: true }, link: "#authentication-spf", }); segments.push({ text: ", check your SPF record configuration" }); @@ -347,7 +331,7 @@ highlight: { color: "good", bold: true }, link: "#dns-bimi", }); - if (bimiResult?.details && bimiResult.details.indexOf("declined") == 0) { + if (bimiResult.details && bimiResult.details.indexOf("declined") == 0) { segments.push({ text: " declined to participate" }); } else if (bimiResult?.result === "fail") { segments.push({ text: " but " }); @@ -438,17 +422,6 @@ }); } - // One-click unsubscribe check - const unsubscribeMethods = report.content_analysis?.unsubscribe_methods; - if (unsubscribeMethods && unsubscribeMethods.length > 0 && !unsubscribeMethods.includes("one-click")) { - segments.push({ text: ". This email could benefit from " }); - segments.push({ - text: "one-click unsubscribe", - highlight: { color: "warning", bold: true }, - link: "#content-details", - }); - } - // Content/spam assessment const spamAssassin = report.spamassassin; const contentScore = report.summary?.content_score || 0; diff --git a/web/src/lib/components/WhitelistCard.svelte b/web/src/lib/components/WhitelistCard.svelte deleted file mode 100644 index 13fd86b..0000000 --- a/web/src/lib/components/WhitelistCard.svelte +++ /dev/null @@ -1,62 +0,0 @@ - - -
-
-

- - - Whitelist Checks - - Informational -

-
-
-

- DNS whitelists identify trusted senders. Being listed here is a positive signal, but has - no impact on the overall score. -

- -
- {#each Object.entries(whitelists) as [ip, checks]} -
-
- - {ip} -
- - - {#each checks as check} - - - - - {/each} - -
- - {check.error - ? "Error" - : check.listed - ? "Listed" - : "Not listed"} - - {check.rbl}
-
- {/each} -
-
-
diff --git a/web/src/lib/components/index.ts b/web/src/lib/components/index.ts index 8ed409c..3c76feb 100644 --- a/web/src/lib/components/index.ts +++ b/web/src/lib/components/index.ts @@ -19,9 +19,7 @@ export { default as PendingState } from "./PendingState.svelte"; export { default as PtrForwardRecordsDisplay } from "./PtrForwardRecordsDisplay.svelte"; export { default as PtrRecordsDisplay } from "./PtrRecordsDisplay.svelte"; export { default as ScoreCard } from "./ScoreCard.svelte"; -export { default as RspamdCard } from "./RspamdCard.svelte"; export { default as SpamAssassinCard } from "./SpamAssassinCard.svelte"; export { default as SpfRecordsDisplay } from "./SpfRecordsDisplay.svelte"; export { default as SummaryCard } from "./SummaryCard.svelte"; export { default as TinySurvey } from "./TinySurvey.svelte"; -export { default as WhitelistCard } from "./WhitelistCard.svelte"; diff --git a/web/src/lib/stores/config.ts b/web/src/lib/stores/config.ts index c393dd2..87662ba 100644 --- a/web/src/lib/stores/config.ts +++ b/web/src/lib/stores/config.ts @@ -25,7 +25,6 @@ interface AppConfig { report_retention?: number; survey_url?: string; custom_logo_url?: string; - rbls?: string[]; } const defaultConfig: AppConfig = { diff --git a/web/src/lib/stores/theme.ts b/web/src/lib/stores/theme.ts index ea24293..362202b 100644 --- a/web/src/lib/stores/theme.ts +++ b/web/src/lib/stores/theme.ts @@ -26,7 +26,7 @@ const getInitialTheme = () => { if (!browser) return "light"; const stored = localStorage.getItem("theme"); - if (stored === "light" || stored === "dark") return stored; + if (stored) return stored; return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"; }; diff --git a/web/src/routes/blacklist/[ip]/+page.svelte b/web/src/routes/blacklist/[ip]/+page.svelte index 89676ed..180bfde 100644 --- a/web/src/routes/blacklist/[ip]/+page.svelte +++ b/web/src/routes/blacklist/[ip]/+page.svelte @@ -3,7 +3,7 @@ import { onMount } from "svelte"; import { checkBlacklist } from "$lib/api"; import type { BlacklistCheckResponse } from "$lib/api/types.gen"; - import { BlacklistCard, GradeDisplay, TinySurvey, WhitelistCard } from "$lib/components"; + import { BlacklistCard, GradeDisplay, TinySurvey } from "$lib/components"; import { theme } from "$lib/stores/theme"; let ip = $derived($page.params.ip); @@ -28,7 +28,7 @@ }); if (response.response.ok) { - result = response.data ?? null; + result = response.data; } else if (response.error) { error = response.error.message || "Failed to check IP address"; } @@ -122,8 +122,8 @@ >

This IP address is listed on {result.listed_count} of - {result.blacklists.length} checked blacklist{result - .blacklists.length > 1 + {result.checks.length} checked blacklist{result + .checks.length > 1 ? "s" : ""}.

@@ -150,23 +150,12 @@
-
- -
- -
- - - {#if result.whitelists && result.whitelists.length > 0} -
- -
- {/if} -
+ +
diff --git a/web/src/routes/domain/[domain]/+page.svelte b/web/src/routes/domain/[domain]/+page.svelte index d866e21..e191192 100644 --- a/web/src/routes/domain/[domain]/+page.svelte +++ b/web/src/routes/domain/[domain]/+page.svelte @@ -130,7 +130,7 @@
diff --git a/web/src/routes/test/[test]/+page.svelte b/web/src/routes/test/[test]/+page.svelte index 113209d..bf44d20 100644 --- a/web/src/routes/test/[test]/+page.svelte +++ b/web/src/routes/test/[test]/+page.svelte @@ -3,26 +3,21 @@ import { onDestroy } from "svelte"; import { getReport, getTest, reanalyzeReport } from "$lib/api"; - import type { BlacklistCheck, Report, Test } from "$lib/api/types.gen"; + import type { Report, Test } from "$lib/api/types.gen"; import { AuthenticationCard, BlacklistCard, ContentAnalysisCard, DnsRecordsCard, - EmailPathCard, ErrorDisplay, HeaderAnalysisCard, PendingState, - RspamdCard, ScoreCard, SpamAssassinCard, SummaryCard, TinySurvey, - WhitelistCard, } from "$lib/components"; - type BlacklistRecords = Record; - let testId = $derived(page.params.test); let test = $state(null); let report = $state(null); @@ -295,15 +290,6 @@
- - {#if report.header_analysis?.received_chain && report.header_analysis.received_chain.length > 0} -
-
- -
-
- {/if} - {#if report.dns_results}
@@ -334,45 +320,17 @@ {/if} - {#snippet blacklistChecks(blacklists: BlacklistRecords, report: Report)} - - {/snippet} - - - {#snippet whitelistChecks(whitelists: BlacklistRecords)} - - {/snippet} - - - {#if report.blacklists && report.whitelists && Object.keys(report.blacklists).length == 1 && Object.keys(report.whitelists).length == 1} -
-
- {@render blacklistChecks(report.blacklists, report)} -
-
- {@render whitelistChecks(report.whitelists)} + {#if report.blacklists && Object.keys(report.blacklists).length > 0} +
+
+
- {:else} - {#if report.blacklists && Object.keys(report.blacklists).length > 0} -
-
- {@render blacklistChecks(report.blacklists, report)} -
-
- {/if} - - {#if report.whitelists && Object.keys(report.whitelists).length > 0} -
-
- {@render whitelistChecks(report.whitelists)} -
-
- {/if} {/if} @@ -389,19 +347,16 @@
{/if} - - {#if report.spamassassin || report.rspamd} + + {#if report.spamassassin}
- {#if report.spamassassin} -
- -
- {/if} - {#if report.rspamd} -
- -
- {/if} +
+ +
{/if}