Replace Synapse antispam module with a "rule server" counterpart

Fixes https://github.com/matrix-org/mjolnir/issues/37

**Note**: this completely replaces the existing Synapse antispam module. Admins are encouraged to upgrade upon release, though will have to make significant changes to how their configuration works.
This commit is contained in:
Travis Ralston 2020-12-24 11:52:42 -07:00
parent e4e5c5e72d
commit 90640b6714
13 changed files with 627 additions and 466 deletions

View File

@ -89,7 +89,27 @@ Using the bot to manage your rooms is great, however if you want to use your ban
is needed. Primarily meant to block invites from undesired homeservers/users, Mjolnir's
Synapse module is a way to interpret ban lists and apply them to your entire homeserver.
First, install the module to your Synapse python environment:
**Warning**: This module works by running generated Python code in your homeserver. The code
is generated based off the rules provided in the ban lists, which may include crafted rules
which may allow unrestricted access to your server. Only use lists you trust and do not
use anyone else's infrastructure to get the ruleset from - only use infrastructure that
you control.
If this is acceptable, the following steps may be performed.
First, run the Docker image for the rule server. This is what will be serving the generated
Python for the Synapse antispam module to read from. This rule server will serve the Python
off a webserver at `/api/v1/py_rules` which must be accessible by wherever Synapse is installed.
It is not recommended to expose this webserver to the outside world.
```
docker run --rm -d -v /etc/mjolnir/ruleserver.yaml:/data/config/production.yaml -p 127.0.0.0:8080:8080 matrixdotorg/mjolnir
```
**Note**: the exact same Mjolnir image is used to run the rule server. To configure using the rule
server instead of the bot function, see the `ruleServer` options in the config.
After that is running, install the module to your Synapse python environment:
```
pip install -e "git+https://github.com/matrix-org/mjolnir.git#egg=mjolnir&subdirectory=synapse_antispam"
```
@ -100,35 +120,18 @@ pip install -e "git+https://github.com/matrix-org/mjolnir.git#egg=mjolnir&subdir
Then add the following to your `homeserver.yaml`:
```yaml
spam_checker:
module: mjolnir.AntiSpam
config:
# Prevent servers/users in the ban lists from inviting users on this
# server to rooms. Default true.
block_invites: true
# Flag messages sent by servers/users in the ban lists as spam. Currently
# this means that spammy messages will appear as empty to users. Default
# false.
block_messages: false
# Remove users from the user directory search by filtering matrix IDs and
# display names by the entries in the user ban list. Default false.
block_usernames: false
# The room IDs of the ban lists to honour. Unlike other parts of Mjolnir,
# this list cannot be room aliases or permalinks. This server is expected
# to already be joined to the room - Mjolnir will not automatically join
# these rooms.
ban_lists:
- "!roomid:example.org"
- module: mjolnir.AntiSpam
config:
# Where the antispam module should periodically retrieve updated rules from. This
# should be pointed at the Mjolnir rule server.
rules_url: 'http://localhost:8080/api/v1/py_rules'
```
*Note*: Although this is described as a "spam checker", it does much more than fight
spam.
Be sure to change the configuration to match your setup. Your server is expected to
already be participating in the ban lists - if it is not, you will need to have a user
on your homeserver join. The antispam module will not join the rooms for you.
If you change the configuration, you will need to restart Synapse. You'll also need
to restart Synapse to install the plugin.
Be sure to change the configuration to match your setup. If you change the configuration,
you will need to restart Synapse. You'll also need to restart Synapse to install the plugin.
## Development

View File

@ -156,3 +156,73 @@ health:
# The HTTP status code which reports that the bot is not healthy/ready.
# Defaults to 418.
unhealthyStatus: 418
# Optional configuration for the built-in rule server
ruleServer:
# When enabled, all bot functionality will be disabled. This means that if you
# want to run a bot alongside a rule server then you'll need two instances of
# this config: one for the bot and one for the rule server. The rule server is
# disabled by default.
#
# Note that the config above should be as complete as possible, though the rule
# server will ignore many options not applicable to it (protections, commands, etc)
enabled: false
# The port to run the rule server on. Defaults to 8080. Note that if you're using
# the healthz options then this port must be different. For health monitoring
# without a healthz endpoint, use /api/v1/py_rules and look for a 200 OK response.
port: 8080
# The address to listen for requests on. Defaults to all addresses. Typically for
# Docker containers this will be 0.0.0.0 so the port mapping can function correctly.
address: '0.0.0.0'
# The policy rooms (matrix.to URLs) to expose on the rule server. These should be
# trusted rooms to avoid potential security risks with the Synapse module.
listRooms:
- "https://matrix.to/#/#yourroom:example.org"
# Restrictions that can be applied to users, servers, and whole rooms. These are
# split into categories (eg: 'messages') which describe the sort of action the
# entity will be taking. Under that are the kinds of entities that can be affected.
# When the flag for an entity is `true`, it means that if a rule applies to that
# kind of entity (eg: users) then it will be given to the antispam module to
# process. When false, rules which apply to that entity will not be considered for
# that action.
blocks:
# Whether or not to block messages (events) sent by an entity.
messages:
users: true
rooms: true
servers: true
# Whether or not to block invites from the given entities
invites:
users: true
rooms: true
servers: true
# Whether or not check a given user's username/displayname against the list rules.
usernames:
users: true
rooms: false # rooms don't have usernames and can't be blocked.
servers: false # the only rule which would apply is one for the local server.
# Whether or not rooms to block rooms from being created by given entities. Rooms are
# only created on the local server - this does not trigger for rooms which have been
# 'discovered' by federation.
roomCreate:
users: true
rooms: false # rooms don't create rooms
servers: false # room creation only happens on the local server, which means banning yourself.
# Whether or not a given entity should be blocked from creating an alias pointing at a room.
# Like room creation, this only happens on the local server - remote aliases do not trigger these.
makeAlias:
users: true
rooms: true # this would block rooms from receiving aliases on the local server
servers: false # room aliases can only be created on the local server, so blocking yourself is not recommended
# Whether or not to block rooms from being published by a given entity. Like other aspects of room
# aliases and creation, this is only triggered for local attempts to publish a room to the server's
# own directory - federated directories do not trigger this.
publishRoom:
users: true
rooms: true # this would block the rooms from entering the directory
servers: false # publishing happens locally, and it's not recommended to ban yourself

