This repo is the single distribution path for every Open Agent AI Security plugin on both Claude Code and Codex. Whoever can change .claude-plugin/marketplace.json decides what code lands on every user's machine — and socxen registers a stdio MCP server that executes locally, so a catalog compromise is downstream code execution. This is a threat-model review of the write path and the trust chain, not of code correctness (covered separately).
Overall the catalog itself is in decent shape: PR-gated with a required approval and a required check, force-push and deletion blocked, no secrets, no deploy keys, no webhooks, default workflow permissions read. The gaps below are about who can bypass that — and about the fact that the catalog is currently protected more strictly than the artifacts it points at.
Findings are ranked by exploitability × blast radius. All of them require insider access or a configuration change; nothing here is an outsider-exploitable hole.
1. The artifact repos are protected more weakly than the catalog (High)
socxen's main requires zero approving reviews. The catalog pins socxen at ref: main, and socxen ships .mcp.json registering an MCP bridge that runs on the user's machine. So the strongest control on the socxen supply chain is not this repo's "1 approval" — it's socxen's "0 approvals".
Attack path: any single account with write on socxen opens a PR and merges it themselves once CI is green. The change is live to every socxen user at their next plugin update. No second pair of eyes anywhere in the chain.
Mitigation: set required_approving_review_count: 1 (minimum) on socxen's main, matching praxen and this repo. Distribution-path parity is worth stating as an explicit invariant: the catalog's protection is only as strong as the weakest pinned target's.
2. enforce_admins is off on all three repos in the chain (High)
plugins, praxen, and socxen all have enforce_admins: false and no push restrictions on main. Four accounts hold admin on this repo.
Attack path: one compromised admin token pushes a modified marketplace.json straight to main — no PR, no review, no catalog check. Signed commits are not required and no commit in this repo is currently signature-verified, so the commit's author name is attacker-chosen and there is no cryptographic evidence of who actually made the change.
Mitigation: enable "include administrators" (or move to a ruleset with an empty bypass list); enable required signed commits on main in all three repos; consider require_last_push_approval so a self-approved final push can't slip in.
3. The write set is much larger than the job requires (High)
Eight accounts have write-or-above on a two-file catalog repo (five write, three admin; nine on socxen). Required approvals is 1 and there is no CODEOWNERS, so any two of those eight — or any one admin, per finding 2 — can ship an arbitrary catalog entry.
Mitigation:
- Trim collaborators to the people who actually publish catalog changes; everyone else contributes by PR from a fork.
- Add
CODEOWNERS covering .claude-plugin/, .github/, and scripts/, with require_code_owner_reviews: true.
- Given the blast radius, 2 required approvals is defensible for this repo specifically even though 1 is right for the product repos.
4. The required catalog check attests to itself (Medium)
validate.yml and scripts/validate_catalog.py are both checked out from the PR head. A PR that edits the validator alongside the manifest passes its own check.
Attack path: a malicious PR points source.url somewhere it shouldn't go and relaxes the corresponding assertion in validate_catalog.py; the required catalog context goes green and the rule that was supposed to stop it never ran.
The check is therefore a defence against honest mistakes, not against a hostile change — worth being explicit about, since the README presents it as an integrity control.
Mitigation: run the validator from a trusted ref rather than the PR head (check out main's scripts/ and run it against the PR's manifest, or move validation to a reusable workflow hosted elsewhere), and cover .github/ + scripts/ with CODEOWNERS (finding 3) so validator edits need an owner's review.
5. Mutable-ref pinning with no verification anywhere in the chain (Medium — accepted risk, worth writing down)
Entries pin ref: "main". That's a deliberate and reasonable tradeoff — it's what lets product releases ship without touching this repo — but the consequence should be stated plainly:
- Anything that lands on praxen/socxen
main is live to users at their next update. There is no per-release gate here.
- Neither Claude Code nor Codex verifies a signature, checksum, or provenance attestation at install or update time. The trust anchor is GitHub's TLS plus the branch protection on those repos'
main — nothing else. Pinning a SHA or tag would move that anchor, not remove it, since neither ecosystem validates the object it fetched either.
- Codex's snapshot-at-add model means Codex users lag until they run
plugin marketplace upgrade. That's accidental delay, not a control.
Mitigation: keep ref: main, but treat those branches as production — findings 1 and 2 are the actual controls. Additionally, require signed tags for releases so there is at least one signed object per release to audit against, and document propagation semantics in the README (what updates when, and how a user pins to a known-good state).
6. The cross-repo integrity tripwire described in the README doesn't exist (Medium)
The README states: "The praxen repo carries a mirror of this index … a CI check in that repo keeps the mirror in sync with this file."
There is no such check. Nothing in praxen references open-agent-ai-security/plugins, and the two manifests have materially diverged — praxen's copy has no socxen entry, carries version metadata (which this repo's validator explicitly forbids), and uses source: "./" rather than a pinned https URL. A documented-but-absent control is worse than no control: it gets counted as coverage during exactly the incident it was supposed to catch.
Even implemented as described, its real coverage is narrow: it would compare the praxen entry only — it would miss socxen entry tampering entirely, and it would miss anyone with write to both repos who changes both copies.
Mitigation: pick one. Either (a) delete the claim from the README, or (b) implement a genuine out-of-band monitor — a scheduled job in a different trust domain that hashes .claude-plugin/marketplace.json and alerts on any change not matching a maintainer-published hash, covering every entry. Option (b) is the only version that detects a change made by someone with write to both repos. Either way, resync or formally retire the duplicate manifest in praxen so there is one authority.
7. No incident-response or recovery story (Medium-Low)
There is no SECURITY.md here and no documented answer to: a bad entry shipped — now what? Plugin distribution is pull-based with auto-update off by default, so there is no kill switch and no way to reach users who installed inside the bad window. Reverting the commit fixes future installs and nothing else.
Mitigation: add a SECURITY.md with a catalog-compromise runbook — revert, rotate credentials and collaborator access, publish a GitHub Security Advisory and a notice on the community site, and give users a self-check ("how to confirm which marketplace and which commit your install came from, and how to reinstall clean"). Cheap to write now, unwritable under pressure later.
8. Actions hardening (Low — blast radius today is a runner, not the repo)
allowed_actions: all, sha_pinning_required: false, and validate.yml uses actions/checkout@v4, a mutable tag. A fork PR can already modify the workflow and the script and get arbitrary execution in the runner — but with contents: read, no secrets, and can_approve_pull_request_reviews: false, there is nothing there to steal or escalate with. That's the correct posture; the point is it stays correct only as long as this repo never gains a secret.
Mitigation: pin actions by commit SHA (or enable sha_pinning_required), restrict allowed_actions to selected/verified, set fork-PR workflow approval to all outside collaborators rather than first-time contributors, and treat "add a secret to this repo" as a decision that requires re-reading this section.
Related, on praxen: dependabot-auto-merge.yml triggers on pull_request_target with contents: write. It does not check out PR code and it gates on github.event.pull_request.user.login == 'dependabot[bot]', so it is not a pwn-request — but it is a write-scoped trigger on a repo the catalog serves. Keep that gate exactly as is, and pin dependabot/fetch-metadata by SHA.
9. Repository and org hygiene (Low)
- Secret scanning, push protection, and Dependabot alerts are disabled on
plugins, praxen, and socxen. All free on public repos; enable them.
- Unused surface: wikis and projects are enabled on repos that use neither. Disable them — every enabled feature on the canonical distribution repo inherits this repo's apparent authority.
- Org settings to confirm (not visible with a repo-scoped token): that org-wide 2FA is required, ideally 2FA with security keys, given that admin accounts here can bypass branch protection. Also
members_can_create_public_repositories: true with repo creation open to all members — any member can create a public repo under the org name that looks canonical to outsiders. Restrict repo creation to owners.
10. Authenticity anchoring (Low, but structural)
The community site never links to this repo, and the canonical marketplace add command is not published anywhere a user can independently verify against — README files only, which is precisely the surface an impersonator gets to imitate. Meanwhile praxen's own install docs still direct users to a different repo than this one, so there is no single published answer to "what is the correct install command?"
Mitigation: publish the canonical install command and repo path on the community site, link it from every README, and converge the product repos' install docs on the one path this repo is meant to be. One published, verifiable answer is worth more here than any amount of repo-side hardening.
Suggested order: 1 → 2 → 3 (close the bypass paths), then 6 → 4 (make the documented controls real), then 7 (write the runbook), then 5/8/9/10 as hygiene.
— Fable-class supply-chain security review, requested by Steve
This repo is the single distribution path for every Open Agent AI Security plugin on both Claude Code and Codex. Whoever can change
.claude-plugin/marketplace.jsondecides what code lands on every user's machine — and socxen registers a stdio MCP server that executes locally, so a catalog compromise is downstream code execution. This is a threat-model review of the write path and the trust chain, not of code correctness (covered separately).Overall the catalog itself is in decent shape: PR-gated with a required approval and a required check, force-push and deletion blocked, no secrets, no deploy keys, no webhooks, default workflow permissions
read. The gaps below are about who can bypass that — and about the fact that the catalog is currently protected more strictly than the artifacts it points at.Findings are ranked by exploitability × blast radius. All of them require insider access or a configuration change; nothing here is an outsider-exploitable hole.
1. The artifact repos are protected more weakly than the catalog (High)
socxen'smainrequires zero approving reviews. The catalog pinssocxenatref: main, and socxen ships.mcp.jsonregistering an MCP bridge that runs on the user's machine. So the strongest control on the socxen supply chain is not this repo's "1 approval" — it's socxen's "0 approvals".Attack path: any single account with write on socxen opens a PR and merges it themselves once CI is green. The change is live to every socxen user at their next plugin update. No second pair of eyes anywhere in the chain.
Mitigation: set
required_approving_review_count: 1(minimum) on socxen'smain, matching praxen and this repo. Distribution-path parity is worth stating as an explicit invariant: the catalog's protection is only as strong as the weakest pinned target's.2.
enforce_adminsis off on all three repos in the chain (High)plugins,praxen, andsocxenall haveenforce_admins: falseand no push restrictions onmain. Four accounts hold admin on this repo.Attack path: one compromised admin token pushes a modified
marketplace.jsonstraight tomain— no PR, no review, nocatalogcheck. Signed commits are not required and no commit in this repo is currently signature-verified, so the commit's author name is attacker-chosen and there is no cryptographic evidence of who actually made the change.Mitigation: enable "include administrators" (or move to a ruleset with an empty bypass list); enable required signed commits on
mainin all three repos; considerrequire_last_push_approvalso a self-approved final push can't slip in.3. The write set is much larger than the job requires (High)
Eight accounts have write-or-above on a two-file catalog repo (five write, three admin; nine on socxen). Required approvals is 1 and there is no
CODEOWNERS, so any two of those eight — or any one admin, per finding 2 — can ship an arbitrary catalog entry.Mitigation:
CODEOWNERScovering.claude-plugin/,.github/, andscripts/, withrequire_code_owner_reviews: true.4. The required
catalogcheck attests to itself (Medium)validate.ymlandscripts/validate_catalog.pyare both checked out from the PR head. A PR that edits the validator alongside the manifest passes its own check.Attack path: a malicious PR points
source.urlsomewhere it shouldn't go and relaxes the corresponding assertion invalidate_catalog.py; the requiredcatalogcontext goes green and the rule that was supposed to stop it never ran.The check is therefore a defence against honest mistakes, not against a hostile change — worth being explicit about, since the README presents it as an integrity control.
Mitigation: run the validator from a trusted ref rather than the PR head (check out
main'sscripts/and run it against the PR's manifest, or move validation to a reusable workflow hosted elsewhere), and cover.github/+scripts/withCODEOWNERS(finding 3) so validator edits need an owner's review.5. Mutable-ref pinning with no verification anywhere in the chain (Medium — accepted risk, worth writing down)
Entries pin
ref: "main". That's a deliberate and reasonable tradeoff — it's what lets product releases ship without touching this repo — but the consequence should be stated plainly:mainis live to users at their next update. There is no per-release gate here.main— nothing else. Pinning a SHA or tag would move that anchor, not remove it, since neither ecosystem validates the object it fetched either.plugin marketplace upgrade. That's accidental delay, not a control.Mitigation: keep
ref: main, but treat those branches as production — findings 1 and 2 are the actual controls. Additionally, require signed tags for releases so there is at least one signed object per release to audit against, and document propagation semantics in the README (what updates when, and how a user pins to a known-good state).6. The cross-repo integrity tripwire described in the README doesn't exist (Medium)
The README states: "The praxen repo carries a mirror of this index … a CI check in that repo keeps the mirror in sync with this file."
There is no such check. Nothing in praxen references
open-agent-ai-security/plugins, and the two manifests have materially diverged — praxen's copy has nosocxenentry, carriesversionmetadata (which this repo's validator explicitly forbids), and usessource: "./"rather than a pinned https URL. A documented-but-absent control is worse than no control: it gets counted as coverage during exactly the incident it was supposed to catch.Even implemented as described, its real coverage is narrow: it would compare the praxen entry only — it would miss socxen entry tampering entirely, and it would miss anyone with write to both repos who changes both copies.
Mitigation: pick one. Either (a) delete the claim from the README, or (b) implement a genuine out-of-band monitor — a scheduled job in a different trust domain that hashes
.claude-plugin/marketplace.jsonand alerts on any change not matching a maintainer-published hash, covering every entry. Option (b) is the only version that detects a change made by someone with write to both repos. Either way, resync or formally retire the duplicate manifest in praxen so there is one authority.7. No incident-response or recovery story (Medium-Low)
There is no
SECURITY.mdhere and no documented answer to: a bad entry shipped — now what? Plugin distribution is pull-based with auto-update off by default, so there is no kill switch and no way to reach users who installed inside the bad window. Reverting the commit fixes future installs and nothing else.Mitigation: add a
SECURITY.mdwith a catalog-compromise runbook — revert, rotate credentials and collaborator access, publish a GitHub Security Advisory and a notice on the community site, and give users a self-check ("how to confirm which marketplace and which commit your install came from, and how to reinstall clean"). Cheap to write now, unwritable under pressure later.8. Actions hardening (Low — blast radius today is a runner, not the repo)
allowed_actions: all,sha_pinning_required: false, andvalidate.ymlusesactions/checkout@v4, a mutable tag. A fork PR can already modify the workflow and the script and get arbitrary execution in the runner — but withcontents: read, no secrets, andcan_approve_pull_request_reviews: false, there is nothing there to steal or escalate with. That's the correct posture; the point is it stays correct only as long as this repo never gains a secret.Mitigation: pin actions by commit SHA (or enable
sha_pinning_required), restrictallowed_actionsto selected/verified, set fork-PR workflow approval to all outside collaborators rather than first-time contributors, and treat "add a secret to this repo" as a decision that requires re-reading this section.Related, on praxen:
dependabot-auto-merge.ymltriggers onpull_request_targetwithcontents: write. It does not check out PR code and it gates ongithub.event.pull_request.user.login == 'dependabot[bot]', so it is not a pwn-request — but it is a write-scoped trigger on a repo the catalog serves. Keep that gate exactly as is, and pindependabot/fetch-metadataby SHA.9. Repository and org hygiene (Low)
plugins,praxen, andsocxen. All free on public repos; enable them.members_can_create_public_repositories: truewith repo creation open to all members — any member can create a public repo under the org name that looks canonical to outsiders. Restrict repo creation to owners.10. Authenticity anchoring (Low, but structural)
The community site never links to this repo, and the canonical
marketplace addcommand is not published anywhere a user can independently verify against — README files only, which is precisely the surface an impersonator gets to imitate. Meanwhile praxen's own install docs still direct users to a different repo than this one, so there is no single published answer to "what is the correct install command?"Mitigation: publish the canonical install command and repo path on the community site, link it from every README, and converge the product repos' install docs on the one path this repo is meant to be. One published, verifiable answer is worth more here than any amount of repo-side hardening.
Suggested order: 1 → 2 → 3 (close the bypass paths), then 6 → 4 (make the documented controls real), then 7 (write the runbook), then 5/8/9/10 as hygiene.
— Fable-class supply-chain security review, requested by Steve