diff --git a/.gitea/workflows/git-to-ptero.yaml b/.gitea/workflows/git-to-ptero.yaml
index 3489c64..3ff6e79 100644
--- a/.gitea/workflows/git-to-ptero.yaml
+++ b/.gitea/workflows/git-to-ptero.yaml
@@ -34,6 +34,17 @@ on:
#
# docs/deployment.md carries the design and the rollback procedure.
+# Least privilege (CI-003). This job never writes back to the repository; it
+# holds the SSH key to the production node, which makes it the most valuable
+# job in either forge to compromise.
+permissions:
+ contents: read
+
+# Actions pinned to a commit, version in the comment. A tag is a moving
+# pointer, and moving `v4` here means running arbitrary code in the job that
+# holds that key. If the Gitea runner ever fails to resolve a commit ref, it
+# fails on the checkout step — loudly, like the `@v7` that did not exist.
+
jobs:
deploy-to-sftp:
runs-on: ubuntu-latest
@@ -42,10 +53,10 @@ jobs:
# Was @v7, which does not exist (latest major is v5): the workflow
# failed on its very first step.
- name: Checkout repository
- uses: actions/checkout@v4
+ uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- name: Set up Python
- uses: actions/setup-python@v5
+ uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: '3.12'
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c6fce70..95d343e 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -15,6 +15,12 @@ concurrency:
permissions:
contents: read
+# Third-party actions are pinned to a commit, with the version in a comment
+# (CI-003). A tag is a moving pointer: whoever can move `v4` runs code in a
+# job that holds this repository's token. The comment is what makes the pin
+# maintainable — a bare 40-character hash tells a reader nothing about
+# whether it is current. Dependabot updates both together.
+
env:
PYTHON_VERSION: '3.12'
@@ -23,10 +29,10 @@ jobs:
name: Security Audit
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- name: Set up Python
- uses: actions/setup-python@v5
+ uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: ${{ env.PYTHON_VERSION }}
cache: 'pip'
@@ -46,10 +52,10 @@ jobs:
name: Lint with Ruff
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- name: Set up Python
- uses: actions/setup-python@v5
+ uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: ${{ env.PYTHON_VERSION }}
@@ -71,10 +77,10 @@ jobs:
name: Security Scan
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- name: Set up Python
- uses: actions/setup-python@v5
+ uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: ${{ env.PYTHON_VERSION }}
cache: 'pip'
@@ -96,10 +102,10 @@ jobs:
runs-on: ubuntu-latest
needs: [security-audit, lint]
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- name: Set up Python
- uses: actions/setup-python@v5
+ uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: ${{ env.PYTHON_VERSION }}
cache: 'pip'
diff --git a/README.md b/README.md
index f49ee07..1c512a2 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@ de la barre latérale (voir `docs/translations.md`).
```bash
python -m venv .venv
.venv/Scripts/pip install -r requirements.txt -r requirements-dev.txt
-cp app/.env.exemple .env # puis remplir les valeurs
+cp app/.env.example .env # puis remplir SECRET_KEY et DATABASE_URL
.venv/Scripts/python run.py # développement, http://127.0.0.1:5000
```
diff --git a/app/.env.example b/app/.env.example
new file mode 100644
index 0000000..e6cc4d2
--- /dev/null
+++ b/app/.env.example
@@ -0,0 +1,115 @@
+# Team Tryouts — environment variables
+#
+# Copy to .env and fill in. Every value here is a PRODUCTION-SAFE default:
+# copying this file and changing nothing gives a locked-down configuration
+# that refuses to start until the two required secrets are set, rather than
+# a working one that happens to be wide open (OPS-003).
+#
+# The previous version shipped FLASK_DEBUG=true under a heading that said
+# "fill in the values for production". The Werkzeug debugger executes code
+# submitted through the browser, so that one line turned a copy-paste into a
+# remote shell.
+#
+# For local development, see the DEVELOPMENT block at the bottom.
+
+# =============================================================================
+# Required — the application refuses to start without these
+# =============================================================================
+
+# Generate with: python -c "import secrets; print(secrets.token_hex(32))"
+# Never reuse one between environments: this key signs session cookies, so
+# whoever holds it can forge a session for any account.
+SECRET_KEY=
+
+# Expected form: postgresql://user:password@host:5432/database
+# The psycopg 3 driver is named for you by create_app(); postgresql:// alone
+# would send SQLAlchemy looking for psycopg2, which is not installed.
+DATABASE_URL=
+
+# =============================================================================
+# Security — these defaults assume HTTPS in front. Do not relax them on a
+# deployed instance.
+# =============================================================================
+
+# Session cookies are only sent over HTTPS.
+SESSION_COOKIE_SECURE=true
+
+# Plain HTTP is redirected to HTTPS.
+FORCE_HTTPS=true
+
+# The Werkzeug debugger is a remote code execution primitive by design.
+# Never true on anything reachable from a network you do not control.
+FLASK_DEBUG=false
+
+# Inline
+{# The stylesheet that used to sit here — index.global.min.css — does not
+ exist. FullCalendar 6 bundles its styles into the JS, and that file is not
+ in the published package: the link had been answering 404 on every calendar
+ load since the upgrade. A failed stylesheet is silent in the browser, which
+ is why it survived.
+
+ Integrity pins the bundle (QUA-004): this file is executable script from a
+ third party, on the page that shows every match in the club. See the note
+ in layouts/base.html for what SRI does and does not promise. #}
+