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. #} +