Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
110 commits
Select commit Hold shift + click to select a range
49c108a
Modernize model fields. Closes #863 (#887)
SupRaKoshti Feb 26, 2026
da9793b
Add IP enrichment via ThreatFox and AbuseIPDB. Progresses #522 (#867)
opbot-xd Feb 26, 2026
536a38a
Skip IOCs with empty days_seen in scoring pipeline. Closes #886 (#892)
Sanchit2662 Feb 26, 2026
80c45a2
Feeds page: add IntelOwl analysis link for each IOC. Closes #292 (#865)
rootp1 Feb 27, 2026
66c8390
Several Docker-related improvements (#890)
regulartim Feb 27, 2026
bf91ee9
Fix Docker regressions from PR #890. Closes #898 (#900)
regulartim Feb 27, 2026
d68c85c
Replace regex IP validation with ipaddress stdlib in EnrichmentSerial…
tanmayjoddar Feb 27, 2026
8b69a1b
feat(pipeline): GeoIP enrichment . Closes #524 (#880)
drona-gyawali Feb 27, 2026
cad5d45
Fix: clear user data and isSuperuser on logout. Closes #893 (#897)
Deepanshu1230 Feb 28, 2026
ef8421c
Feeds filters controlled by Formik state. Closes #889 (#894)
UsamaElareeny Mar 2, 2026
3a94276
Login attempts from various honeypots included. Fixes #470 (#911)
rootp1 Mar 2, 2026
cc55456
feat(api): Geo related field addition in the feed_response. Closes #5…
drona-gyawali Mar 2, 2026
9918f7d
Disable submit buttons while submitting to avoid duplicate api reques…
UsamaElareeny Mar 3, 2026
c733f58
Fix DatabaseDefault crash in _update_days_seen. Closes #908 (#910)
Sanchit2662 Mar 3, 2026
be55457
Filter feed type. Closes #421 (#907)
armoredvortex Mar 3, 2026
dbadaaf
build(deps): bump django from 5.2.11 to 5.2.12 in /requirements (#940)
dependabot[bot] Mar 4, 2026
727786f
Propagate firehol_categories in _merge_iocs. Closes #922 (#933)
tanmayjoddar Mar 4, 2026
0f1e252
build(deps): bump axios from 1.13.5 to 1.13.6 in /frontend (#937)
dependabot[bot] Mar 4, 2026
4effbf3
build(deps-dev): bump stylelint from 17.3.0 to 17.4.0 in /frontend (#…
dependabot[bot] Mar 4, 2026
3067c1e
fix: remove redundant loading state in EnrichmentLookup. Closes #901(…
Deepanshu1230 Mar 4, 2026
6e5df77
expose tags in API responses and add tag-based filtering. Closes #522…
opbot-xd Mar 4, 2026
acd0688
Add rate limiting to feeds endpoints. Closes #923 (#927)
manik3160 Mar 4, 2026
b01e3e0
Bump rollup from 4.57.1 to 4.59.0
regulartim Mar 4, 2026
06a17ed
Chore: Add missing rel attributes to target="_blank" links. Closes #9…
ZGr3Y Mar 4, 2026
f1d6b3c
Add button to reset filters on Feeds page. Closes #935 (#943)
armoredvortex Mar 4, 2026
ed245e3
fix: use correct query param key for format in feeds_advanced (#953)
Sanchit2662 Mar 5, 2026
4b02814
Bump immutable from 5.1.4 to 5.1.5 in package-lock.json
regulartim Mar 5, 2026
bfc69a3
fix(security): Secure ALLOWED_HOSTS configuration. Closes #925 (#950)
chauhan-varun Mar 5, 2026
0ddc0be
feat(feeds): display country info in details popover. Closes #549 (#954)
lvb05 Mar 5, 2026
4fd35a9
Remove redundant nested Suspense wrappers in Routes.jsx. Closes #957 …
Deepanshu1230 Mar 5, 2026
f96967a
fix: return empty list instead of empty dict in load_training_data. C…
Sanchit2662 Mar 5, 2026
46669a5
refactor: remove localStorage persistence from Register form. Closes …
minthanttun20 Mar 6, 2026
5cff476
Advanced filtering, STIX 2.1 export, and shareable feed URLs. Closes …
R1sh0bh-1 Mar 6, 2026
c42c03e
Implementation of the Tanner Honeypot Extraction and Including it in …
rootp1 Mar 9, 2026
4f65b03
Fix: changepassword validation. Closes #951 (#963)
UsamaElareeny Mar 9, 2026
e13fb00
test: add test coverage for tasks.py wrapper functions. Closes #964 (…
opbot-xd Mar 9, 2026
e5588cd
fix(extraction): use min/max semantics for first_seen and last_seen …
tanmayjoddar Mar 9, 2026
a03e42a
fix: union instead of replace for include_similar in command_sequence…
Sanchit2662 Mar 9, 2026
d16e1ba
Strip timezone from parsed ES timestamps to prevent naive/aware compa…
regulartim Mar 9, 2026
cd9069b
Add GitHub and LinkedIn links to footer. Closes #979 (#1002)
Aditya30ag Mar 9, 2026
9b2e124
tests: add test coverage for greedybear/slack.py. Closes #977 (#1000)
Abhijeet17o Mar 10, 2026
b0ba921
feature: normalize credentials into separate Credential model. Closes…
rahulgunwanistudy-2005 Mar 10, 2026
b0d85d1
Truncate credentials in data migration to prevent varchar overflow
regulartim Mar 10, 2026
bebf474
Remove country code from AbuseIPDB enrichment / Tags (#1001)
regulartim Mar 10, 2026
5cbda72
Add rDNS-based mass scanner detection via behavioral heuristics. Clos…
Sahityaaryan Mar 10, 2026
6c7dc2b
Frontend: Improve Error Boundary Coverage. Closes #978 (#982)
chauhan-varun Mar 10, 2026
eceac53
test: add tests for AuthGuard and IfAuthRedirectGuard. Closes #972 (#…
Deepanshu1230 Mar 10, 2026
a6f5ddf
build(deps): bump stix2 from 3.0.1 to 3.0.2 in /requirements (#1015)
dependabot[bot] Mar 10, 2026
ab2e0d1
build(deps): bump numpy from 2.4.2 to 2.4.3 in /requirements (#1016)
dependabot[bot] Mar 10, 2026
6640841
Attack Origin Visualizer for Dashboard. Closes #955 (#983)
armoredvortex Mar 11, 2026
9ca2747
fix(extraction): add sort guard to _update_days_seen and tests. Close…
tanmayjoddar Mar 11, 2026
59c5ba4
Fix unauthenticated feeds memory exhaustion DoS. Closes #844 (#993)
R1sh0bh-1 Mar 11, 2026
22cea38
test: add coverage for ClusterCommandSequences.run(). Closes #976 (#1…
tanmayjoddar Mar 11, 2026
9dd5464
fix: record statistics only after input validation in cowrie_session_…
Sanchit2662 Mar 11, 2026
ef47509
Refactor: Reduce duplicated logic in chart components. Closes #969 (#…
swara-2006 Mar 11, 2026
870ff77
feat: allow querying CowrieSession API by password. Closes #607 (#1022)
rahulgunwanistudy-2005 Mar 11, 2026
97ac79a
feat(pipeline/api): replace IOC.asn with AutonomousSystem FK. Closes …
drona-gyawali Mar 12, 2026
489f070
perf: bulk-prefetch extraction data. Closes #1008 (#1020)
manik3160 Mar 13, 2026
143f6ff
fix: eliminate N+1 queries in IocRepository.add_honeypot_to_ioc(). Cl…
Abhijeet17o Mar 16, 2026
51e3679
Migrate from uWSGI to gunicorn. Closes #891 (#904)
SupRaKoshti Mar 16, 2026
77b3389
refactor: define IpReputation constants and replace hardcoded strings…
Sahityaaryan Mar 16, 2026
4a20eb2
Add client-side validation to enrichment lookup. Closes #1023 (#1031)
chauhan-varun Mar 17, 2026
0a47a1c
fix: normalize_credential_field missing truncation. Closes #1029 (#1033)
Sanchit2662 Mar 17, 2026
3cfd73a
tests: add test coverage for utilities in greedybear/utils.py. Closes…
manik3160 Mar 17, 2026
bb3eb70
fix: replace socket.inet_aton with ipaddress.ip_address to support IP…
rahulgunwanistudy-2005 Mar 17, 2026
cbafa4d
fix: normalize honeypot membership check to avoid case-drift re-adds …
Abhijeet17o Mar 18, 2026
7c4a689
build(deps): bump library/nginx in /docker (#1075)
dependabot[bot] Mar 18, 2026
53b681f
build(deps): bump slack-sdk from 3.40.1 to 3.41.0 in /requirements (#…
dependabot[bot] Mar 18, 2026
d50e3b7
feat: persist Feeds page filters in URL query params. Closes #1017 (#…
Deepanshu1230 Mar 18, 2026
88ea858
build(deps): bump croniter from 6.0.0 to 6.2.2 in /requirements (#1077)
dependabot[bot] Mar 18, 2026
7e29ceb
enh: Log feature importances after Random Forest training. Closes: #1…
drona-gyawali Mar 18, 2026
ab507e7
Feature: Extract and store Cowrie file transfer metadata. Closes #848…
cclts Mar 19, 2026
707ed46
Feature: Implementation of Heralding extraction strategy. Closes #100…
rootp1 Mar 19, 2026
6ec9ae7
build(deps): bump sass from 1.97.3 to 1.98.0 in /frontend (#1078)
dependabot[bot] Mar 21, 2026
6a79346
build(deps-dev): bump @vitest/coverage-v8 in /frontend (#1079)
dependabot[bot] Mar 21, 2026
eb916ea
build(deps-dev): bump jsdom from 28.1.0 to 29.0.0 in /frontend (#1080)
dependabot[bot] Mar 21, 2026
02c07a6
build(deps-dev): bump @vitejs/plugin-react in /frontend (#1081)
dependabot[bot] Mar 21, 2026
14284ea
build(deps-dev): bump vite from 7.3.1 to 8.0.0 in /frontend (#1082)
dependabot[bot] Mar 21, 2026
9974f53
Added test for useAuthStore component:Closes #987 (#1037)
swara-2006 Mar 22, 2026
152ce04
fix(cronjob): propagate exceptions in execute() to prevent downstream…
IQRAZAM Mar 22, 2026
d77a85c
Fix ordering regression. Closes #1093 (#1094)
armoredvortex Mar 22, 2026
c8289ea
bump flatted form 3.3.3 to 3.4.2 in /frontend
regulartim Mar 23, 2026
4f93e8f
Move stats recording after validation in command_sequence_view. Close…
ChaitanyaChute Mar 23, 2026
b56811c
build(deps-dev): bump @vitest/coverage-v8 in /frontend (#1116)
dependabot[bot] Mar 25, 2026
f1a7635
build(deps-dev): bump vite from 8.0.1 to 8.0.2 in /frontend (#1114)
dependabot[bot] Mar 25, 2026
2b3f015
build(deps): bump djangorestframework in /requirements (#1112)
dependabot[bot] Mar 25, 2026
a323012
build(deps-dev): bump stylelint from 17.4.0 to 17.5.0 in /frontend (#…
dependabot[bot] Mar 25, 2026
43be36e
build(deps-dev): bump @vitejs/plugin-react in /frontend (#1115)
dependabot[bot] Mar 25, 2026
03855bf
Non-root user using gosu in app container. closes #1102 (#1106)
SupRaKoshti Mar 25, 2026
9e9a700
tests: add behavioral coverage for dashboard/utils/charts.jsx (#1109)
piyushzgautam99-ship-it Mar 25, 2026
1933771
perf: optimize N+1 queries and bulk database operations in extraction…
manik3160 Mar 26, 2026
9d3e466
Support for custom labels/descriptions in the Sensor model.Closes #10…
R1sh0bh-1 Mar 26, 2026
83c5ed7
Fix blank page caused by upgrade to vite 8. Closes #1126 (#1127)
regulartim Mar 26, 2026
2916d04
Fix Zombie Sessions by handling 401/403. Closes #1072 (#1123)
chauhan-varun Mar 26, 2026
74ac529
Migration to uv. Closes #1131 (#1027)
regulartim Mar 27, 2026
fdd8957
fix: remove dead code and public sensor exposure from feeds table. Cl…
R1sh0bh-1 Mar 27, 2026
785f576
Fix/cowrie session str nonetype. Closes #1083 (#1141)
ayushagarwalhere Mar 27, 2026
00722b5
refactor: move reputation update loggin into IocRepository. Closes #1…
Sahityaaryan Mar 30, 2026
514be3c
Redundant API fetching fix. Closes #1135 (#1144)
chauhan-varun Mar 30, 2026
602e786
fix(cronjobs): update missing autonomous system names from enrichment…
manik3160 Mar 30, 2026
c443b18
fix(tests): stabilize flaky Register.test.jsx. Closes #1148 (#1151)
manik3160 Mar 31, 2026
792f6a3
build(deps): bump library/nginx in /docker (#1153)
dependabot[bot] Mar 31, 2026
8f33cbb
build(deps-dev): bump @vitest/coverage-v8 in /frontend (#1154)
dependabot[bot] Mar 31, 2026
0d87786
build(deps-dev): bump vite from 8.0.2 to 8.0.3 in /frontend (#1155)
dependabot[bot] Mar 31, 2026
ef538e1
build(deps-dev): bump stylelint from 17.5.0 to 17.6.0 in /frontend (#…
dependabot[bot] Mar 31, 2026
94d7ec3
build(deps): bump axios from 1.13.6 to 1.14.0 in /frontend (#1157)
dependabot[bot] Mar 31, 2026
d6e671f
test: add LSH clustering coverage for #975 (#1150)
Demiserular Apr 1, 2026
a21b32b
Enhance session revoke UX. Closes #1125 (#1137)
armoredvortex Apr 1, 2026
ec99a5b
Enh: Add sources tracking to Credential and link IPs on credential cr…
drona-gyawali Apr 1, 2026
ed08ca6
Merge pull request #1025 from intelowlproject/develop
regulartim Apr 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
.vscode
.lgtm.yml
__pycache__
.venv/
venv/
**/build
.env
Expand All @@ -15,4 +16,6 @@ frontend/dist
frontend/build
docker-compose*
.pre-commit-config.yaml
.ipython/
.ipython/
.github/
tests/
4 changes: 2 additions & 2 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
version: 2

updates:
- package-ecosystem: "pip"
directory: "/requirements"
- package-ecosystem: "uv"
directory: "/"
schedule:
interval: "weekly"
day: "tuesday"
Expand Down
2 changes: 1 addition & 1 deletion .github/release_template.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Checklist for creating a new release

- [ ] Change version number in `docker/.version` and in `.env_template`
- [ ] Change version number in `pyproject.toml`
- [ ] Verify CI Tests
- [ ] Verify that the PR is named with a correct version number like x.x.x
- [ ] Merge the PR to the `main` branch. The release will be done automatically by the CI
Expand Down
129 changes: 10 additions & 119 deletions .github/workflows/_python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ on:
type: string
required: true
requirements_path:
description: Path to the requirements.txt file
description: Path to the requirements.txt file (deprecated, unused with uv)
type: string
required: true
required: false
project_dev_requirements_file:
description: Path to an additional project dev requirements file
type: string
Expand Down Expand Up @@ -264,7 +264,7 @@ jobs:
id: setup_python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python_version }}
python-version-file: "pyproject.toml"

- name: Inject stuff to environment
run: |
Expand Down Expand Up @@ -312,128 +312,19 @@ jobs:
with:
apt_requirements_file_path: ${{ inputs.packages_path }}

- name: Create linter requirements file
uses: ./.github/actions/python_requirements/create_linter_requirements_file
with:
install_from: ${{ inputs.install_from }}
django_settings_module: ${{ inputs.django_settings_module }}
use_autoflake: ${{ inputs.use_autoflake }}
use_bandit: ${{ inputs.use_bandit }}
use_black: ${{ inputs.use_black }}
use_flake8: ${{ inputs.use_flake8 }}
use_isort: ${{ inputs.use_isort }}
use_pylint: ${{ inputs.use_pylint }}
use_ruff_formatter: ${{ inputs.use_ruff_formatter }}
use_ruff_linter: ${{ inputs.use_ruff_linter }}

- name: Create dev requirements file
uses: ./.github/actions/python_requirements/create_dev_requirements_file
with:
install_from: ${{ inputs.install_from }}
project_dev_requirements_file: ${{ inputs.project_dev_requirements_file }}

- name: Create docs requirements file
uses: ./.github/actions/python_requirements/create_docs_requirements_file
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
install_from: ${{ inputs.install_from }}
check_docs_directory: ${{ inputs.check_docs_directory }}
django_settings_module: ${{ inputs.django_settings_module }}
enable-cache: true
version: "0.11.1"

- name: Restore Python virtual environment related to PR event
id: restore_python_virtual_environment_pr
uses: ./.github/actions/python_requirements/restore_virtualenv/
with:
requirements_paths: "${{ inputs.requirements_path }} requirements-linters.txt requirements-dev.txt requirements-docs.txt"
python_version: ${{ steps.setup_python.outputs.python-version }}

- name: Restore Python virtual environment related to target branch
id: restore_python_virtual_environment_target_branch
if: steps.restore_python_virtual_environment_pr.outputs.cache-hit != 'true'
uses: ./.github/actions/python_requirements/restore_virtualenv/
with:
requirements_paths: ${{ inputs.requirements_path }}
git_reference: ${{ github.base_ref }}
python_version: ${{ steps.setup_python.outputs.python-version }}

- name: Create Python virtual environment
if: >
steps.restore_python_virtual_environment_pr.outputs.cache-hit != 'true' &&
steps.restore_python_virtual_environment_target_branch.outputs.cache-hit != 'true'
uses: ./.github/actions/python_requirements/create_virtualenv

- name: Restore pip cache related to PR event
id: restore_pip_cache_pr
if: >
steps.restore_python_virtual_environment_pr.outputs.cache-hit != 'true' &&
steps.restore_python_virtual_environment_target_branch.outputs.cache-hit != 'true'
uses: ./.github/actions/python_requirements/restore_pip_cache

- name: Restore pip cache related to target branch
id: restore_pip_cache_target_branch
if: >
steps.restore_python_virtual_environment_pr.outputs.cache-hit != 'true' &&
steps.restore_python_virtual_environment_target_branch.outputs.cache-hit != 'true' &&
steps.restore_pip_cache_pr.outputs.cache-hit != 'true'
uses: ./.github/actions/python_requirements/restore_pip_cache
with:
git_reference: ${{ github.base_ref }}

- name: Install project requirements
if: >
steps.restore_python_virtual_environment_pr.outputs.cache-hit != 'true' &&
steps.restore_python_virtual_environment_target_branch.outputs.cache-hit != 'true'
run: pip install -r ${{ inputs.requirements_path }}
shell: bash
working-directory: ${{ inputs.install_from }}

- name: Install other requirements
if: >
steps.restore_python_virtual_environment_pr.outputs.cache-hit != 'true'
- name: Install dependencies
run: |
pip install -r requirements-dev.txt
pip install -r requirements-linters.txt
pip install -r requirements-docs.txt
uv sync --frozen --all-groups
echo "${{ github.workspace }}/${{ inputs.install_from }}/.venv/bin" >> $GITHUB_PATH
shell: bash
working-directory: ${{ inputs.install_from }}

- name: Check requirements licenses
if: >
inputs.check_requirements_licenses &&
steps.restore_python_virtual_environment_pr.outputs.cache-hit != 'true'
id: license_check_report
continue-on-error: true
uses: pilosus/action-pip-license-checker@v2
with:
requirements: ${{ inputs.install_from }}/${{ inputs.requirements_path }}
exclude: ${{ inputs.ignore_requirements_licenses_regex }}
headers: true
fail: 'StrongCopyleft,NetworkCopyleft,Error'
fails-only: true

- name: Print wrong licenses
if: steps.license_check_report.outcome == 'failure'
run: |
echo "License check failed"
echo "===================="
echo "${{ steps.license_check_report.outputs.report }}"
echo "===================="
exit 1
shell: bash

- name: Save Python virtual environment related to PR event
if: >
steps.restore_python_virtual_environment_pr.outputs.cache-hit != 'true'
uses: ./.github/actions/python_requirements/save_virtualenv
with:
requirements_paths: "${{ inputs.requirements_path }} requirements-linters.txt requirements-dev.txt requirements-docs.txt"
python_version: ${{ steps.setup_python.outputs.python-version }}

- name: Save pip cache related to PR event
if: >
steps.restore_python_virtual_environment_pr.outputs.cache-hit != 'true' &&
steps.restore_pip_cache_pr.outputs.cache-hit != 'true'
uses: ./.github/actions/python_requirements/save_pip_cache

- name: Run linters
uses: ./.github/actions/python_linter
if: >
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/dependency_review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ jobs:
- name: 'Checkout Repository'
uses: actions/checkout@v3
- name: 'Dependency Review'
uses: actions/dependency-review-action@v3
uses: actions/dependency-review-action@v4
2 changes: 0 additions & 2 deletions .github/workflows/pull_request_automation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,6 @@ jobs:
use_ruff_formatter: true
use_ruff_linter: true

requirements_path: requirements/project-requirements.txt
project_dev_requirements_file: requirements/dev-requirements.txt
packages_path: packages.txt
django_settings_module: greedybear.settings

Expand Down
75 changes: 63 additions & 12 deletions api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
from django.core.exceptions import FieldDoesNotExist
from rest_framework import serializers

from greedybear.consts import REGEX_DOMAIN, REGEX_IP
from greedybear.models import IOC, GeneralHoneypot
from greedybear.consts import REGEX_DOMAIN
from greedybear.models import IOC, GeneralHoneypot, Sensor, Tag
from greedybear.utils import is_ip_address

logger = logging.getLogger(__name__)

Expand All @@ -19,8 +20,22 @@ def to_representation(self, value):
return value.name


class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = ["key", "value", "source"]


class SensorSerializer(serializers.ModelSerializer):
class Meta:
model = Sensor
fields = ["address", "label"]


class IOCSerializer(serializers.ModelSerializer):
general_honeypot = GeneralHoneypotSerializer(many=True, read_only=True)
tags = TagSerializer(many=True, read_only=True)
sensors = SensorSerializer(many=True, read_only=True)

class Meta:
model = IOC
Expand All @@ -36,22 +51,38 @@ class EnrichmentSerializer(serializers.Serializer):

def validate(self, data):
"""
Check a given observable against regex expression
Validate that the query is a valid IP address (IPv4/IPv6) or domain.
"""
observable = data["query"]
if re.match(r"^[\d\.]+$", observable) and not re.match(REGEX_IP, observable):
raise serializers.ValidationError("Observable is not a valid IP")
if not re.match(REGEX_IP, observable) and not re.match(REGEX_DOMAIN, observable):
raise serializers.ValidationError("Observable is not a valid IP or domain")
observable = data["query"].strip()
data["query"] = observable

# A valid domain must match the domain regex AND contain at least one alphabetic character
is_domain = bool(re.match(REGEX_DOMAIN, observable)) and any(c.isalpha() for c in observable)

if not is_ip_address(observable) and not is_domain:
raise serializers.ValidationError("Observable is not a valid IP address or domain")

try:
required_object = IOC.objects.get(name=observable)
required_object = IOC.objects.prefetch_related("tags", "sensors").get(name=observable)
data["found"] = True
data["ioc"] = required_object
except IOC.DoesNotExist:
data["found"] = False
return data


def parse_feed_types(feed_type_str: str) -> list:
"""Split a comma-separated feed type string into a stripped list of individual feed types.

Args:
feed_type_str (str): Comma-separated feed type string (e.g. "cowrie,adbhoney").

Returns:
list[str]: List of non-empty, stripped feed type tokens.
"""
return [ft.strip() for ft in feed_type_str.split(",") if ft.strip()]


def feed_type_validation(feed_type: str, valid_feed_types: frozenset) -> str:
"""Validates that a given feed type exists in the set of valid feed types.

Expand Down Expand Up @@ -96,7 +127,7 @@ def ordering_validation(ordering: str) -> str:


class FeedsRequestSerializer(serializers.Serializer):
feed_type = serializers.CharField(max_length=120)
feed_type = serializers.CharField()
attack_type = serializers.ChoiceField(choices=["scanner", "payload_request", "all"])
ioc_type = serializers.ChoiceField(choices=["ip", "domain", "all"])
max_age = serializers.IntegerField(min_value=1)
Expand All @@ -107,11 +138,28 @@ class FeedsRequestSerializer(serializers.Serializer):
ordering = serializers.CharField(max_length=120)
verbose = serializers.ChoiceField(choices=["true", "false"])
paginate = serializers.ChoiceField(choices=["true", "false"])
format = serializers.ChoiceField(choices=["csv", "json", "txt"])
format = serializers.ChoiceField(choices=["csv", "json", "txt", "stix21"])
asn = serializers.IntegerField(min_value=1, required=False, allow_null=True)
min_score = serializers.FloatField(min_value=0, max_value=1, required=False, allow_null=True)
port = serializers.IntegerField(min_value=1, max_value=65535, required=False, allow_null=True)
start_date = serializers.DateField(format="%Y-%m-%d", required=False, allow_null=True)
end_date = serializers.DateField(format="%Y-%m-%d", required=False, allow_null=True)
tag_key = serializers.CharField(max_length=128, required=False, allow_blank=True)
tag_value = serializers.CharField(max_length=256, required=False, allow_blank=True)

def validate_feed_type(self, feed_type):
logger.debug(f"FeedsRequestSerializer - validation feed_type: '{feed_type}'")
return feed_type_validation(feed_type, self.context["valid_feed_types"])
feed_types = parse_feed_types(feed_type)
if not feed_types:
raise serializers.ValidationError("Invalid feed_type: must not be empty")
valid_feed_types = self.context["valid_feed_types"]
if len(feed_types) > len(valid_feed_types):
raise serializers.ValidationError(f"Invalid feed_type: too many types specified (max {len(valid_feed_types)})")
if "all" in feed_types and len(feed_types) > 1:
raise serializers.ValidationError("Invalid feed_type: 'all' cannot be combined with other feed types")
for ft in feed_types:
feed_type_validation(ft, valid_feed_types)
return feed_type

def validate_ordering(self, ordering):
logger.debug(f"FeedsRequestSerializer - validation ordering: '{ordering}'")
Expand All @@ -122,6 +170,7 @@ class ASNFeedsOrderingSerializer(FeedsRequestSerializer):
ALLOWED_ORDERING_FIELDS = frozenset(
{
"asn",
"as_name",
"ioc_count",
"total_attack_count",
"total_interaction_count",
Expand Down Expand Up @@ -183,6 +232,8 @@ class FeedsResponseSerializer(serializers.Serializer):
login_attempts = serializers.IntegerField(min_value=0)
recurrence_probability = serializers.FloatField(min_value=0, max_value=1)
expected_interactions = serializers.FloatField(min_value=0)
attacker_country = serializers.CharField(allow_null=True, allow_blank=True, max_length=120)
tags = TagSerializer(many=True, required=False, default=list)

def validate_feed_type(self, feed_type):
logger.debug(f"FeedsResponseSerializer - validation feed_type: '{feed_type}'")
Expand Down
47 changes: 47 additions & 0 deletions api/throttles.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# This file is a part of GreedyBear https://github.com/honeynet/GreedyBear
# See the file 'LICENSE' for copying permission.
from rest_framework.throttling import SimpleRateThrottle


class FeedsThrottle(SimpleRateThrottle):
"""Rate-limit for public (unauthenticated) feeds endpoints."""

scope = "feeds"

def get_cache_key(self, request, view):
return self.cache_format % {
"scope": self.scope,
"ident": self.get_ident(request),
}


class FeedsAdvancedThrottle(SimpleRateThrottle):
"""Rate-limit for authenticated feeds endpoints (advanced, asn)."""

scope = "feeds_advanced"

def get_cache_key(self, request, view):
if request.user and request.user.is_authenticated:
ident = request.user.pk
else:
ident = self.get_ident(request)

return self.cache_format % {
"scope": self.scope,
"ident": ident,
}


class SharedFeedRateThrottle(SimpleRateThrottle):
"""
Rate throttle for the public shared feed consume endpoint.
Limits unauthenticated access to prevent abuse.
Rate is configurable via the ``FEEDS_SHARED_THROTTLE_RATE`` environment variable
(key: ``feeds_shared`` in DEFAULT_THROTTLE_RATES). Default: 10/minute.
"""

scope = "feeds_shared"

def get_cache_key(self, request, view):
return self.cache_format % {"scope": self.scope, "ident": self.get_ident(request)}
Loading
Loading