View File

@ -27,6 +27,6 @@
"config": "^3.3.1",
"escape-html": "^1.0.3",
"js-yaml": "^3.13.1",
"matrix-bot-sdk": "^0.5.4"
"matrix-bot-sdk": "^0.5.9"
}
}

View File

@ -30,6 +30,12 @@ export async function logMessage(level: LogLevel, module: string, message: strin
if (!additionalRoomIds) additionalRoomIds = [];
if (!Array.isArray(additionalRoomIds)) additionalRoomIds = [additionalRoomIds];
// Prefix logging if we're running in ruleserver mode
if (config.ruleServer?.enabled) {
module = `[RuleServer] ${module}`;
message = `[RuleServer] ${message}`;
}
if (config.RUNTIME.client && (config.verboseLogging || LogLevel.INFO.includes(level))) {
let clientMessage = message;
if (level === LogLevel.WARN) clientMessage = `⚠ | ${message}`;

View File

@ -17,6 +17,21 @@ limitations under the License.
import * as config from "config";
import { MatrixClient } from "matrix-bot-sdk";
export interface IBlock {
users: boolean;
rooms: boolean;
servers: boolean;
}
export interface IRuleServerBlocks {
messages: IBlock;
invites: IBlock;
usernames: IBlock;
roomCreate: IBlock;
makeAlias: IBlock;
publishRoom: IBlock;
}
interface IConfig {
homeserverUrl: string;
accessToken: string;
@ -59,6 +74,13 @@ interface IConfig {
unhealthyStatus: number;
};
};
ruleServer: {
enabled: boolean;
port: number;
address: string;
listRooms: string[];
blocks: IRuleServerBlocks;
};
/**
* Config options only set at runtime. Try to avoid using the objects
@ -111,6 +133,44 @@ const defaultConfig: IConfig = {
unhealthyStatus: 418,
},
},
ruleServer: {
enabled: false,
port: 8080,
address: '0.0.0.0',
listRooms: [],
blocks: {
messages: {
users: false,
rooms: false,
servers: false,
},
invites: {
users: false,
rooms: false,
servers: false,
},
usernames: {
users: false,
rooms: false,
servers: false,
},
roomCreate: {
users: false,
rooms: false,
servers: false,
},
makeAlias: {
users: false,
rooms: false,
servers: false,
},
publishRoom: {
users: false,
rooms: false,
servers: false,
},
}
},
// Needed to make the interface happy.
RUNTIME: {

View File

@ -31,6 +31,7 @@ import { logMessage } from "./LogProxy";
import { MembershipEvent } from "matrix-bot-sdk/lib/models/events/MembershipEvent";
import * as htmlEscape from "escape-html";
import { Healthz } from "./health/healthz";
import { RuleServer } from "./modules/RuleServer";
config.RUNTIME = {client: null};
@ -57,6 +58,40 @@ if (config.health.healthz.enabled) {
config.RUNTIME.client = client;
const joinedRooms = await client.getJoinedRooms();
// Ensure we're in the management room
LogService.info("index", "Resolving management room...");
const managementRoomId = await client.resolveRoom(config.managementRoom);
if (!joinedRooms.includes(managementRoomId)) {
config.managementRoom = await client.joinRoom(config.managementRoom);
} else {
config.managementRoom = managementRoomId;
}
// Switch to using the rule server if we need to
if (config.ruleServer?.enabled) {
await logMessage(LogLevel.INFO, "index", "Rule server is starting up.");
// Resolve all the rule server's watched rooms
const banLists: BanList[] = [];
for (const roomRef of config.ruleServer.listRooms) {
const permalink = Permalinks.parseUrl(roomRef);
if (!permalink.roomIdOrAlias) continue;
let roomId = await client.resolveRoom(permalink.roomIdOrAlias);
if (!joinedRooms.includes(roomId)) {
roomId = await client.joinRoom(permalink.roomIdOrAlias, permalink.viaServers);
}
banLists.push(new BanList(roomId, roomRef, client));
}
const ruleServer = new RuleServer(client, banLists);
await ruleServer.start();
return;
}
client.on("room.invite", async (roomId: string, inviteEvent: any) => {
const membershipEvent = new MembershipEvent(inviteEvent);
@ -89,8 +124,6 @@ if (config.health.healthz.enabled) {
const banLists: BanList[] = [];
const protectedRooms: { [roomId: string]: string } = {};
const joinedRooms = await client.getJoinedRooms();
// Ensure we're also joined to the rooms we're protecting
LogService.info("index", "Resolving protected rooms...");
for (const roomRef of config.protectedRooms) {
@ -105,14 +138,6 @@ if (config.health.healthz.enabled) {
protectedRooms[roomId] = roomRef;
}
// Ensure we're also in the management room
LogService.info("index", "Resolving management room...");
const managementRoomId = await client.resolveRoom(config.managementRoom);
if (!joinedRooms.includes(managementRoomId)) {
config.managementRoom = await client.joinRoom(config.managementRoom);
} else {
config.managementRoom = managementRoomId;
}
await logMessage(LogLevel.INFO, "index", "Mjolnir is starting up. Use !mjolnir to query status.");
const bot = new Mjolnir(client, protectedRooms, banLists);

View File

@ -26,7 +26,7 @@ export function recommendationToStable(recommendation: string, unstable = true):
export class ListRule {
private glob: MatrixGlob;
public readonly glob: MatrixGlob;
constructor(public readonly entity: string, private action: string, public readonly reason: string, public readonly kind: string) {
this.glob = new MatrixGlob(entity);

163
src/modules/RuleServer.ts Normal file
View File

@ -0,0 +1,163 @@
/*
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { LogLevel, MatrixClient } from "matrix-bot-sdk";
import BanList, { ALL_RULE_TYPES } from "../models/BanList";
import { logMessage } from "../LogProxy";
import { Healthz } from "../health/healthz";
import config, { IRuleServerBlocks } from "../config";
import { ListRule } from "../models/ListRule";
import * as http from "http";
interface IRule {
search: string; // python
pattern: string;
}
interface IPyRules {
checks: {
spam: IRule[];
invites: IRule[];
profiles: IRule[];
createRoom: IRule[];
createAlias: IRule[];
publishRoom: IRule[];
};
}
export class RuleServer {
private pyRules: IPyRules;
constructor(private client: MatrixClient, private banLists: BanList[]) {
client.on("room.event", this.handleEvent.bind(this));
}
public async start() {
for (const list of this.banLists) {
await list.updateList();
}
await this.rebuildRules();
await this.client.start();
Healthz.isHealthy = true;
http.createServer((req, res) => {
res.setHeader('Content-Type', 'application/json');
res.writeHead(200);
res.end(JSON.stringify({healthy: Healthz.isHealthy, ...this.pyRules}));
}).listen(config.ruleServer.port, config.ruleServer.address, async () => {
await logMessage(LogLevel.INFO, "RuleServer", "Rule server is running");
});
}
private async handleEvent(roomId: string, event: any) {
const banList = this.banLists.find(b => b.roomId === roomId);
if (!banList) return; // not useful to us
if (!ALL_RULE_TYPES.includes(event['type'])) return; // not useful to us
await banList.updateList();
await this.rebuildRules();
}
private async rebuildRules() {
const userRules = this.banLists.map(b => b.userRules).reduce((a, c) => {a.push(...c); return a;}, []);
const roomRules = this.banLists.map(b => b.roomRules).reduce((a, c) => {a.push(...c); return a;}, []);
const serverRules = this.banLists.map(b => b.serverRules).reduce((a, c) => {a.push(...c); return a;}, []);
console.log({userRules, roomRules, serverRules});
this.pyRules = {
checks: {
spam: pythonForRulesCond('messages', {
users: {rules: userRules, python: "event.get('sender', '')"},
rooms: {rules: roomRules, python: "event.get('room_id', '')"},
servers: {rules: serverRules, python: "UserID.from_string(event.get('sender', '')).domain"},
}),
invites: pythonForRulesCond('invites', {
users: {rules: userRules, python: "inviter_user_id"},
rooms: {rules: roomRules, python: "room_id"},
servers: {rules: serverRules, python: "UserID.from_string(inviter_user_id).domain"},
}),
profiles: [
...pythonForRulesCond('usernames', {
users: {rules: userRules, python: 'user_profile["user_id"]'},
rooms: null, // not possible
servers: {rules: serverRules, python: 'UserID.from_string(user_profile["user_id"]).domain'},
}),
...pythonForRulesCond('usernames', {
// run a second check on the user's display name
users: {rules: userRules, python: 'user_profile["display_name"]'},
rooms: null,
servers: null,
}),
],
createRoom: pythonForRulesCond('roomCreate', {
users: {rules: userRules, python: "user_id"},
rooms: null,
servers: {rules: serverRules, python: "UserID.from_string(user_id).domain"},
}),
createAlias: pythonForRulesCond('makeAlias', {
users: {rules: userRules, python: "user_id"},
rooms: {rules: roomRules, python: "room_alias"},
servers: {rules: serverRules, python: "UserID.from_string(user_id).domain"},
}),
publishRoom: pythonForRulesCond('publishRoom', {
users: {rules: userRules, python: "user_id"},
rooms: {rules: roomRules, python: "room_id"},
servers: {rules: serverRules, python: "UserID.from_string(user_id).domain"},
}),
},
};
await logMessage(LogLevel.INFO, "RuleServer", "Python rule set updated");
}
}
// ==== mini python lib below ====
interface IPythonRuleEntity {
rules: ListRule[];
python: string;
}
interface IPythonRule {
users: IPythonRuleEntity;
rooms: IPythonRuleEntity;
servers: IPythonRuleEntity;
}
function pythonForRulesCond(blockName: keyof IRuleServerBlocks, conf: IPythonRule): IRule[] {
const generated: IRule[] = [];
if (config.ruleServer.blocks[blockName].users && conf.users) {
for (const rule of conf.users.rules) {
generated.push({pattern: rule.glob.regex.source, search: conf.users.python});
}
}
if (config.ruleServer.blocks[blockName].rooms && conf.rooms) {
for (const rule of conf.rooms.rules) {
generated.push({pattern: rule.glob.regex.source, search: conf.rooms.python});
}
}
if (config.ruleServer.blocks[blockName].servers && conf.servers) {
for (const rule of conf.servers.rules) {
generated.push({pattern: rule.glob.regex.source, search: conf.servers.python});
}
}
return generated;
}

View File

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2019 The Matrix.org Foundation C.I.C.
# Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -14,115 +14,145 @@
# limitations under the License.
import logging
from .list_rule import ALL_RULE_TYPES, RECOMMENDATION_BAN
from .ban_list import BanList
import re
from synapse.types import UserID
from twisted.internet import task, reactor, defer
logger = logging.getLogger("synapse.contrib." + __name__)
class AntiSpam(object):
def __init__(self, config, api):
self.block_invites = config.get("block_invites", True)
self.block_messages = config.get("block_messages", False)
self.block_usernames = config.get("block_usernames", False)
self.list_room_ids = config.get("ban_lists", [])
self.rooms_to_lists = {} # type: Dict[str, BanList]
self.api = api
self._api = api
self._config = config
# Now we build the ban lists so we can match them
self.build_lists()
# Start a timer to rip the new rules off the server. We hit the rule server
# often to avoid missing rules - the response should be tens of milliseconds
# in duration, though network issues are possible - Twisted should automatically
# stack the calls for us.
# HACK: Private member access (_hs)
api._hs.get_clock().looping_call(self._update_rules, 5 * 1000)
def build_lists(self):
for room_id in self.list_room_ids:
self.build_list(room_id)
# These are all arrays of compile()'d code from the ban list server.
# First match wins. See _update_rules() for more info.
self._code_spam_checks = []
self._code_invite_checks = []
self._code_profile_checks = []
self._code_create_room_checks = []
self._code_create_alias_checks = []
self._code_publish_room_checks = []
def build_list(self, room_id):
logger.info("Rebuilding ban list for %s" % (room_id))
self.get_list_for_room(room_id).build()
def _update_rules(self):
async def run():
try:
logger.info("Updating mjolnir rules...")
def get_list_for_room(self, room_id):
if room_id not in self.rooms_to_lists:
self.rooms_to_lists[room_id] = BanList(api=self.api, room_id=room_id)
return self.rooms_to_lists[room_id]
# HACK: Private member access (_hs)
resp = await self._api._hs.get_proxied_http_client().get_json(self._config['rules_url'])
def is_user_banned(self, user_id):
for room_id in self.rooms_to_lists:
ban_list = self.rooms_to_lists[room_id]
for rule in ban_list.user_rules:
if rule.matches(user_id):
return rule.action == RECOMMENDATION_BAN
return False
# *** !! DANGER !! ***
# We're about to trust that the admin has secured their network appropriately and
# that the values returned by the configurable URL are safe to execute as real
# python code. We're using eval(), which should mean that it can only be expressions,
# however there is still risk of damages. We are knowingly processing the strings
# returned and assuming they won't try to take control of the homeserver, or worse.
# *** !! DANGER !! ***
def is_room_banned(self, invite_room_id):
for room_id in self.rooms_to_lists:
ban_list = self.rooms_to_lists[room_id]
for rule in ban_list.room_rules:
if rule.matches(invite_room_id):
return rule.action == RECOMMENDATION_BAN
return False
self._code_spam_checks = self._compile_rules(resp['checks']['spam'])
self._code_invite_checks = self._compile_rules(resp['checks']['invites'])
self._code_profile_checks = self._compile_rules(resp['checks']['profiles'])
self._code_create_room_checks = self._compile_rules(resp['checks']['createRoom'])
self._code_create_alias_checks = self._compile_rules(resp['checks']['createAlias'])
self._code_publish_room_checks = self._compile_rules(resp['checks']['publishRoom'])
def is_server_banned(self, server_name):
for room_id in self.rooms_to_lists:
ban_list = self.rooms_to_lists[room_id]
for rule in ban_list.server_rules:
if rule.matches(server_name):
return rule.action == RECOMMENDATION_BAN
return False
logger.info("Updated mjolnir rules")
except Exception as e:
logger.warning("Error updating mjolnir rules: %s", e)
return defer.ensureDeferred(run())
def _compile_rules(self, rules):
return [{
"search": compile(rule["search"], '<string>', 'eval'),
"pattern": rule["pattern"],
} for rule in rules]
# --- spam checker interface below here ---
def check_event_for_spam(self, event):
room_id = event.get("room_id", "")
event_type = event.get("type", "")
state_key = event.get("state_key", None)
# Rebuild the rules if there's an event for our ban lists
if state_key is not None and event_type in ALL_RULE_TYPES and room_id in self.list_room_ids:
logger.info("Received ban list event - updating list")
self.get_list_for_room(room_id).build(with_event=event)
return False # Ban list updates aren't spam
if not self.block_messages:
return False # not spam (we aren't blocking messages)
sender = UserID.from_string(event.get("sender", ""))
if self.is_user_banned(sender.to_string()):
return True
if self.is_server_banned(sender.domain):
return True
for check in self._code_spam_checks:
params = {
"event": event,
"UserID": UserID,
}
search = eval(check["search"], {}, params)
if re.search(check["pattern"], search):
return True # is spam
return False # not spam (as far as we're concerned)
def user_may_invite(self, inviter_user_id, invitee_user_id, room_id):
if not self.block_invites:
return True # allowed (we aren't blocking invites)
sender = UserID.from_string(inviter_user_id)
if self.is_user_banned(sender.to_string()):
return False
if self.is_room_banned(room_id):
return False
if self.is_server_banned(sender.domain):
return False
for check in self._code_invite_checks:
params = {
"inviter_user_id": inviter_user_id,
"invitee_user_id": invitee_user_id,
"room_id": room_id,
"UserID": UserID,
}
search = eval(check["search"], {}, params)
if re.search(check["pattern"], search):
return False # is spam
return True # allowed (as far as we're concerned)
def check_username_for_spam(self, user_profile):
if not self.block_usernames:
return True # allowed (we aren't blocking based on usernames)
for check in self._code_profile_checks:
params = {
"user_profile": user_profile,
"UserID": UserID,
}
search = eval(check["search"], {}, params)
if re.search(check["pattern"], search):
return False # is spam
# Check whether the user ID or display name matches any of the banned
# patterns.
return self.is_user_banned(user_profile["user_id"]) or self.is_user_banned(user_profile["display_name"])
return True # allowed (as far as we're concerned)
def user_may_create_room(self, user_id):
return True # allowed
for check in self._code_create_room_checks:
params = {
"user_id": user_id,
"UserID": UserID,
}
search = eval(check["search"], {}, params)
if re.search(check["pattern"], search):
return False # is spam
return True # allowed (as far as we're concerned)
def user_may_create_room_alias(self, user_id, room_alias):
return True # allowed
for check in self._code_create_alias_checks:
params = {
"user_id": user_id,
"room_alias": room_alias,
"UserID": UserID,
}
search = eval(check["search"], {}, params)
if re.search(check["pattern"], search):
return False # is spam
return True # allowed (as far as we're concerned)
def user_may_publish_room(self, user_id, room_id):
return True # allowed
for check in self._code_publish_room_checks:
params = {
"user_id": user_id,
"room_id": room_id,
"UserID": UserID,
}
search = eval(check["search"], {}, params)
if re.search(check["pattern"], search):
return False # is spam
return True # allowed (as far as we're concerned)
@staticmethod
def parse_config(config):

View File

@ -1,76 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright 2019 The Matrix.org Foundation C.I.C.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
from .list_rule import ListRule, ALL_RULE_TYPES, USER_RULE_TYPES, SERVER_RULE_TYPES, ROOM_RULE_TYPES
from twisted.internet import defer
from synapse.metrics.background_process_metrics import run_as_background_process
logger = logging.getLogger("synapse.contrib." + __name__)
class BanList(object):
def __init__(self, api, room_id):
self.api = api
self.room_id = room_id
self.server_rules = []
self.user_rules = []
self.room_rules = []
self.build()
def build(self, with_event=None):
@defer.inlineCallbacks
def run(with_event):
events = yield self.get_relevant_state_events()
if with_event is not None:
events = [*events, with_event]
self.server_rules = []
self.user_rules = []
self.room_rules = []
for event in events:
event_type = event.get("type", "")
state_key = event.get("state_key", "")
content = event.get("content", {})
if state_key is None:
continue # Some message event got in here?
# Skip over events which are replaced by with_event. We do this
# to ensure that when we rebuild the list we're using updated rules.
if with_event is not None:
w_event_type = with_event.get("type", "")
w_state_key = with_event.get("state_key", "")
w_event_id = with_event.event_id
event_id = event.event_id
if w_event_type == event_type and w_state_key == state_key and w_event_id != event_id:
continue
entity = content.get("entity", None)
recommendation = content.get("recommendation", None)
reason = content.get("reason", None)
if entity is None or recommendation is None or reason is None:
continue # invalid event
logger.info("Adding rule %s/%s with action %s" % (event_type, entity, recommendation))
rule = ListRule(entity=entity, action=recommendation, reason=reason, kind=event_type)
if event_type in USER_RULE_TYPES:
self.user_rules.append(rule)
elif event_type in ROOM_RULE_TYPES:
self.room_rules.append(rule)
elif event_type in SERVER_RULE_TYPES:
self.server_rules.append(rule)
run_as_background_process("mjolnir_build_ban_list", run, with_event)
def get_relevant_state_events(self):
return self.api.get_state_events_in_room(self.room_id, [(t, None) for t in ALL_RULE_TYPES])

View File

@ -1,52 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright 2019 The Matrix.org Foundation C.I.C.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from synapse.util import glob_to_regex
RECOMMENDATION_BAN = "m.ban"
RECOMMENDATION_BAN_TYPES = [RECOMMENDATION_BAN, "org.matrix.mjolnir.ban"]
RULE_USER = "m.room.rule.user"
RULE_ROOM = "m.room.rule.room"
RULE_SERVER = "m.room.rule.server"
USER_RULE_TYPES = [RULE_USER, "org.matrix.mjolnir.rule.user"]
ROOM_RULE_TYPES = [RULE_ROOM, "org.matrix.mjolnir.rule.room"]
SERVER_RULE_TYPES = [RULE_SERVER, "org.matrix.mjolnir.rule.server"]
ALL_RULE_TYPES = [*USER_RULE_TYPES, *ROOM_RULE_TYPES, *SERVER_RULE_TYPES]
def recommendation_to_stable(recommendation):
if recommendation in RECOMMENDATION_BAN_TYPES:
return RECOMMENDATION_BAN
return None
def rule_type_to_stable(rule):
if rule in USER_RULE_TYPES:
return RULE_USER
if rule in ROOM_RULE_TYPES:
return RULE_ROOM
if rule in SERVER_RULE_TYPES:
return RULE_SERVER
return None
class ListRule(object):
def __init__(self, entity, action, reason, kind):
self.entity = entity
self.regex = glob_to_regex(entity)
self.action = recommendation_to_stable(action)
self.reason = reason
self.kind = rule_type_to_stable(kind)
def matches(self, victim):
return self.regex.match(victim)

View File

@ -2,7 +2,7 @@ from setuptools import setup, find_packages
setup(
name="mjolnir",
version="0.0.1",
version="0.1.0",
packages=find_packages(),
description="Mjolnir Antispam",
include_package_data=True,

376
yarn.lock
View File

@ -99,13 +99,14 @@
"@types/node" "*"
"@types/range-parser" "*"
"@types/express@^4.17.2":
version "4.17.2"
resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.2.tgz#a0fb7a23d8855bac31bc01d5a58cadd9b2173e6c"
integrity sha512-5mHFNyavtLoJmnusB8OKJ5bshSzw+qkMIBAobLrIM48HJvunFva9mOa6aBwh64lBFyNwBbs0xiEFuj4eU/NjCA==
"@types/express@^4.17.7":
version "4.17.9"
resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.9.tgz#f5f2df6add703ff28428add52bdec8a1091b0a78"
integrity sha512-SDzEIZInC4sivGIFY4Sz1GG6J9UObPwCInYJjko2jzOf/Imx/dlpume6Xxwj1ORL82tBbmN4cPDIDkLbWHk9hw==
dependencies:
"@types/body-parser" "*"
"@types/express-serve-static-core" "*"
"@types/qs" "*"
"@types/serve-static" "*"
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0":
@ -153,6 +154,11 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-11.13.21.tgz#afcf913437eeb1f9e1994a62a03b1762ee4600d3"
integrity sha512-fLwcSjMmDnjfk4FP7/QDiNzXSCEOGNvEe9eA6vaITmC784+Gm70wF7woaFQxUb2CpMjgLBhSPyhH0oIe1JS2uw==
"@types/qs@*":
version "6.9.5"
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.5.tgz#434711bdd49eb5ee69d90c1d67c354a9a8ecb18b"
integrity sha512-/JHkVHtx/REVG0VVToGRGH2+23hsYLHdyG+GrvoUGlGAd0ErauXDyvHtRI/7H7mzLm+tBCKA7pfcpkQ1lf58iQ==
"@types/range-parser@*":
version "1.2.3"
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c"
@ -198,12 +204,12 @@ accepts@~1.3.7:
mime-types "~2.1.24"
negotiator "0.6.2"
ajv@^6.5.5:
version "6.10.2"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.2.tgz#d3cea04d6b017b2894ad69040fec8b623eb4bd52"
integrity sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==
ajv@^6.12.3:
version "6.12.6"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
dependencies:
fast-deep-equal "^2.0.1"
fast-deep-equal "^3.1.1"
fast-json-stable-stringify "^2.0.0"
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
@ -286,11 +292,6 @@ array-flatten@1.1.1:
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=
array-uniq@^1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6"
integrity sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=
array-unique@^0.3.2:
version "0.3.2"
resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428"
@ -356,7 +357,7 @@ base@^0.11.1:
mixin-deep "^1.2.0"
pascalcase "^0.1.1"
basic-auth@~2.0.0:
basic-auth@~2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a"
integrity sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==
@ -477,7 +478,7 @@ caseless@~0.12.0:
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
chalk@^2.0.0, chalk@^2.0.1, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4.2:
chalk@^2.0.0, chalk@^2.0.1, chalk@^2.3.0, chalk@^2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
@ -486,14 +487,6 @@ chalk@^2.0.0, chalk@^2.0.1, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4.2:
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
chalk@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4"
integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==
dependencies:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
chalk@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.0.0.tgz#6e98081ed2d17faab615eb52ac66ec1fe6209e72"
@ -502,6 +495,14 @@ chalk@^4.0.0:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
chalk@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a"
integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==
dependencies:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
chokidar@3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.0.tgz#12c0714668c55800f659e262d4962a97faf554a6"
@ -699,6 +700,11 @@ depd@~1.1.2:
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
depd@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
destroy@~1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
@ -724,38 +730,47 @@ diff@^4.0.1:
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.1.tgz#0c667cb467ebbb5cea7f14f135cc2dba7780a8ff"
integrity sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==
dom-serializer@0:
version "0.2.1"
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.1.tgz#13650c850daffea35d8b626a4cfc4d3a17643fdb"
integrity sha512-sK3ujri04WyjwQXVoK4PU3y8ula1stq10GJZpqHIUgoGZdsGzAGu65BnU3d08aTVSvO7mGPZUc0wTEDL+qGE0Q==
dom-serializer@^1.0.1:
version "1.2.0"
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.2.0.tgz#3433d9136aeb3c627981daa385fc7f32d27c48f1"
integrity sha512-n6kZFH/KlCrqs/1GHMOd5i2fd/beQHuehKdWvNNffbGHTr/almdhuVvTVFb3V7fglz+nC50fFusu3lY33h12pA==
dependencies:
domelementtype "^2.0.1"
domhandler "^4.0.0"
entities "^2.0.0"
domelementtype@1, domelementtype@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f"
integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==
domelementtype@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.0.1.tgz#1f8bdfe91f5a78063274e803b4bdcedf6e94f94d"
integrity sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==
domhandler@^2.3.0:
version "2.4.2"
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803"
integrity sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==
dependencies:
domelementtype "1"
domelementtype@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.1.0.tgz#a851c080a6d1c3d94344aed151d99f669edf585e"
integrity sha512-LsTgx/L5VpD+Q8lmsXSHW2WpA+eBlZ9HPf3erD1IoPF00/3JKHZ3BknUVA2QGDNu69ZNmyFmCWBSO45XjYKC5w==
domutils@^1.5.1:
version "1.7.0"
resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a"
integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==
domhandler@^3.0.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-3.3.0.tgz#6db7ea46e4617eb15cf875df68b2b8524ce0037a"
integrity sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA==
dependencies:
dom-serializer "0"
domelementtype "1"
domelementtype "^2.0.1"
domhandler@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.0.0.tgz#01ea7821de996d85f69029e81fa873c21833098e"
integrity sha512-KPTbnGQ1JeEMQyO1iYXoagsI6so/C96HZiFyByU3T6iAzpXn8EGEvct6unm1ZGoed8ByO2oirxgwxBmqKF9haA==
dependencies:
domelementtype "^2.1.0"
domutils@^2.0.0:
version "2.4.4"
resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.4.4.tgz#282739c4b150d022d34699797369aad8d19bbbd3"
integrity sha512-jBC0vOsECI4OMdD0GC9mGn7NXPLb+Qt6KW1YDQzeQYRUFKmNG8lh7mO5HiELfr+lLQE7loDVI4QcAxV80HS+RA==
dependencies:
dom-serializer "^1.0.1"
domelementtype "^2.0.1"
domhandler "^4.0.0"
ecc-jsbn@~0.1.1:
version "0.1.2"
@ -780,11 +795,6 @@ encodeurl@~1.0.2:
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
entities@^1.1.1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56"
integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==
entities@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4"
@ -962,10 +972,10 @@ extsprintf@^1.2.0:
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=
fast-deep-equal@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49"
integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=
fast-deep-equal@^3.1.1:
version "3.1.3"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
fast-json-stable-stringify@^2.0.0:
version "2.0.0"
@ -1145,12 +1155,12 @@ har-schema@^2.0.0:
resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=
har-validator@~5.1.0:
version "5.1.3"
resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080"
integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==
har-validator@~5.1.3:
version "5.1.5"
resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd"
integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==
dependencies:
ajv "^6.5.5"
ajv "^6.12.3"
har-schema "^2.0.0"
has-flag@^3.0.0:
@ -1224,17 +1234,15 @@ htmlencode@^0.0.4:
resolved "https://registry.yarnpkg.com/htmlencode/-/htmlencode-0.0.4.tgz#f7e2d6afbe18a87a78e63ba3308e753766740e3f"
integrity sha1-9+LWr74YqHp45jujMI51N2Z0Dj8=
htmlparser2@^3.10.0:
version "3.10.1"
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f"
integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==
htmlparser2@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-4.1.0.tgz#9a4ef161f2e4625ebf7dfbe6c0a2f52d18a59e78"
integrity sha512-4zDq1a1zhE4gQso/c5LP1OtrhYTncXNSpvJYtWJBtXAETPlMfi3IFNjGuQbYLuVY4ZR0QMqRVvo4Pdy9KLyP8Q==
dependencies:
domelementtype "^1.3.1"
domhandler "^2.3.0"
domutils "^1.5.1"
entities "^1.1.1"
inherits "^2.0.1"
readable-stream "^3.1.1"
domelementtype "^2.0.1"
domhandler "^3.0.0"
domutils "^2.0.0"
entities "^2.0.0"
http-errors@1.7.2:
version "1.7.2"
@ -1282,7 +1290,7 @@ inflight@^1.0.4:
once "^1.3.0"
wrappy "1"
inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3:
inherits@2, inherits@2.0.4, inherits@^2.0.3:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@ -1651,36 +1659,16 @@ locate-path@^3.0.0:
p-locate "^3.0.0"
path-exists "^3.0.0"
lodash.clonedeep@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=
lodash.escaperegexp@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347"
integrity sha1-ZHYsSGGAglGKw99Mz11YhtriA0c=
lodash.isplainobject@^4.0.6:
version "4.0.6"
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=
lodash.isstring@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=
lodash.mergewith@^4.6.1:
version "4.6.2"
resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz#617121f89ac55f59047c7aec1ccd6654c6590f55"
integrity sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==
lodash@4, lodash@^4.17.15:
version "4.17.19"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b"
integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==
lodash@^4.17.19:
version "4.17.20"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
log-symbols@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-3.0.0.tgz#f3a08516a5dea893336a7dee14d18a1cfdab77c4"
@ -1699,12 +1687,12 @@ lowdb@^1.0.0:
pify "^3.0.0"
steno "^0.4.1"
lru-cache@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==
lru-cache@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
dependencies:
yallist "^3.0.2"
yallist "^4.0.0"
make-error@^1.1.1:
version "1.3.5"
@ -1723,24 +1711,24 @@ map-visit@^1.0.0:
dependencies:
object-visit "^1.0.0"
matrix-bot-sdk@^0.5.4:
version "0.5.4"
resolved "https://registry.yarnpkg.com/matrix-bot-sdk/-/matrix-bot-sdk-0.5.4.tgz#8c26bef826bd0b3fc9b693c8d07b52c30d843fd7"
integrity sha512-nUAE1WW7ast+Xsfux1+qBPmKSY6OG0RoZfeh0WfMP42JW6GRnNiY3hq7aiifomnJAMASZ6EwkdHS3ChWpIRYzg==
matrix-bot-sdk@^0.5.9:
version "0.5.9"
resolved "https://registry.yarnpkg.com/matrix-bot-sdk/-/matrix-bot-sdk-0.5.9.tgz#f16018d8f6c3c6a2cd22e42a505973bcdad147a3"
integrity sha512-KN7M/xg+SfgZD809gp/qlgy5sQB84PwpsZRhV2GLNIls2iOG3xes8dVnc9bxX8Cg1ueaIzuILtKdU4CFxcjzyA==
dependencies:
"@types/express" "^4.17.2"
chalk "^3.0.0"
"@types/express" "^4.17.7"
chalk "^4.1.0"
express "^4.17.1"
glob-to-regexp "^0.4.1"
hash.js "^1.1.7"
htmlencode "^0.0.4"
lowdb "^1.0.0"
lru-cache "^5.1.1"
mkdirp "^0.5.1"
morgan "^1.9.1"
request "^2.88.0"
request-promise "^4.2.5"
sanitize-html "^1.20.1"
lru-cache "^6.0.0"
mkdirp "^1.0.4"
morgan "^1.10.0"
request "^2.88.2"
request-promise "^4.2.6"
sanitize-html "^1.27.2"
media-typer@0.3.0:
version "0.3.0"
@ -1850,6 +1838,11 @@ mkdirp@^0.5.1:
dependencies:
minimist "0.0.8"
mkdirp@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
mocha@^7.1.2:
version "7.1.2"
resolved "https://registry.yarnpkg.com/mocha/-/mocha-7.1.2.tgz#8e40d198acf91a52ace122cd7599c9ab857b29e6"
@ -1880,16 +1873,16 @@ mocha@^7.1.2:
yargs-parser "13.1.2"
yargs-unparser "1.6.0"
morgan@^1.9.1:
version "1.9.1"
resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.9.1.tgz#0a8d16734a1d9afbc824b99df87e738e58e2da59"
integrity sha512-HQStPIV4y3afTiCYVxirakhlCfGkI161c76kKFca7Fk1JusM//Qeo1ej2XaMniiNeaZklMVrh3vTtIzpzwbpmA==
morgan@^1.10.0:
version "1.10.0"
resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.10.0.tgz#091778abc1fc47cd3509824653dae1faab6b17d7"
integrity sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==
dependencies:
basic-auth "~2.0.0"
basic-auth "~2.0.1"
debug "2.6.9"
depd "~1.1.2"
depd "~2.0.0"
on-finished "~2.3.0"
on-headers "~1.0.1"
on-headers "~1.0.2"
ms@2.0.0:
version "2.0.0"
@ -1941,11 +1934,6 @@ normalize-path@^3.0.0, normalize-path@~3.0.0:
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
number-is-nan@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=
oauth-sign@~0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
@ -2009,7 +1997,7 @@ on-finished@~2.3.0:
dependencies:
ee-first "1.1.1"
on-headers@~1.0.1:
on-headers@~1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f"
integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==
@ -2040,6 +2028,11 @@ p-try@^2.0.0:
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
parse-srcset@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/parse-srcset/-/parse-srcset-1.0.2.tgz#f2bd221f6cc970a938d88556abc589caaaa2bde1"
integrity sha1-8r0iH2zJcKk42IVWq8WJyqqiveE=
parseurl@~1.3.3:
version "1.3.3"
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
@ -2090,10 +2083,10 @@ posix-character-classes@^0.1.0:
resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=
postcss@^7.0.5:
version "7.0.18"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.18.tgz#4b9cda95ae6c069c67a4d933029eddd4838ac233"
integrity sha512-/7g1QXXgegpF+9GJj4iN7ChGF40sYuGYJ8WZu8DZWnmhQ/G36hfdk3q9LBJmoK+lZ+yzZ5KYpOoxq7LF1BxE8g==
postcss@^7.0.27:
version "7.0.35"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.35.tgz#d2be00b998f7f211d8a276974079f2e92b970e24"
integrity sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==
dependencies:
chalk "^2.4.2"
source-map "^0.6.1"
@ -2127,16 +2120,11 @@ proxy-addr@~2.0.5:
forwarded "~0.1.2"
ipaddr.js "1.9.0"
psl@^1.1.24, psl@^1.1.28:
psl@^1.1.28:
version "1.4.0"
resolved "https://registry.yarnpkg.com/psl/-/psl-1.4.0.tgz#5dd26156cdb69fa1fdb8ab1991667d3f80ced7c2"
integrity sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw==
punycode@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
integrity sha1-wNWmOycYgArY4esPpSachN1BhF4=
punycode@^2.1.0, punycode@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
@ -2177,15 +2165,6 @@ react-is@^16.8.4:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.11.0.tgz#b85dfecd48ad1ce469ff558a882ca8e8313928fa"
integrity sha512-gbBVYR2p8mnriqAwWx9LbuUrShnAuSCNnuPGyc7GJrMVQtPDAh8iLpv7FRuMPFb56KkaVZIYSz1PrjI9q0QPCw==
readable-stream@^3.1.1:
version "3.4.0"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.4.0.tgz#a51c26754658e0a3c21dbf59163bd45ba6f447fc"
integrity sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==
dependencies:
inherits "^2.0.3"
string_decoder "^1.1.1"
util-deprecate "^1.0.1"
readdirp@~3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.2.0.tgz#c30c33352b12c96dfb4b895421a49fd5a9593839"
@ -2211,27 +2190,27 @@ repeat-string@^1.6.1:
resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc=
request-promise-core@1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.3.tgz#e9a3c081b51380dfea677336061fea879a829ee9"
integrity sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==
request-promise-core@1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.4.tgz#3eedd4223208d419867b78ce815167d10593a22f"
integrity sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==
dependencies:
lodash "^4.17.15"
lodash "^4.17.19"
request-promise@^4.2.5:
version "4.2.5"
resolved "https://registry.yarnpkg.com/request-promise/-/request-promise-4.2.5.tgz#186222c59ae512f3497dfe4d75a9c8461bd0053c"
integrity sha512-ZgnepCykFdmpq86fKGwqntyTiUrHycALuGggpyCZwMvGaZWgxW6yagT0FHkgo5LzYvOaCNvxYwWYIjevSH1EDg==
request-promise@^4.2.6:
version "4.2.6"
resolved "https://registry.yarnpkg.com/request-promise/-/request-promise-4.2.6.tgz#7e7e5b9578630e6f598e3813c0f8eb342a27f0a2"
integrity sha512-HCHI3DJJUakkOr8fNoCc73E5nU5bqITjOYFMDrKHYOXWXrgD/SBaC7LjwuPymUprRyuF06UK7hd/lMHkmUXglQ==
dependencies:
bluebird "^3.5.0"
request-promise-core "1.1.3"
request-promise-core "1.1.4"
stealthy-require "^1.1.1"
tough-cookie "^2.3.3"
request@^2.88.0:
version "2.88.0"
resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef"
integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==
request@^2.88.2:
version "2.88.2"
resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==
dependencies:
aws-sign2 "~0.7.0"
aws4 "^1.8.0"
@ -2240,7 +2219,7 @@ request@^2.88.0:
extend "~3.0.2"
forever-agent "~0.6.1"
form-data "~2.3.2"
har-validator "~5.1.0"
har-validator "~5.1.3"
http-signature "~1.2.0"
is-typedarray "~1.0.0"
isstream "~0.1.2"
@ -2250,7 +2229,7 @@ request@^2.88.0:
performance-now "^2.1.0"
qs "~6.5.2"
safe-buffer "^5.1.2"
tough-cookie "~2.4.3"
tough-cookie "~2.5.0"
tunnel-agent "^0.6.0"
uuid "^3.3.2"
@ -2286,7 +2265,7 @@ safe-buffer@5.1.2:
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0:
safe-buffer@^5.0.1, safe-buffer@^5.1.2:
version "5.2.0"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519"
integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==
@ -2303,21 +2282,15 @@ safe-regex@^1.1.0:
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
sanitize-html@^1.20.1:
version "1.20.1"
resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-1.20.1.tgz#f6effdf55dd398807171215a62bfc21811bacf85"
integrity sha512-txnH8TQjaQvg2Q0HY06G6CDJLVYCpbnxrdO0WN8gjCKaU5J0KbyGYhZxx5QJg3WLZ1lB7XU9kDkfrCXUozqptA==
sanitize-html@^1.27.2:
version "1.27.5"
resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-1.27.5.tgz#6c8149462adb23e360e1bb71cc0bae7f08c823c7"
integrity sha512-M4M5iXDAUEcZKLXkmk90zSYWEtk5NH3JmojQxKxV371fnMh+x9t1rqdmXaGoyEHw3z/X/8vnFhKjGL5xFGOJ3A==
dependencies:
chalk "^2.4.1"
htmlparser2 "^3.10.0"
lodash.clonedeep "^4.5.0"
lodash.escaperegexp "^4.1.2"
lodash.isplainobject "^4.0.6"
lodash.isstring "^4.0.1"
lodash.mergewith "^4.6.1"
postcss "^7.0.5"
srcset "^1.0.0"
xtend "^4.0.1"
htmlparser2 "^4.1.0"
lodash "^4.17.15"
parse-srcset "^1.0.2"
postcss "^7.0.27"
semver@^5.3.0, semver@^5.7.0:
version "5.7.1"
@ -2459,14 +2432,6 @@ sprintf-js@~1.0.2:
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
srcset@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/srcset/-/srcset-1.0.0.tgz#a5669de12b42f3b1d5e83ed03c71046fc48f41ef"
integrity sha1-pWad4StC87HV6D7QPHEEb8SPQe8=
dependencies:
array-uniq "^1.0.2"
number-is-nan "^1.0.0"
sshpk@^1.7.0:
version "1.16.1"
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877"
@ -2552,13 +2517,6 @@ string.prototype.trimright@^2.1.0:
define-properties "^1.1.3"
function-bind "^1.1.1"
string_decoder@^1.1.1:
version "1.3.0"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
dependencies:
safe-buffer "~5.2.0"
strip-ansi@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f"
@ -2648,7 +2606,7 @@ toidentifier@1.0.0:
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
tough-cookie@^2.3.3:
tough-cookie@^2.3.3, tough-cookie@~2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==
@ -2656,14 +2614,6 @@ tough-cookie@^2.3.3:
psl "^1.1.28"
punycode "^2.1.1"
tough-cookie@~2.4.3:
version "2.4.3"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781"
integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==
dependencies:
psl "^1.1.24"
punycode "^1.4.1"
ts-mocha@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/ts-mocha/-/ts-mocha-7.0.0.tgz#f1549b48b46f53d7ae1dccbb26313c7879acb190"
@ -2798,11 +2748,6 @@ use@^3.1.0:
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==
util-deprecate@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
utils-merge@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
@ -2860,30 +2805,17 @@ wrappy@1:
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
xtend@^4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
y18n@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b"
integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==
yallist@^3.0.2:
version "3.0.3"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9"
integrity sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==
yallist@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
yargs-parser@13.1.2, yargs-parser@^13.1.1:
version "13.1.2"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38"
integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==
dependencies:
camelcase "^5.0.0"
decamelize "^1.2.0"
yargs-parser@^13.1.2:
yargs-parser@13.1.2, yargs-parser@^13.1.1, yargs-parser@^13.1.2:
version "13.1.2"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38"
integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==