Round-3 PR re-review follow-ups:
LOW — CODEOWNERS missed /scripts/build-plugins-index.mjs and
/wrangler.toml. Both can change the bytes that get signed without
touching the sign step. Replaced the per-file enumeration with /scripts/
catch-all and added wrangler.toml.
HIGH — workflow comment said the post-sign verify step "catches a buggy
or tampered sign-script run". The "tampered" claim was wrong: any
attacker who can edit sign-plugins-index.mjs in a PR can edit the
verify step (and the embedded pubkey) in the same diff. Re-stated as
"catches accidental regressions only — CODEOWNERS is what stops
adversarial edits".
Branch protection on main has been enabled separately via gh API
(force-push + deletion blocked, PR review required, CODEOWNERS
enforced; bypass for repo owner and github-actions[bot] so the
auto-publish workflow keeps working).
The first iteration moved signing from the Cloudflare worker into this
repo's CI to fix the worker-as-sign-oracle defect. The re-review pointed
out that this just relocated the trust problem: anyone who lands a
commit on main gets the resulting bytes signed automatically. Mitigations:
* CODEOWNERS — sign-script, build scripts, the workflow itself, and
the auto-generated index/sig files are owned by the registry owner.
Combined with branch protection requiring CODEOWNERS review, a PR
touching the signing infrastructure or the artefacts it produces
cannot land without explicit owner sign-off. Plugin contributions
under plugins/<name>/ are covered by the standard PR-review rules
but don't trip CODEOWNERS unless they touch the signing path.
* SHA-pinned actions — actions/checkout@v4 and actions/setup-node@v4
replaced with full-SHA refs (v4.2.2 / v4.1.0). Blocks
action-supply-chain swaps (a malicious mutable tag move on a
popular action would otherwise execute in the same job that has
REGISTRY_PRIVATE_KEY in scope).
* Post-sign self-verify — a new step verifies plugins-index.json.sig
against the committed pubkey (not a secret) BEFORE the .sig hits
main. Catches a buggy sign-script run, an env-leak that produces
zero-bytes output, or a half-applied edit.
* REGISTRY_PRIVATE_KEY env scope — explicitly noted in workflow
comment that the secret is on the sign step ONLY (where it was
already), not the job. Prevents future contributors lifting it
to job-level out of convenience.
* In-workflow security model docstring — enumerates the residual
threat model (compromised maintainer, malicious PR, sign-step
bug, leaked refresh token) and what each defense addresses.
Trust root is now: GitHub branch protection on main + CODEOWNERS on
sign infrastructure + SHA-pinned actions + post-sign verification.
A maintainer with push rights can still ship malicious bytes through
a code-review bypass — that residual is the same as for any signed
package registry and falls outside what CI alone can mitigate.
Branch protection on `main` MUST be configured by an org admin to
match the assumptions in CODEOWNERS:
- require pull request reviews (at least 1)
- require review from CODEOWNERS
- dismiss stale approvals on new commits
- restrict who can push directly to main (org admins only)
Pair with the worker-side simplification on the librefang PR — the
worker is now a pure transport (no key material, no signing) and this
repo's CI takes over signature production.
scripts/sign-plugins-index.mjs reads REGISTRY_PRIVATE_KEY from a GitHub
Actions secret, signs plugins-index.json with Ed25519, and writes
plugins-index.json.sig alongside it. Aborts loudly when the secret is
missing so a misconfigured CI can't silently ship an unsigned payload.
The workflow now runs build → sign → commit (.json + .sig) → push →
poke worker /refresh. The worker fetches the committed .json + .sig
verbatim and stores both — the daemon then verifies against the
embedded pubkey it ships with.
Closes PR review CRITICAL #1: the worker is no longer a sign-anything
oracle reachable via REGISTRY_REFRESH_TOKEN. Trust root is now this
repo's branch protection + Actions secret scope, not a token any CI
job that can talk to stats.librefang.ai can use to mint signatures.
Note: the keypair was rotated as part of this change (PR not yet
merged so no daemon TOFU pins exist). New pubkey:
ClGa0Ucap8NdrKAy1rw9Tt6A9I8eg4zJ53+xIuKMuq0=
The plugins-index.json.sig committed here is signed with the matching
new private key, in lockstep with the daemon EMBEDDED_REGISTRY_PUBKEY
constant and all three worker [vars] entries.
Pair with the existing plugins-index.json path (which feeds the
daemon's signed install lane). registry-index.json mirrors the
dict-shaped payload the registry-worker's cron currently builds via
40+ GitHub Contents API calls — but built locally from the checked-out
tree by scripts/build-registry-index.mjs, so the worker only fetches
ONE file per category type (2 total: plugins + registry) on refresh.
Workflow now picks up content changes across all 8 category dirs
(was: plugins/ only) so dashboard updates land within seconds of a
push instead of waiting for the 02:00 UTC cron tick.
The dashboard's /api/registry endpoint reads kv_store('registry_data');
the worker's forced-refresh now writes that key with these bytes and
purges the Cache-API entry, so the next dashboard hit sees fresh
data instead of the 1h-cached previous payload.
Generated counts on first build: 11p 17h 32a 60s 44c 57pr 22w 33mcp.
Walking ~40+ plugin TOMLs via the GitHub Contents API from the worker
exceeded the Workers Free 50-subrequest-per-invocation limit, leaving
the daemon's signed plugins index either empty or partial after every
forced refresh.
Move the walk into the repo: scripts/build-plugins-index.mjs reads each
plugins/<name>/plugin.toml directly from the checked-out tree and emits
a sorted flat array (name, version?, description?, needs?) at
plugins-index.json. The CI workflow regenerates and commits this file
on every push under plugins/, then pokes the worker's
/api/registry/refresh — which now fetches the single committed
plugins-index.json (1 subrequest), validates the JSON shape, and
re-signs it with Ed25519. Refresh cost is now constant in registry
size, not linear.
The dashboard's dict-shaped /api/registry payload is unchanged — that
still rebuilds via the daily 02:00 UTC cron.
Without this, dashboard / daemon see content changes only after the
next 02:00 UTC cron tick (up to ~24h delay). The action POSTs to
stats.librefang.ai/api/registry/refresh with a bearer token shared
with the worker secret of the same name; until both secrets exist on
their respective sides, the endpoint returns 503 and the action fails
loud — no silent half-deploy.
Filters on directories the worker actually reads (plugins/, agents/,
skills/, hands/, channels/, providers/, workflows/, mcp/) so README
edits don't burn worker invocations.
Known limitation: the worker rebuild walks ~40+ GitHub Contents API
subrequests, which on Workers Free truncates partway through and
leaves some categories empty. This action fires the right path; the
underlying budget fix (Workers Paid, or pre-building the index in
this repo) is tracked separately.
The daemon's `manifest_missing_integrity_hooks` check (#3804) hard-fails
any registry install whose plugin.toml declares hooks but lacks an
[integrity] entry for each one. All 11 plugins under plugins/ had the
[hooks] table but were missing [integrity], so `librefang plugin install
<name>` would error after download with "missing [integrity] hashes for
hook script(s): ...".
Compute and pin SHA-256 over each `hooks/*.py` under every plugin dir.
The mempalace-indexer entries were already present and are unchanged
(reordered alphabetically by the regenerator).
Verified each plugin.toml still parses (Python tomllib).
Remove git conflict markers from linkedin, reddit, and twitter
HAND.toml and SKILL.md files. Keep the more complete version
(with new settings i18n translations) and fix French/Spanish
accent issues (dépasser, entraîner, média, réponse, conexión,
imágenes, Mode de croissance).
- Fix French accent characters (é/è/ê/ç/â/ô) across all 14 HAND.toml files
- Fix German special characters (ä/ö/ü/ß) across all 14 HAND.toml files
- Add category translations to all 6 i18n language blocks in all 14 hands
- Enhance SKILL.md content for 9 hands with practical examples and workflows
- Trim bloated SKILL.md files (apitester 1400→892, devops 1301→870)
- Rewrite root README.md with accurate stats, complete hand/integration tables
- Update hands/README.md with full 14-hand listing and i18n documentation
Expand from ~124 to ~816 lines covering navigation, form handling,
screenshots, JavaScript execution, error handling patterns, and
best practices for the browser automation hand.
- Add version = "1.0.0" to all 14 hands
- Add approval_mode toggle to clip and devops hands (queue actions
for user review before executing)
- Strengthen routing aliases for analytics, apitester, browser,
collector, devops, lead, predictor, researcher, strategist
All plugins are stdlib-only Python with no external dependencies.
- auto-summarizer: extractive conversation summary for long context
compression, persists per-agent summaries to disk
- conversation-logger: JSONL audit logs per agent with ISO 8601
timestamps, auto-creates log directory tree
- guardrails: safety filter detecting PII (email, phone, SSN, CC),
prompt injection patterns, and credential exposure via regex
- keyword-memory: extracts entities (emails, URLs, dates, technical
terms like camelCase/snake_case/dotted identifiers) as memories
- sentiment-tracker: keyword-based sentiment scoring with intensifiers
and negation handling, only injects context for non-neutral sentiment
- todo-tracker: detects action items via 7 task patterns, tracks
completion, deduplicates, persists per-agent with 20-item FIFO limit
33 agent READMEs, 14 hand READMEs, 2 plugin READMEs
(echo-memory + hooks), and 2 skill READMEs
(custom-skill-prompt + custom-skill-python).
Each README documents the component's purpose, configuration,
and usage based on its TOML definition.
Each directory (agents, hands, integrations, plugins, providers,
scripts, skills) now has a README documenting its TOML format,
current contents, and contribution steps.