Security hardening, multi-site portability, and a documentation and API-surface sweep
This commit is contained in:
@@ -48,7 +48,16 @@ from .plugins import plugin_manager
|
||||
# the literal values of C:\Enrollment\display-type.txt - so a plugin holding its
|
||||
# own copy of that map (geenforce did) can read core's instead of drifting from
|
||||
# it. Additive names on the import surface, minor bump.
|
||||
__contract_version__ = '0.18.0'
|
||||
# 0.19.0: get_dashboard_widgets returns DATA AND SHAPE, not a component name.
|
||||
# The old shape ('name' + 'component' + 'size') named a Vue component per
|
||||
# widget, which cannot survive a lean build - a plugin's component may never be
|
||||
# staged into the frontend bundle (ADR-013) - and five plugins were declaring
|
||||
# widgets that pointed at components nobody had written. Core now owns the
|
||||
# renderers and a card declares id / title / endpoint / render / severity /
|
||||
# permission / empty / position. BREAKING for any plugin still using the old
|
||||
# shape, which is why it is recorded here: the change itself shipped earlier
|
||||
# without a bump, and a contract that changes silently is not a contract.
|
||||
__contract_version__ = '0.19.0'
|
||||
|
||||
# Product release version (see ADR-007). The product version and the
|
||||
# plugin-contract version above are distinct series with independent
|
||||
@@ -134,6 +143,41 @@ def create_app(config_name: str = None) -> Flask:
|
||||
identity = jwt_data["sub"]
|
||||
return db.session.get(User, int(identity))
|
||||
|
||||
@app.after_request
|
||||
def apply_security_headers(response):
|
||||
"""Baseline response headers. The app shipped with none of these.
|
||||
|
||||
Deliberately the three that cost nothing and break nothing:
|
||||
|
||||
nosniff a response whose bytes and declared type disagree is
|
||||
not re-typed by the browser into something executable.
|
||||
This is the general form of the upload problem that
|
||||
shopdb/utils/uploads.py addresses per-file.
|
||||
frame-ancestors
|
||||
same-origin only. The kiosks open routes directly
|
||||
rather than framing them, so this costs the fleet
|
||||
nothing and stops the UI being framed elsewhere and
|
||||
clicked through. Sent as X-Frame-Options too, because
|
||||
the display bays run browsers old enough to want it.
|
||||
Referrer-Policy
|
||||
an asset id or a hostname in a path is not handed to
|
||||
whatever a user clicks through to.
|
||||
|
||||
NOT a full page CSP. This app serves an SPA with inline styles, so a
|
||||
real script-src policy is a change worth making on its own with the
|
||||
frontend in front of you - claiming one here by adding a permissive
|
||||
header would be worse than having none.
|
||||
"""
|
||||
response.headers.setdefault('X-Content-Type-Options', 'nosniff')
|
||||
response.headers.setdefault('X-Frame-Options', 'SAMEORIGIN')
|
||||
response.headers.setdefault('Referrer-Policy',
|
||||
'strict-origin-when-cross-origin')
|
||||
# setdefault: an upload response has already declared its own, stricter
|
||||
# Content-Security-Policy, and this must not weaken it.
|
||||
response.headers.setdefault('Content-Security-Policy',
|
||||
"frame-ancestors 'self'")
|
||||
return response
|
||||
|
||||
return app
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user