Automation runs as ForkMesh instance Actions from .forkmesh/*.yml on the instance's own runner.
These files replace .github/workflows/ (the GitHub copy is a read-only mirror with Actions off) and the
scheduled jobs of the per-project .gitlab-ci.yml files.
The runner differs from GitHub Actions in a few ways:
uses:steps are ignored, so every step is arun:step. The workspace is already a full clone.- Toolchains are git, curl, jq, Node.js 22 with npm, and Debian Python 3.13. Python steps make a
virtualenv (
python3 -m venv .venv). Projects that pinned 3.11 or 3.12 run on 3.13. Flutter, Java, Ruby, Rust and Chromium are not installed, and there is no sudo. - Jobs that commit set
permissions: contents: writeand callscripts/actions/commit-back.sh. It stages only the named paths and skips the commit when they did not change. It then runsgit pull --rebaseandgit push origin HEAD:main, retrying up to 5 times. The runner sets the bot identity. - A push made by a workflow does not trigger other workflows, so
deploy.ymlnever sees data commits. After a data commit,commit-back.sh --deploy <project>runsscripts/deploy.sh <project>itself. It only does this whenCLOUDFLARE_API_TOKENis set. - Inside a job,
GITHUB_TOKENis the platform job token. Scripts that call api.github.com getGITHUB_TOKEN: ${{ secrets.GH_API_TOKEN }}instead. When that secret is not set, they run unauthenticated and hit lower rate limits. - Issue automation calls the job API through
scripts/actions/forge.mjs. It reads the event fromFORKMESH_EVENT_PATHand usesFORKMESH_API_URLandFORKMESH_TOKEN.
Schedules are UTC. Every workflow with a schedule can also be run by hand (workflow_dispatch).
In the table, CF means CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID, which the job uses to deploy after a data commit.
| File | Source | Triggers | Secrets |
|---|---|---|---|
ci.yml |
GitHub ci.yml |
push (any branch): scripts/ci/test.sh --changed $FORKMESH_BEFORE; pull request into main: --changed $FORKMESH_BASE_SHA (the folders the pull request changes); dispatch input projects (default all) |
none |
deploy.yml |
new | push to main: scripts/deploy.sh --changed $FORKMESH_BEFORE (folders in scripts/deploy/projects.tsv); dispatch input project (a name, several space-separated, or all) |
CF |
readme.yml |
GitHub readme.yml |
push to main (any path; Projects, Deploying and Contributors), 17 6 * * * (also refreshes Blog Posts); commits README.md and .forkmesh/contributors/ |
GH_API_TOKEN (optional) |
blt-action--assign.yml |
BLT-Action /assign, /unassign |
issue comments (created) starting a line with /assign or /unassign; dispatch input issue (process the commands already in that issue). Verified authors only. Assigns through the job API (falls back to committing a signed ledger event on an older instance) and replies through the job API |
BLT_TASKS_SEED (fallback only) |
blt-action--unassign-stale.yml |
BLT-Action / BLT-Pool stale-assignment unassign | 53 * * * * (hourly): warns, then releases, self-claimed issues with no pull request and no update from the assignee for 24 hours (see Stale claims). Job API only, no commit |
none |
blt-cve--build-index.yml |
GitHub | push to main under blt-cve/public/cves, public/schema, scripts/build_index.py |
CF |
blt-design-contest--build-showcase.yml |
GitLab build_showcase |
issue events on contest issues (project label or [blt-design-contest] prefix, contest labels, winner), 47 7 * * * (votes raise no issue event), push under blt-design-contest/scripts/ or data/; reads the git.owaspblt.org issue ledger and writes changed pages to KV (blt-design-contest/scripts/refresh-kv.sh), no commit |
CF (with KV write) |
blt-extension--release.yml |
GitHub release.yml + packaging from create-release.yml |
dispatch only (input publish) |
CHROME_CLIENT_ID, CHROME_CLIENT_SECRET, CHROME_REFRESH_TOKEN |
blt-hackathons--create-hackathon.yml |
GitHub | issues opened/labeled with hackathon-creation |
CF |
blt-hackathons--process-sponsor.yml |
GitHub | issues opened/labeled with sponsorship |
CF |
blt-hackathons--update-stats.yml |
GitHub update-stats.yml + GitLab update-future-hackathons |
37 */6 * * * (every 6 h), push under blt-hackathons/; reads git.owaspblt.org incrementally within a 15-minute budget and writes changed stats and its state to KV (scripts/refresh-kv.sh), no commit |
CF (with KV read/write); optional GH_API_TOKEN, GITLAB_TOKEN (third-party GitLab events only), SLACK_TOKEN_OWASP, SLACK_TOKEN_BLT |
blt-jobs--expire-jobs.yml |
GitLab expire_jobs |
dispatch only (optional cleanup of expired jobs/*.md; the Worker already hides expired jobs) |
CF |
blt-jobs--process-submissions.yml |
GitLab process_submissions |
issues opened/edited/labeled that look like job, seeker or flag submissions; dispatch input issue |
CF |
blt-ossh--update-catalog.yml |
GitHub | 0 6 * * *; seeds from KV, writes data/ossh_catalog.json to KV when changed (blt-ossh/scripts/refresh-kv.sh), no commit |
DISCORD_BOT_TOKEN (optional), CF (with KV read/write) |
blt-ossh--update-profiles.yml |
GitHub | 13 4 * * * (safety net), issue events on profile issues; dispatch input issues to import issues |
CF |
blt-owasp-bumper--generate-repo-list.yml |
GitHub + GitLab pages |
0 0 * * *, push of generate_repo_list.py or scripts/refresh-kv.sh; writes index.html to KV (blt-owasp-bumper/scripts/refresh-kv.sh), no commit |
GH_API_TOKEN, CF (with KV write) |
blt-owasp-metadata--scraper.yml |
GitHub | 0 3 * * 1; writes data/metadata*.{json,csv,md} to KV (blt-owasp-metadata/scripts/refresh-kv.sh), no commit |
GH_API_TOKEN, CF (with KV write) |
blt-owasp-projects--refresh-data.yml |
GitLab refresh_data |
23 4 * * *; seeds from the live site, writes changed data files to KV (blt-owasp-projects/scripts/refresh-kv.sh), no commit |
GH_API_TOKEN, CF (with KV write) |
blt-owasp-projects--sweep-repos.yml |
GitLab sweep_repos (manual) |
dispatch only | GH_API_TOKEN, SLACK_WEBHOOK_URL_SWEEP |
blt-pages--update-leaderboard.yml |
GitLab update_leaderboard |
issue events on blt-pages or [NOMINATION] issues, 11 5 * * * (safety net), push under blt-pages/scripts/ or of the two pre-rendered pages or public/data/leaders.json; reads git.owaspblt.org and writes to KV (blt-pages/scripts/refresh-kv.sh), no commit |
CF (with KV write) |
blt-pool--mentor.yml |
BLT-Pool /mentor, /rematch, /unmentor (GitHub App commands) |
issue comments (created) starting a line with one of them; dispatch input issue. Verified authors only. Reads the pool from GET https://pool.owaspblt.org/api/mentors, replies through the job API and toggles the mentor-assigned label (see /mentor). No commit |
none |
blt-panini--update_slack_apps.yml |
GitHub | 0 0 * * 0; seeds from KV with the committed entries over it, writes slack_apps.json to KV when changed (blt-panini/scripts/refresh-kv.sh), no commit |
CF (with KV read/write) |
blt-profiles--add-profile.yml |
GitLab issue webhook in the blt-profiles Worker | issues opened/edited/labeled that look like Add-profile submissions (labels profile/add-profile/profile-submission, a [blt-profiles] title, or the template headings); dispatch input issue. Commits public/profiles/<user>.json and index.json, deploys, comments and closes |
CF |
blt-tomato--scrape_funding.yml |
GitHub | 0 18 * * 5, push under blt-tomato/.github/scripts/ or scripts/refresh-kv.sh; writes index.html and project_repos_links.json to KV (blt-tomato/scripts/refresh-kv.sh), no commit |
GH_API_TOKEN, CF (with KV write) |
blt-university--course-submission.yml |
GitHub | issue opened with course-submission or [COURSE] title, or that label added |
none |
blt-university--enrollment.yml |
GitHub | issue opened with enrollment or [ENROLLMENT] title, or that label added |
none |
blt-waypoint--build-site.yml |
GitLab build_site |
29 6 * * *, push of blt-waypoint/scripts/generate_forge_data.py, forge_urls.py or scripts/routing/projects.tsv; reads git.owaspblt.org and writes forge_data.json to KV, no commit |
CF (with KV write) |
renovate.yml |
new (discussion #1) | 41 3 * * 1 (Mondays); dispatch input mode (pulls, report or dry-run). Renovate looks up updates; scripts/renovate/apply.mjs opens or updates at most 5 signed pull requests and refreshes the Dependency Dashboard issue. See renovate.md |
BLT_RENOVATE_SEED (pulls), GH_API_TOKEN (optional) |
blt-action/forge/assign.mjs is the forge port of BLT-Action's comment commands. A line of an issue
comment that starts with /assign or /unassign (not in a quote or code block) acts on the commenter
only:
/assignis refused when the issue is closed, when someone else is assigned, or when the commenter already holds another open issue (BLT-Action allowed one issue without an open pull request; the forge has no issue-to-pull link, so any other open assignment counts). Otherwise the commenter is assigned and asked to open a pull request within 24 hours or comment/unassign. A claim with no pull request and no update for 24 hours is released byblt-action--unassign-stale.yml(Stale claims below)./unassignremoves only the commenter.- Comments from bots (
[bot],-actions) are ignored. - Only a verified author counts. The forge sets an issue comment's author from the signed-in session
(or the account its signing key belongs to) and marks it
comment.user.verified: truein theissue_commentpayload; for a manual run the ledger comment needsauthorVerified: true. A comment without that (anonymous, or from an instance older than the check, where the name was whatever the browser sent) gets a "sign in" reply and changes nothing. Comments made before the check, such as #27's, therefore cannot be replayed with a manual run. - Spam guards (both paths): a person gets at most one refusal reply a day, and more than 3
/assigncomments in an hour pause/assignfor them for 24 hours, announced once. Only verified/assigncomments count, and the refusal and pause markers count only in replies from the verified-actionsbot, so nobody can pause or silence someone else.
The assignment goes through the job API: POST /api/actions/job/issues/<n>/assignees with
{add: [user]} or {remove: [user]}. The forge records it as a claim event through the issue inbox,
signed by the instance's <owner>-actions bot, and sends the assignee its usual "issue assigned"
notification. The script keeps the ledger state in memory for the rest of the run and does not
commit anything.
If the instance answers that route with 404 not_found (an instance without the endpoint), the
script falls back to the earlier path: a signed assignees event (forkmesh-issue-event-v2, as the
desktop writes for an owner) appended to .forkmesh/issues/open/<n>/issue-<n>.json with the
record's assignees, signed by blt-tasks[bot]. A ledger change committed to git is trusted through
the push grant, like the desktop's, so the key needs no registration. The push uses
commit-back.sh --rebuild: when another writer (the issue intake) moved main, the ledger is rebuilt
on the new main instead of rebasing with -X theirs, which would drop the other side's events. That
path raises no "issue assigned" notification. BLT_ASSIGN_LEDGER=1 forces it.
The reply goes through the job API, signed by the instance's <owner>-actions bot, because only
inbox submissions raise notifications. It starts with @<user>, which notifies the commenter (the
platform's MENTION_RE). Each command is keyed by issue, comment id and login:
<!-- blt-assign:<key> --> at the end of the reply marks it answered (and, on the fallback path, the
ledger event id blt-assign-<key> marks it applied), so a rerun neither assigns nor replies twice once
the reply has reached the ledger. One comment is handled per event, at most 10 per manual run.
Setup: the platform needs the verified-author check and the job API assignees endpoint deployed
before this workflow (otherwise every comment is refused as unverified). BLT_TASKS_SEED (the
contents of ~/.config/blt/blt-tasks.seed, base64url, or 64 hex characters, under the repository's
Settings tab, "Actions secrets") is needed only for the fallback.
blt-pool/forge/mentor.mjs answers the mentor pool's commands on forge issues. The pool itself stays
in BLT-Pool's D1 (pool.owaspblt.org, where mentors sign up); the script reads the published mentors
from GET /api/mentors (BLT_POOL_URL overrides the base).
/mentormatches the commenter with a mentor who gave a git.owaspblt.org account (forge_usernamein the pool; set on the signup form or by an admin). Mentors without one are never matched, since the@namein the reply is how the request reaches them. Mentors whose specialties match an issue label come first, then the fewest open mentored issues (under theirmax_menteescap), then the name; the commenter is never their own mentor. Issues labelledsecurity,vulnerability,security-sensitiveorprivate-securitygo to the maintainers instead./rematch(the person who asked, or the mentor) moves the issue to another mentor; the previous one is skipped./unmentor(the same two people) cancels the match.- Asking for a mentor does not assign anyone; the reply points at
/assign, and the/assignreply points at/mentor. - Every reply ends with the commenter's referral link,
https://pool.owaspblt.org/?ref=<name>#join-mentor, which fills in "Referred By" on the signup form.referred_byaccepts a GitHub or a forge account.
The match lives in the ledger: the reply carries <!-- blt-mentor-match:<mentor>:<requester> --> or
<!-- blt-mentor-clear -->, and only verified bot comments are read back, so the current mentor of
every open issue, and with it each mentor's load, comes from .forkmesh/issues. <!-- blt-mentor:<key> -->
(keyed like /assign) marks a command answered. The mentor-assigned label follows the match on a
best-effort basis. When the pool cannot be read, the command still gets the "no mentor has room" reply.
BLT-Action is the only bot that answers /assign (issue #38). BLT-Pool (blt-pool, the
blt-github-app Worker) ignores /assign and /unassign, and its 2-hourly blt-cron job, which swept
every GitHub App installation for stale assignments on the frozen OWASP-BLT/* GitHub repositories, is
gone. Its stale-assignment release is ported here as blt-action/forge/stale.mjs, run hourly by
blt-action--unassign-stale.yml:
- Only self-claims are released: claims made with
/assign(recorded by the-actionsbot orblt-tasks[bot]) or with the web claim button by the assignee. An assignment a maintainer made for someone else is left alone, as is an assignee with no claim event in the ledger. - The clock starts at the newest claim and restarts with each verified comment the assignee posts on the issue. Bot comments (including the warning) do not restart it.
- An assignee who has an open pull request, or opened any pull request after the claim, keeps the
issue (the forge has no issue-to-pull link, so any pull request by them counts). Pull requests are
read anonymously from the
forkmesh/pullsledger branch; when it cannot be read the run stops and changes nothing. - After 18 quiet hours the assignee gets one warning (
<!-- blt-assign-stale-warning:<name>:<since> -->). At 24 hours, once that warning is at least 5 hours old, they are unassigned through the job API with a reply saying so. At most 10 actions per run. - On an instance without the job API assignees endpoint the run stops; there is no ledger fallback.
GitLab pipeline schedules were set in the GitLab UI, so their times were not in the repository. The schedules above came from:
blt-gsocupdate-progress:7 23 * * *. The.gitlab-ci.ymlcomment gives0 23 * * *, and the job fills in the current day. It ran in the Worker (blt-cron, 23:07-23:11) until 2026-10-18, when the job was turned off because GSoC 2026 coding ended on 17 Aug. Theprogresstask and its KV data stay, so the page shows the final state.blt-hackathonsfuture hackathons: was37 0 * * *(the README said daily). Since the data moved to KV there is one job (every 6 hours) for every hackathon that has not ended.blt-jobsexpire: no schedule (see "Fewer bot commits" below). Submissions are driven by issue events instead of polling.blt-owasp-projectsrefresh:23 4 * * *, daily. This is OWASP repository metadata.blt-pagesleaderboard:11 5 * * *, daily, plus issue events. It writes KV, not git, since 2026-10-18.blt-waypointdata:29 6 * * *, daily (KV, no commits).blt-design-contestshowcase: issue events on contest issues plus a daily47 7 * * *run for votes; the pages go to KV, not git.
Every data commit a workflow pushes lands on main, so schedules are chosen to commit only when something changed:
readme.ymlused to run hourly and committed every time because both "Last updated" lines carried the run time. It now runs on each push to main (ForkMesh has nopaths-ignore, but pushes made by workflows, including its own commit, do not trigger push workflows) and once a day for the external Blog Posts section.scripts/readme/update_readme.pykeeps a section's old text, timestamp included, when nothing else in it changed, so a run with no news commits nothing.blt-jobsexpired listings are hidden at read time: the Worker serves/data/jobs.json(run_worker_first) from the static asset and drops jobs whoseeffective_expires_at(written bynpm run build:jobs:expires_at, else posted/created + 60 days) has passed, fixingcount, withCache-Control: max-age=300. The daily delete-commit-redeploy is gone; the expired Markdown files stay in the repository until someone runs the workflow by hand.seekers.jsonhas no expiry rule and is still served as a plain asset.blt-osshprofiles are updated by issue events; the poll went from every 6 hours to once a day.- Hackathon statistics, GSoC stats and progress, and the my-gsoc-tool dashboard data and forks list
are kept in Workers KV (next section). Their six workflows (up to 28 commits a day, 24 of them the
hourly hackathon stats, whose
stats.jsontimestamp changed every run) are gone or no longer commit.
Data that a schedule refreshes now lives in each project's Workers KV namespace, not in git: no data
commits and no redeploys. Each Worker serves the data at the same path the pages already load, from KV,
and falls back to the copy committed under its static assets while KV has none (the first deploy).
Responses carry Cache-Control: public, max-age=60, stale-while-revalidate=600, X-Data-Source: kv or
static, and X-Data-Updated-At.
| Project | Paths served from KV | Refreshed by | Secrets on the Worker |
|---|---|---|---|
blt-hackathons |
/stats.json, /hackathon-data/*.json (and the unserved refresh state state/fetch-state.json) |
blt-hackathons--update-stats.yml, every 6 hours, through the Cloudflare KV API (scripts/worker-data/kv-get.sh, kv-put.sh) |
none |
blt-gsoc |
/js/gsoc2026_stats.generated.js, /data/progress.json |
no longer scheduled: GSoC 2026 ended, and gsoc2026-stats counted the frozen OWASP-BLT/* GitHub copies; both keep serving their final data from KV (run POST /__scheduled with a task by hand if needed) |
CRON_SECRET, GH_API_TOKEN, GITLAB_TOKEN (optional), SLACK_TOKEN_OWASP, SLACK_TOKEN_BLT |
my-gsoc-tool |
/data/github-contributions.json, /data/forks.json, /data/directory.json |
the Worker, from blt-cron at 00:03 (directory: 00:33-00:52) | CRON_SECRET, GH_API_TOKEN |
blt-pages |
/, /index.html, /leaderboard, /leaderboard.html (pre-rendered), /data/leaderboard.json, /data/leaders.json |
blt-pages--update-leaderboard.yml (issue events, daily, pushes of the pages) through the Cloudflare KV API |
none |
blt-design-contest |
/, /index.html, /show-all-entries.html, /<contest>.html and the extensionless forms |
blt-design-contest--build-showcase.yml (issue events, daily), only pages whose content changed |
none |
blt-waypoint |
/forge_data.json (also at the old /gitlab_data.json) |
blt-waypoint--build-site.yml, daily: per-folder history?path, the issue and pull ledgers (about 200 anonymous requests, FORGE_BUDGET_SECONDS 420) |
none |
blt-owasp-projects |
/www_project_repos.json, /repo_status.json, /issues.json, /known_issue_ids.json, /project_repos_links.json (and the unserved alert state state:new-project-ids) |
blt-owasp-projects--refresh-data.yml, daily (seeded from the live site); the new-repository and new-issue Slack alerts run in the Worker, from blt-cron hourly at :41 and :43 (state:new-issue-ids too) |
CRON_SECRET, SLACK_WEBHOOK_URL (#project-new-projects), SLACK_NEW_ISSUES_WEBHOOK, GH_API_TOKEN (optional) |
blt-owasp-bumper |
/ and /index.html (key index.html) |
blt-owasp-bumper--generate-repo-list.yml, daily |
none |
blt-ossh |
/data/ossh_catalog.json |
blt-ossh--update-catalog.yml, daily (kv-get.sh, kv-put.sh) |
none |
blt-owasp-metadata |
/data/metadata.json, /data/metadata_matrix.json, /data/metadata.csv, /data/metadata_matrix.csv, /data/metadata_summary.md |
blt-owasp-metadata--scraper.yml, weekly |
none |
blt-tomato |
/, /index.html (key index.html), /project_repos_links.json |
blt-tomato--scrape_funding.yml, weekly |
none |
blt-panini |
/slack_apps.json |
blt-panini--update_slack_apps.yml, weekly (kv-get.sh, kv-put.sh) |
none |
Shared code is in scripts/worker-data/:
kv-data.js:serveData()(KV, else the static file),putData()(storesupdatedAtas KV metadata),handleCronDispatch()(thePOST /__scheduledendpoint, checked againstCRON_SECRET) andrunChunked(). The Workers import it and wrangler bundles it, so a change here needs a redeploy of every project in the KV table above (deploy.sh --changedonly looks at project folders).kv-put.sh: writes files to a project's namespace through the Cloudflare API. It reads the namespace id from the project'swrangler.tomland needsCLOUDFLARE_API_TOKENwith Workers KV Storage: Edit.kv-get.sh: reads one key back the same way (exit 3 when the key is missing); blt-hackathons keeps its refresh state with it.create-kv.sh: creates the namespaces (<project>-datafor each project in the KV table above, all bound asDATA) and writes their ids into thewrangler.tomlfiles, replacing theREPLACE_WITH_KV_ID_<title>placeholders.testing.mjs: an in-memory KV, a staticASSETSstub and a fetch stub for the projects' tests.kvSiteWorker(patterns, {aliases})inkv-data.js: a whole Worker for a static site whose refreshed files live in KV. Paths matchingpatternsare served byserveData(), the rest byASSETS. The project'swrangler.tomllists the same paths in[assets] run_worker_first.
Generators that read git.owaspblt.org use scripts/forkmesh/ledger.py:
full issue records from .forkmesh/issues (labels, comments, votes, an oid cache), pull front
matter from the forkmesh/pulls ledger, and history?path=<folder> paged by ref. It builds on
blt-hackathons/forkmesh_source.py and its project attribution rules.
Free plan limits shaped each refresh. A Worker invocation may make 50 subrequests and use 10 ms of CPU:
- blt-hackathons stays in Actions. The first sync of a hackathon reads every pull request and
issue folder of the monorepo on git.owaspblt.org (GitHub events: every pull request and review of
each repository), hundreds of requests, over one invocation's subrequests and CPU. Later runs
are incremental: the refresh state in KV (
state/fetch-state.json) keeps each pull and issue folder's git oid, so a run lists the folders (3 requests) and re-reads only the changed ones.FETCH_BUDGET_SECONDS(900) stops a run early and saves its progress for the next one, so the single runner is never held for the whole 30-minute timeout. Slack counts (trackSlack) are spaced for Slack's 20-a-minute search limit, at most 40 per run. The job seeds the incremental review merge from the live files (KV holds the latest data, not git), runsfetch_stats.py, and writes only the files it rewrote.stats.jsonis written only when something other thanlastUpdatedchanged. The Worker only streams KV values, so serving costs almost no CPU. - blt-gsoc
gsoc2026-statsis about 90 GitHub requests, 78 of them searches (30 per minute allowed).runChunked()does about 3 projects (at most 24 requests) per call, keeping its cursor atstate:gsoc2026-stats; a count that fails keeps the previous day's value. - blt-gsoc
progress(not scheduled since 2026-10-18; run by hand only) is about 40 requests (GitLab commits per repository, two Slack searches per contributor), done in 2 calls of at most 30. - my-gsoc-tool needs 2 requests.
To refresh by hand, call the Worker with the cron secret (reset redoes a run that already finished;
repeat chunked tasks until the answer says "done": true; progress also takes "day": "YYYY-MM-DD"):
curl -X POST https://gsoc.owaspblt.org/__scheduled -H "X-BLT-Cron-Secret: $CRON_SECRET" \
-d '{"task": "progress", "reset": true}'For blt-hackathons, run the workflow by hand.
-
Issue scripts: the logic from github-script steps moved into these files:
blt-hackathons/scripts/actions/create-hackathon.mjsblt-hackathons/scripts/actions/process-sponsor.mjsblt-university/scripts/actions/welcome.mjsblt-ossh/scripts/actions/update-profiles.mjsblt-jobs/scripts/actions/process-submission.mjsblt-profiles/scripts/actions/add-profile.mjs
Validation, comments, labels, commits to main and closing the issue all work as before. The "eyes" reaction is gone because the job API has no reactions endpoint. Issue links point at
<instance>/<owner>/<repo>/issues/<n>. -
create-hackathon: a banner image used to produce invalid JavaScript (a missing comma). That is fixed.
-
blt-university: the old workflows re-ran on every label change. They now run when the issue is opened, or when their own label is added.
-
blt-ossh profiles: the job API cannot list issues. Each issue event adds, updates or removes that one issue's profile. The schedule re-reads the issues already in
profiles.jsonand drops any that were closed or unlabelled. To import older profile issues, run the workflow by hand with their numbers. -
blt-jobs submissions: the old job polled every open GitLab issue. Now each issue is processed when it is opened, edited or labelled, through
createFromIssue()inblt-jobs/scripts/submissions.js(the GitLab polling script is gone). The forge has no issue templates, so the job board links to the bodies inblt-jobs/issue-templates/. -
README contributor wall (
readme.yml): the list used to show only commit authors with a known GitHub login.scripts/readme/contributors.pynow lists every person with a commit in any project, as author or co-author, and leaves out bots. It merges each person's emails and names, and matches them to a GitHub or git.owaspblt.org account (pull and issue authors from the forge ledgers; GitLab noreply emails andgitlab:overrides are still parsed, but nobody links to gitlab.com). Matching uses noreply emails,scripts/readme/contributor-logins.tsv, the GSoC contributor data, and a few GitHub commit searches per run. People without an account are listed by name with an identicon. The job writes the following into.forkmesh/contributors/, and the README shows the local images:- a 64x64 PNG avatar per person (
<slug>.png) contributors.jsonlookups.json, a cache of the network lookups
Avatars are downloaded again after 30 days, at most 60 per run. A failed download keeps the old image or an identicon, and never fails the job. The job needs Pillow and runs with
GH_API_TOKENfor higher GitHub limits. - a 64x64 PNG avatar per person (
-
Small fixes:
blt-tomatoscrapes into the runner checkout (OUTPUT_DIR), then KV. It sendsGITHUB_TOKENwhen one is set.blt-owasp-bumperwritespublic/index.htmlin the runner checkout, then KV.my-gsoc-tool's two workflows became Worker tasks (my-gsoc-tool/worker/refresh.js). The forks list (GitHub repository OWASP-BLT/MY-GSOC-TOOL) is served at/data/forks.jsoninstead of being written into the README.
-
Scripts:
scripts/ci/test.sh --changedtests nothing when the base is empty, all zeros (a new branch) or unknown.scripts/deploy.sh --changed <base>deploys projects whoseprojects.tsvfolder changed.scripts/ci/tests.tsvhas new rows:blt-hackathons,blt-hackerhouse,blt-nextandblt-owasp-projects, from their GitLab test jobsblt-osshandscripts, for the action scripts
ci.yml runs the same scripts/ci/tests.tsv commands as before, but some projects cannot run on
this server:
- Flutter projects (
blt-flutter,blt-netguardian-client,blt-timer/flutter_app) andblt-cli(Rust) are reported as skipped. blt-mcprequires Python 3.12; itstests.tsvrow installs with--ignore-requires-python, so it runs on the server's 3.13 (the OAuth and SSE tests need loopback connections).blt-legacy(formerlyblt-core) passes only if xvfb, Chromium and libmemcached are installed, and that needs sudo.blt-safecloakrunsplaywright install --with-deps, which needs sudo.
Run these on a machine with the toolchain: scripts/ci/test.sh <project>.
Every open pull request into main runs ci.yml on its newest commit, and the result shows on the
pull request (Checks row and Checks tab) and in the Actions tab.
- The workflow is always the one on
main; edits to.forkmesh/in the pull request are not used. A pull request that changes.forkmesh/waits for a maintainer even when its author is trusted. - Runs get no secrets, no job token and no push access. They run in the runner's sandbox like every other job.
- Runs start by themselves for maintainers (organization owners and writers) and for contributors a maintainer marked with Always run for @name on a pull request. Everyone else's run waits: a maintainer reads the change and presses Approve and run (that approval covers that commit only; a new push needs a new approval). The author sees that nothing is needed from them.
- Remove a contributor from the trusted list with Stop running automatically on the Checks tab of one of their pull requests.
CI is not a required check for merging. Required checks on this forge need signed results that the
server's runner does not produce yet, and on the 1 GB server heavy suites (blt-legacy) and
projects without a toolchain are skipped, so a green run is advice, not a gate.
| What | Why |
|---|---|
blt-extension create-release (GitHub release with the zip) |
There is no release-upload API yet, and tag pushes do not trigger workflows. scripts/release-chrome.sh --package-only builds the zip locally. |
blt-vanish release (Flutter APK/desktop builds + GitHub release) |
No Flutter or Java on the runner, and no release API. |
blt-waypoint screenshot-repos (Playwright) |
No Chromium, and no sudo for --with-deps. |
blt-mcp Rust build, nightly and tagged release uploads (GitLab packages) |
The jobs built blt-cli/, now a separate project. There is no Rust and no package registry. blt-mcp/.gitlab-ci.yml is deleted; run the golden-snapshot test locally with BLT_VERIFY_SNAPSHOTS=true pytest. |
blt-university validate-labs, validate-vulnerability-intel, Jekyll build + commit of public/, Playwright e2e |
Jekyll needs Ruby and e2e needs Chromium. The commands are in blt-university/README.md ("Continuous integration and deployment"); .gitlab-ci.yml is deleted. Build public/ locally and commit it. |
blt-hackerhouse D1 migrations and staging deploy |
deploy.yml deploys production with pywrangler but does not apply migrations. Run npx wrangler d1 migrations apply hackerhouse --remote by hand (blt-hackerhouse/README.md, Deploy). .gitlab-ci.yml is deleted. |
blt-toasty lint (black/ruff/bandit) and staging/production environments |
Lint runs from .pre-commit-config.yaml; blt-toasty/.gitlab-ci.yml is deleted. Tests run in CI, and deploy.sh deploys the default environment. |
blt-pages Playwright tests, blt-netguardian Flutter client tests |
No Chromium or Flutter on the runner. Run them locally: npm ci && npm test in blt-pages, flutter test in blt-netguardian/client (blt-netguardian-client in scripts/ci/tests.tsv runs where Flutter exists). Both .gitlab-ci.yml files are deleted. |
blt-safecloak desktop client builds |
Already disabled in GitLab, and .gitlab-ci.yml is deleted. public/install.sh builds the client from a sparse clone of blt-safecloak/client; there are no prebuilt downloads. |
GitLab Pages / Cloudflare deploy jobs (blt-gsoc, blt-university, blt-design-contest, blt-toasty, blt-hackerhouse) |
Replaced by scripts/deploy.sh and deploy.yml. |