Harden: cap request body size (MAX_CONTENT_LENGTH)
Provisioning bodies are tiny; set MAX_CONTENT_LENGTH=16 KiB so oversized POSTs are rejected with 413 rather than buffered. Tests: +1; 99 pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
7c71ab1def
commit
c33843d1ec
|
|
@ -55,6 +55,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
- The in-memory profile cache is guarded by a lock (thread-safe under a
|
- The in-memory profile cache is guarded by a lock (thread-safe under a
|
||||||
threaded server), and the captive portal disables the activate button while
|
threaded server), and the captive portal disables the activate button while
|
||||||
a request is in flight to prevent double provisioning.
|
a request is in flight to prevent double provisioning.
|
||||||
|
- Request bodies are capped (`MAX_CONTENT_LENGTH` 16 KiB); oversized POSTs get a
|
||||||
|
413 instead of being buffered.
|
||||||
- `setup_wifi_ap.sh`: the deployed `config.yaml` (holds the OP key) is now
|
- `setup_wifi_ap.sh`: the deployed `config.yaml` (holds the OP key) is now
|
||||||
`chmod 600` and owned by the service user instead of world-readable, and
|
`chmod 600` and owned by the service user instead of world-readable, and
|
||||||
`data/` is chowned *after* `init_db` so the root-created SQLite file is
|
`data/` is chowned *after* `init_db` so the root-created SQLite file is
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,9 @@ def create_app(config_path: str = CONFIG_PATH) -> Flask:
|
||||||
portal_dir = os.path.abspath(portal_dir)
|
portal_dir = os.path.abspath(portal_dir)
|
||||||
|
|
||||||
app = Flask(__name__, static_folder=portal_dir, static_url_path="")
|
app = Flask(__name__, static_folder=portal_dir, static_url_path="")
|
||||||
|
# Provisioning bodies are tiny (a MAC address); cap request size so oversized
|
||||||
|
# POSTs are rejected with 413 instead of being buffered.
|
||||||
|
app.config["MAX_CONTENT_LENGTH"] = 16 * 1024
|
||||||
|
|
||||||
db_path = cfg["database"]["path"]
|
db_path = cfg["database"]["path"]
|
||||||
ensure_parent_dir(db_path)
|
ensure_parent_dir(db_path)
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,12 @@ class TestProvision:
|
||||||
r = client.post("/api/provision", json={"mac_address": "not-a-mac"})
|
r = client.post("/api/provision", json={"mac_address": "not-a-mac"})
|
||||||
assert r.status_code == 400
|
assert r.status_code == 400
|
||||||
|
|
||||||
|
def test_oversized_body_rejected(self, client):
|
||||||
|
big = '{"mac_address":"' + "a" * (17 * 1024) + '"}'
|
||||||
|
r = client.post("/api/provision", data=big,
|
||||||
|
content_type="application/json")
|
||||||
|
assert r.status_code == 413
|
||||||
|
|
||||||
def test_rate_limited_second_request(self, client):
|
def test_rate_limited_second_request(self, client):
|
||||||
client.post("/api/provision", json={"mac_address": "de:ad:be:ef:00:01"})
|
client.post("/api/provision", json={"mac_address": "de:ad:be:ef:00:01"})
|
||||||
r = client.post("/api/provision", json={"mac_address": "de:ad:be:ef:00:01"})
|
r = client.post("/api/provision", json={"mac_address": "de:ad:be:ef:00:01"})
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user