From cb472a193842d434e0f74c547c10325928a79aa7 Mon Sep 17 00:00:00 2001 From: Esteban Carisimo <32495768+estcarisimo@users.noreply.github.com> Date: Mon, 28 Sep 2026 19:54:48 -0500 Subject: [PATCH 1/3] WIP: repo hardening (icons, link fixes, pinned workflows, link check, lockfile) Not pushed. Remaining: zizmor adhoc-packages finding, CODEOWNERS, dependabot weekly groups, pre-commit, .editorconfig, REVIEW.md, docs, excluding lychee.toml from the build, local test, PR + fresh review. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/build.yml | 49 ++++-- .github/workflows/deploy.yml | 14 +- .github/workflows/link-check.yml | 78 ++++++++++ .github/workflows/security.yml | 57 +++++++ .gitignore | 1 - AGENTS.md | 4 + Gemfile.lock | 227 ++++++++++++++++++++++++++++ _bibliography/conexdat.bib | 24 ++- _config.yml | 6 +- _data/highlights.yml | 3 +- _data/members.yml | 4 +- _data/research_areas.yml | 6 +- _includes/head/custom.html | 9 +- _includes/people-list.html | 15 +- _plugins/people_photos.rb | 11 ++ images/apple-touch-icon-180x180.png | Bin 0 -> 9057 bytes images/favicon-192x192.png | Bin 0 -> 9854 bytes images/favicon-32x32.png | Bin 0 -> 959 bytes images/favicon.ico | Bin 318 -> 15086 bytes images/icon-512x512.png | Bin 0 -> 41241 bytes images/manifest.json | 12 ++ lychee.toml | 22 +++ 22 files changed, 494 insertions(+), 48 deletions(-) create mode 100644 .github/workflows/link-check.yml create mode 100644 .github/workflows/security.yml create mode 100644 Gemfile.lock create mode 100644 _plugins/people_photos.rb create mode 100644 images/apple-touch-icon-180x180.png create mode 100644 images/favicon-192x192.png create mode 100644 images/favicon-32x32.png create mode 100644 images/icon-512x512.png create mode 100644 images/manifest.json create mode 100644 lychee.toml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 01151e6..d7ea350 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,5 +1,7 @@ name: Build site +# Required check on every pull request (ruleset "Protect main"): build the site +# exactly as deploy.yml does, then check the output. on: pull_request: workflow_dispatch: @@ -16,26 +18,36 @@ jobs: name: Build site runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - ruby-version: "3.3" - bundler-cache: true + persist-credentials: false - name: Check publication topic ids run: | # Every id in a `topics = {...}` field must exist in # _data/publication_topics.yml; an unknown id renders no chip silently. + fields=$(grep -ciE '^[[:space:]]*topics[[:space:]]*=' _bibliography/conexdat.bib || true) + if [ "$fields" -eq 0 ]; then + echo "::error::No topics fields found in _bibliography/conexdat.bib"; exit 1 + fi known=$(sed -n 's/^- id: *//p' _data/publication_topics.yml | sort -u) - used=$(grep -oE '^[[:space:]]*topics[[:space:]]*=[[:space:]]*\{[^}]*\}' _bibliography/conexdat.bib \ + used=$(grep -oiE '^[[:space:]]*topics[[:space:]]*=[[:space:]]*\{[^}]*\}' _bibliography/conexdat.bib \ | sed -E 's/.*\{(.*)\}/\1/' | tr ',' '\n' | tr -d ' ' | sed '/^$/d' | sort -u) + matched=$(grep -ciE '^[[:space:]]*topics[[:space:]]*=[[:space:]]*\{[^}]*\}' _bibliography/conexdat.bib || true) + if [ "$matched" -ne "$fields" ]; then + echo "::error::$((fields - matched)) topics field(s) are not on one line as topics = {id1, id2}"; exit 1 + fi unknown=$(comm -13 <(echo "$known") <(echo "$used")) if [ -n "$unknown" ]; then echo "::error::Unknown publication topic id(s): $unknown"; exit 1 fi - echo "OK: $(echo "$used" | wc -l) topic ids used, all known" + echo "OK: $fields entries tagged, $(echo "$used" | wc -l) topic ids used, all known" + + - name: Set up Ruby + uses: ruby/setup-ruby@14594264cd68ce8a2345dd349bc3d138a4ef85c8 # v1.327.0 + with: + ruby-version: "3.3" + bundler-cache: true - name: Build site (Jekyll + polyglot + scholar) env: @@ -57,12 +69,29 @@ jobs: # entries (the filter splits on whitespace). grep -q 'data-topic="latam"' _site/publications/index.html grep -Eq 'data-topics="[a-z-]+ +[a-z-]+' _site/en/publications/index.html - # Group decision: no e-mail addresses anywhere on the site. + # Group decision: no e-mail addresses in any HTML page. if grep -rl 'mailto:' _site --include='*.html'; then echo "::error::mailto: link found (the site publishes no e-mail addresses)"; exit 1 fi # Repository docs must not be published with the site. - for f in AGENTS.md CLAUDE.md CONTRIBUTING.md CODE_OF_CONDUCT.md SECURITY.md; do + for f in AGENTS.md CLAUDE.md CONTRIBUTING.md CODE_OF_CONDUCT.md SECURITY.md lychee.toml; do test ! -e "_site/$f" done echo "OK: $(find _site -name '*.html' | wc -l) HTML files" + + # Every internal link and asset must resolve inside the build. Absolute + # links to this site are mapped to the local files; external links are + # checked weekly by link-check.yml instead, so a PR never fails because + # some other server is down. + - name: Check internal links + uses: lycheeverse/lychee-action@e7477775783ea5526144ba13e8db5eec57747ce8 # v2.9.0 + with: + args: >- + --config lychee.toml --offline + --root-dir ${{ github.workspace }}/_site + --remap 'https://conexdat\.github\.io/(.*) file://${{ github.workspace }}/_site/$1' + --remap 'https://conexdat\.github\.io$ file://${{ github.workspace }}/_site/' + --exclude '/_site/LaNet-vi/' + '_site/**/*.html' + fail: true + jobSummary: true diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 9146441..0696126 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -5,8 +5,7 @@ on: branches: [main] workflow_dispatch: -permissions: - contents: write +permissions: {} concurrency: group: pages @@ -14,12 +13,17 @@ concurrency: jobs: build-deploy: + name: Build and deploy runs-on: ubuntu-latest + permissions: + contents: write # push the built site to the gh-pages branch steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Set up Ruby - uses: ruby/setup-ruby@v1 + uses: ruby/setup-ruby@14594264cd68ce8a2345dd349bc3d138a4ef85c8 # v1.327.0 with: ruby-version: "3.3" bundler-cache: true @@ -30,7 +34,7 @@ jobs: run: bundle exec jekyll build --trace - name: Deploy to gh-pages - uses: peaceiris/actions-gh-pages@v4 + uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4.1.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_branch: gh-pages diff --git a/.github/workflows/link-check.yml b/.github/workflows/link-check.yml new file mode 100644 index 0000000..d40d397 --- /dev/null +++ b/.github/workflows/link-check.yml @@ -0,0 +1,78 @@ +name: Link check + +# Weekly check of every link on the site, internal and external. Broken links +# are reported in a single open issue labeled "broken-link", which is updated +# each week and closed automatically once everything passes again. +on: + schedule: + - cron: "41 7 * * 1" # Mondays 07:41 UTC + workflow_dispatch: + +permissions: {} + +concurrency: + group: link-check + cancel-in-progress: true + +jobs: + links: + name: Check links + runs-on: ubuntu-latest + permissions: + contents: read + issues: write # open, update or close the broken-link report + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Set up Ruby + uses: ruby/setup-ruby@14594264cd68ce8a2345dd349bc3d138a4ef85c8 # v1.327.0 + with: + ruby-version: "3.3" + bundler-cache: true + + - name: Build site + env: + JEKYLL_ENV: production + run: bundle exec jekyll build + + - name: Check links + id: lychee + uses: lycheeverse/lychee-action@e7477775783ea5526144ba13e8db5eec57747ce8 # v2.9.0 + with: + args: >- + --config lychee.toml + --root-dir ${{ github.workspace }}/_site + '_site/**/*.html' + output: lychee-report.md + format: markdown + fail: false + jobSummary: true + + - name: Open, update or close the broken-link issue + env: + GH_TOKEN: ${{ github.token }} + EXIT_CODE: ${{ steps.lychee.outputs.exit_code }} + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + run: | + existing=$(gh issue list --label broken-link --state open --json number --jq '.[0].number // empty') + if [ "$EXIT_CODE" != "0" ]; then + { + echo "The weekly link check found broken links on https://conexdat.github.io." + echo "Fix them in the source (see AGENTS.md: never link into cnet.fi.uba.ar;" + echo "prefer the new home of a project or a Wayback Machine snapshot)." + echo + echo "Run: $RUN_URL" + echo + cat lychee-report.md + } > issue-body.md + if [ -n "$existing" ]; then + gh issue edit "$existing" --body-file issue-body.md + gh issue comment "$existing" --body "Still failing as of $(date -u +%F). Report updated: $RUN_URL" + else + gh issue create --title "Broken links on the website" --label broken-link --body-file issue-body.md + fi + elif [ -n "$existing" ]; then + gh issue close "$existing" --comment "All links pass as of $(date -u +%F): $RUN_URL" + fi diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml new file mode 100644 index 0000000..6eb115f --- /dev/null +++ b/.github/workflows/security.yml @@ -0,0 +1,57 @@ +name: Security + +# Two audits, like the other CoNexDat repositories: +# - bundler-audit: the locked gems (Gemfile.lock) against the Ruby advisory +# database. Runs when the lockfile changes and weekly, so advisories +# published after the last lock change still surface. +# - zizmor: static analysis of the workflow files themselves (unpinned +# actions, credential persistence, excessive permissions, template injection). +on: + push: + branches: [main] + paths: ["Gemfile", "Gemfile.lock", ".github/workflows/**"] + pull_request: + paths: ["Gemfile", "Gemfile.lock", ".github/workflows/**"] + schedule: + - cron: "23 6 * * 1" # Mondays 06:23 UTC + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: security-${{ github.ref }} + cancel-in-progress: true + +jobs: + audit: + name: bundler-audit + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Set up Ruby + uses: ruby/setup-ruby@14594264cd68ce8a2345dd349bc3d138a4ef85c8 # v1.327.0 + with: + ruby-version: "3.3" + + - name: Audit locked gems + run: | + gem install --no-document bundler-audit + bundle-audit check --update + + workflows: + name: zizmor + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Install uv + uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0 + + - name: Lint workflows + run: uvx zizmor --min-severity low .github/workflows/ diff --git a/.gitignore b/.gitignore index bffd200..36f6518 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,6 @@ _site/ .sass-cache/ .bundle/ /vendor/ -Gemfile.lock .DS_Store *~ node_modules/ diff --git a/AGENTS.md b/AGENTS.md index 94398ac..2b65dcb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -94,6 +94,10 @@ hard-codes that origin for CSS and assets. - **No `//` comments inside inline `