add module .dorks and flask.harden()

This commit is contained in:
Yusur 2025-07-17 21:33:11 +02:00
parent ee36616b43
commit e5ca63953d
13 changed files with 65 additions and 14 deletions

1
.gitignore vendored
View file

@ -24,3 +24,4 @@ dist/
.err
.vscode
/run.sh
ROADMAP.md

View file

@ -5,6 +5,7 @@
+ Added `ValueProperty`, abstract superclass for `ConfigProperty`
+ \[BREAKING] Changed the behavior of `makelist()`: now it's also a decorator, converting its return type to a list (revertable with `wrap=False`)
+ New module `lex` with functions `symbol_table()` and `lex()` — make tokenization more affordable
+ Add `dorks` module and `flask.harden()`
+ Added `addattr()`
## 0.3.6

View file

@ -26,6 +26,7 @@ from .classtools import Wanted, Incomplete
from .itertools import makelist, kwargs_prefix, ltuple, rtuple, additem
from .i18n import I18n, JsonI18n, TomlI18n
from .snowflake import Snowflake, SnowflakeGen
from .lex import symbol_table, lex, ilex
__version__ = "0.4.0-dev28"
@ -36,7 +37,7 @@ __all__ = (
'SiqType', 'Snowflake', 'SnowflakeGen', 'StringCase', 'TomlI18n', 'Wanted',
'additem', 'b2048decode', 'b2048encode', 'b32ldecode', 'b32lencode',
'b64encode', 'b64decode', 'cb32encode', 'cb32decode', 'count_ones',
'deprecated', 'join_bits', 'jsonencode', 'kwargs_prefix', 'ltuple',
'deprecated', 'ilex', 'join_bits', 'jsonencode', 'kwargs_prefix', 'lex', 'ltuple',
'makelist', 'mask_shift', 'not_implemented', 'rtuple', 'split_bits',
'ssv_list', 'want_bytes', 'want_str'
'ssv_list', 'symbol_table', 'want_bytes', 'want_str'
)

28
src/suou/dorks.py Normal file
View file

@ -0,0 +1,28 @@
"""
Web app hardening and PT utilities.
---
Copyright (c) 2025 Sakuragasaki46.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
See LICENSE for the specific language governing permissions and
limitations under the License.
This software is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
"""
SENSITIVE_ENDPOINTS = """
/.git
/.gitignore
/node_modules
/wp-admin
/wp-login.php
/.ht
/package.json
/package-lock.json
/composer.
""".split()

View file

@ -14,8 +14,6 @@ This software is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
"""
from .functools import deprecated
class MissingConfigError(LookupError):
"""
Config variable not found.
@ -42,3 +40,7 @@ class InconsistencyError(RuntimeError):
"""
This program is in a state which it's not supposed to be in.
"""
__all__ = (
'MissingConfigError', 'MissingConfigWarning', 'LexError', 'InconsistencyError'
)

View file

@ -15,9 +15,10 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
"""
from typing import Any
from flask import Flask, current_app, g, request
from flask import Flask, abort, current_app, g, request
from .i18n import I18n
from .configparse import ConfigOptions
from .dorks import SENSITIVE_ENDPOINTS
def add_context_from_config(app: Flask, config: ConfigOptions) -> Flask:
@ -66,6 +67,21 @@ def get_flask_conf(key: str, default = None, *, app: Flask | None = None) -> Any
app = current_app
return app.config.get(key, default)
__all__ = ('add_context_from_config', 'add_i18n', 'get_flask_conf')
## XXX UNTESTED!
def harden(app: Flask):
"""
Make common "dork" endpoints unavailable
"""
i = 1
for ep in SENSITIVE_ENDPOINTS:
@app.route(f'{ep}<path:rest>', name=f'unavailable_{i}')
def unavailable(rest):
abort(403)
i += 1
return app
# Optional dependency: do not import into __init__.py
__all__ = ('add_context_from_config', 'add_i18n', 'get_flask_conf', 'harden')

View file

@ -74,5 +74,5 @@ class Api(_Api):
super().__init__(*a, **ka)
self.representations['application/json'] = output_json
# Optional dependency: do not import into __init__.py
__all__ = ('Api',)

View file

@ -76,5 +76,5 @@ def require_auth(cls: type[DeclarativeBase], db: SQLAlchemy) -> Callable[Any, Ca
return auth_required
# Optional dependency: do not import into __init__.py
__all__ = ('require_auth', )

View file

@ -15,14 +15,14 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
'''
from functools import wraps
from typing import Any, Iterable, MutableMapping, TypeVar
from typing import Any, Callable, Iterable, MutableMapping, TypeVar
import warnings
from suou.classtools import MISSING
_T = TypeVar('_T')
def makelist(l: Any, *, wrap: bool = True) -> list:
def makelist(l: Any, *, wrap: bool = True) -> list | Callable[Any, list]:
'''
Make a list out of an iterable or a single value.

View file

@ -52,6 +52,7 @@ def symbol_table(*args: Iterable[tuple | TokenSym], whitespace: str | None = Non
yield TokenSym('[' + re.escape(whitespace) + ']+', '', discard=True)
symbol_table: Callable[..., list]
def ilex(text: str, table: Iterable[TokenSym], *, whitespace = False):
"""
@ -80,5 +81,6 @@ def ilex(text: str, table: Iterable[TokenSym], *, whitespace = False):
raise InconsistencyError
i = mo.end(0)
lex = makelist(ilex)
lex: Callable[..., list] = makelist(ilex)
__all__ = ('symbol_table', 'lex', 'ilex')

View file

@ -117,6 +117,6 @@ class SiqField(Field):
def python_value(self, value: bytes) -> Siq:
return Siq.from_bytes(value)
# Optional dependency: do not import into __init__.py
__all__ = ('connect_reconnect', 'RegexCharField', 'SiqField')

View file

@ -295,7 +295,7 @@ def require_auth_base(cls: type[DeclarativeBase], *, src: AuthSrc, column: str |
return wrapper
return decorator
# Optional dependency: do not import into __init__.py
__all__ = (
'IdType', 'id_column', 'entity_base', 'declarative_base', 'token_signer', 'match_column', 'match_constraint',
'author_pair', 'age_pair', 'require_auth_base', 'want_column'

View file

@ -1,5 +1,5 @@
"""
Utilities for marshmallow, a schema-agnostic serializer/deserializer.
Miscellaneous validator closures.
---