Hi all, thanks for the work on openHop. The policy engine has been really useful.
The problem
I've been using the policy engine to deal with spam on the UK Public channel. The spammer keeps rotating their client key, so the only stable thing to match on is where the traffic enters the mesh, i.e. the start of its path.
Today path_hashes with contains / intersects matches a hash anywhere in the path, and can't check order. So a rule can't tell apart:
a packet that entered the mesh via repeater AA, and
a packet that just passed throughAA on its way.
Adding hop_count == 1 helps a little, but misses anything that crossed a few repeaters before reaching us.
Two use cases
Spam entering via one repeater. Matching the first hop (AA) is enough.
Spam that always starts on the same first two hops (say AA>BB), while genuine users share the first repeater but go a different way after it. A rule on AA alone also catches the genuine users, and contains AA + contains BB can't check the order, so it still catches routes like CC>AA>BB.
Proposal
Either of these would cover both cases:
Option A: a list operator on path_hashes (probably the smallest change).
Let starts_with / ends_with take a list and compare element by element after normalisation, e.g. path_hashes starts_with ["AA", "BB"]. _compare already has starts_with / ends_with, but only for strings.
- id:1name:drop-route-starting-AA-BBenabled:trueif:all:- field:payload_typeop:equalsvalue:5# channel (group text) messages- field:path_hashesop:starts_withvalue:["AA","BB"]# first hop AA, then BB, in that orderthen:action:drop
Option B: an indexed accessor (the most flexible). path_hash[0], path_hash[1] … and path_hash[-1], each normalised like path_hashes and null when the path is shorter. path_hash[0] is where the packet entered the mesh, and path_hash[-1] is the repeater we actually heard it from. (Named first_path_hash / last_path_hash fields would be a simpler subset of this, covering use case 1 only.)
- id:2name:drop-route-starting-AA-BBenabled:trueif:all:- field:payload_typeop:equalsvalue:5- field:path_hash[0] # where it entered the meshop:equalsvalue:"AA"- field:path_hash[1] # the next repeater alongop:equalsvalue:"BB"then:action:drop
Implementation notes
From a quick look, this is a fairly small change in repeater/policy_engine.py:
Option A: extend PolicyEngine._compare so starts_with / ends_with accept lists, after path_hashes normalisation.
Option B: in PolicyEngine._get_field_value, take the index from packet.get_path_hashes_hex() and run it through _normalize_path_hash_value like the existing field.
The RepeaterUI rule builder would need the new operator or fields adding to its lists.
Caveats
This only really makes sense for flood packets, since direct routes drop hops as they go.
With 1-byte hashes there will be the odd collision, the same as with path_hashes today.
Testing
Happy to test either option on my repeater if someone picks it up. SpamGuard will use it as soon as it's in a release; at the moment it has to approximate "starts with AA>BB" as "passes through both AA and BB".
Hi all, thanks for the work on openHop. The policy engine has been really useful.
## The problem
I've been using the policy engine to deal with spam on the UK Public channel. The spammer keeps rotating their client key, so the only stable thing to match on is **where the traffic enters the mesh**, i.e. the start of its path.
Today `path_hashes` with `contains` / `intersects` matches a hash **anywhere** in the path, and can't check order. So a rule can't tell apart:
- a packet that **entered the mesh** via repeater `AA`, and
- a packet that just **passed through** `AA` on its way.
Adding `hop_count == 1` helps a little, but misses anything that crossed a few repeaters before reaching us.
## Two use cases
1. **Spam entering via one repeater.** Matching the first hop (`AA`) is enough.
2. **Spam that always starts on the same first two hops** (say `AA>BB`), while genuine users share the first repeater but go a different way after it. A rule on `AA` alone also catches the genuine users, and `contains AA` + `contains BB` can't check the order, so it still catches routes like `CC>AA>BB`.
## Proposal
Either of these would cover both cases:
**Option A: a list operator on `path_hashes` (probably the smallest change).**
Let `starts_with` / `ends_with` take a list and compare element by element after normalisation, e.g. `path_hashes starts_with ["AA", "BB"]`. `_compare` already has `starts_with` / `ends_with`, but only for strings.
```yaml
- id: 1
name: drop-route-starting-AA-BB
enabled: true
if:
all:
- field: payload_type
op: equals
value: 5 # channel (group text) messages
- field: path_hashes
op: starts_with
value: ["AA", "BB"] # first hop AA, then BB, in that order
then:
action: drop
```
**Option B: an indexed accessor (the most flexible).**
`path_hash[0]`, `path_hash[1]` … and `path_hash[-1]`, each normalised like `path_hashes` and `null` when the path is shorter. `path_hash[0]` is where the packet entered the mesh, and `path_hash[-1]` is the repeater we actually heard it from. (Named `first_path_hash` / `last_path_hash` fields would be a simpler subset of this, covering use case 1 only.)
```yaml
- id: 2
name: drop-route-starting-AA-BB
enabled: true
if:
all:
- field: payload_type
op: equals
value: 5
- field: path_hash[0] # where it entered the mesh
op: equals
value: "AA"
- field: path_hash[1] # the next repeater along
op: equals
value: "BB"
then:
action: drop
```
## Implementation notes
From a quick look, this is a fairly small change in `repeater/policy_engine.py`:
- **Option A:** extend `PolicyEngine._compare` so `starts_with` / `ends_with` accept lists, after `path_hashes` normalisation.
- **Option B:** in `PolicyEngine._get_field_value`, take the index from `packet.get_path_hashes_hex()` and run it through `_normalize_path_hash_value` like the existing field.
The RepeaterUI rule builder would need the new operator or fields adding to its lists.
## Caveats
- This only really makes sense for **flood** packets, since direct routes drop hops as they go.
- With 1-byte hashes there will be the odd collision, the same as with `path_hashes` today.
## Testing
Happy to test either option on my repeater if someone picks it up. SpamGuard will use it as soon as it's in a release; at the moment it has to approximate "starts with `AA>BB`" as "passes through both `AA` and `BB`".
<!-- github-sync-id:5774585577 -->
It watches the Public channel, picks out the spam campaigns and made-up names, and writes path_hashes rules into the policy engine for the repeater the spam comes in through. Because those rules can only match a hash anywhere in the path, it lets through anyone it has already seen sending normal messages, and the blocks lapse a couple of hours after the spam stops. It does the job, but it's still a workaround. With a first_path_hash field it could block only the traffic that actually starts at that repeater, so I'll switch it over as soon as that exists. Still happy to test.
Thanks for the +1 @SubnetConflict.
In case it helps anyone else stuck with the same spam, I've put together a small add-on that works around this for now: https://github.com/flackrat/openhop-spamguard
It watches the Public channel, picks out the spam campaigns and made-up names, and writes path_hashes rules into the policy engine for the repeater the spam comes in through. Because those rules can only match a hash anywhere in the path, it lets through anyone it has already seen sending normal messages, and the blocks lapse a couple of hours after the spam stops. It does the job, but it's still a workaround. With a first_path_hash field it could block only the traffic that actually starts at that repeater, so I'll switch it over as soon as that exists. Still happy to test.
<!-- github-comment-id:6082641535 -->
This would be useful. I got spammer using 1 byte to making harder to pinpoint, but we were able to trace the signals with a good number of observers. He is always going the same 2 repeaters which we believe the first repeater is owned by him. All of our normal users have switched to 2 or 3 byte messaging.
This would be useful. I got spammer using 1 byte to making harder to pinpoint, but we were able to trace the signals with a good number of observers. He is always going the same 2 repeaters which we believe the first repeater is owned by him. All of our normal users have switched to 2 or 3 byte messaging.
<!-- github-comment-id:6444962831 -->
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Hi all, thanks for the work on openHop. The policy engine has been really useful.
The problem
I've been using the policy engine to deal with spam on the UK Public channel. The spammer keeps rotating their client key, so the only stable thing to match on is where the traffic enters the mesh, i.e. the start of its path.
Today
path_hasheswithcontains/intersectsmatches a hash anywhere in the path, and can't check order. So a rule can't tell apart:AA, andAAon its way.Adding
hop_count == 1helps a little, but misses anything that crossed a few repeaters before reaching us.Two use cases
AA) is enough.AA>BB), while genuine users share the first repeater but go a different way after it. A rule onAAalone also catches the genuine users, andcontains AA+contains BBcan't check the order, so it still catches routes likeCC>AA>BB.Proposal
Either of these would cover both cases:
Option A: a list operator on
path_hashes(probably the smallest change).Let
starts_with/ends_withtake a list and compare element by element after normalisation, e.g.path_hashes starts_with ["AA", "BB"]._comparealready hasstarts_with/ends_with, but only for strings.Option B: an indexed accessor (the most flexible).
path_hash[0],path_hash[1]… andpath_hash[-1], each normalised likepath_hashesandnullwhen the path is shorter.path_hash[0]is where the packet entered the mesh, andpath_hash[-1]is the repeater we actually heard it from. (Namedfirst_path_hash/last_path_hashfields would be a simpler subset of this, covering use case 1 only.)Implementation notes
From a quick look, this is a fairly small change in
repeater/policy_engine.py:PolicyEngine._comparesostarts_with/ends_withaccept lists, afterpath_hashesnormalisation.PolicyEngine._get_field_value, take the index frompacket.get_path_hashes_hex()and run it through_normalize_path_hash_valuelike the existing field.The RepeaterUI rule builder would need the new operator or fields adding to its lists.
Caveats
path_hashestoday.Testing
Happy to test either option on my repeater if someone picks it up. SpamGuard will use it as soon as it's in a release; at the moment it has to approximate "starts with
AA>BB" as "passes through bothAAandBB".I'd like to +1 this, it would be super useful
Thanks for the +1 @SubnetConflict.
In case it helps anyone else stuck with the same spam, I've put together a small add-on that works around this for now: https://github.com/flackrat/openhop-spamguard
It watches the Public channel, picks out the spam campaigns and made-up names, and writes path_hashes rules into the policy engine for the repeater the spam comes in through. Because those rules can only match a hash anywhere in the path, it lets through anyone it has already seen sending normal messages, and the blocks lapse a couple of hours after the spam stops. It does the job, but it's still a workaround. With a first_path_hash field it could block only the traffic that actually starts at that repeater, so I'll switch it over as soon as that exists. Still happy to test.
This would be useful. I got spammer using 1 byte to making harder to pinpoint, but we were able to trace the signals with a good number of observers. He is always going the same 2 repeaters which we believe the first repeater is owned by him. All of our normal users have switched to 2 or 3 byte messaging.