Repository navigation
restructure: ship only plugin/ via git-subdir — keep build artifacts out of the distributed plugin (#29) - #66
Conversation
virtualsteve-exa
left a comment
There was a problem hiding this comment.
Thorough review with independent verification — the restructure itself is excellent (clean 100% renames, ${CLAUDE_PLUGIN_ROOT} wiring correct, bump_version/invariants/CI all repointed, no two-README pill drift, shipped README's clone line updated). I reproduced the core claim myself: a real install from this branch via a ./plugin marketplace source caches only plugin/ contents — no tests/ evals/ security/ scripts/. Requesting changes on three items, one of which turned out to be a P0 hiding in your verification section.
1. Your "1 skipped" was a masked live breakage — now fixed on dev; rebase required
The skip wasn't env noise: mcp 2.0.0 removed streamablehttp_client, and the bridge's unbounded mcp>=1.0 pin meant every fresh uv resolve got 2.0.0 and crashed at import. Your path move busts the env cache key, which is why the fresh resolve happened (on both our machines) — and the smoke test's env-failure guard downgraded the crash to a skip. Details in #67; pinned mcp>=1.0,<2 in #68 and released as 0.6.8 (fresh installs on main were broken for everyone, independent of this PR — promoting this PR unpinned would have extended that to every updating user at once).
Action: rebase onto dev (0.6.8). After rebase the suite must report 238 passed, 0 skipped — treat any remaining skip as a failure.
2. plugin-smoke.sh breaks on its first post-promotion run (blocking, in-PR)
The upgrade leg can't handle a pre-restructure prior ref — which is exactly what prior will be (0.6.8, old layout) on the first run after promotion:
version_at()reads$1/plugin/.claude-plugin/plugin.json→FileNotFoundErrorunderset -ewhen the prior worktree has it at the root.fabricate_marketplacehardcodes"source": "./plugin"→ the prior install has noplugin/to install from.
Both need an existence fallback, e.g. in version_at: try plugin/.claude-plugin/plugin.json then .claude-plugin/plugin.json; in fabricate_marketplace: [ -d "$1/plugin" ] && src="./plugin" || src="./". (Same boundary class 0.6.6 fixed once with checkout -f — this is its two remaining instances.)
3. Harden the skip guard that masked #67 (strongly recommended, small)
In test_check_without_creds_exits_clean: a bridge traceback is never an environment problem. Before the skip at the returncode != 0 guard, add: if "Traceback" in proc.stderr → pytest.fail, only then fall through to skip for genuine uv-provisioning failures. That one line would have caught #67 on both our machines instead of hiding it.
Minor
evals/README.md:8,87still sayskills/soc-investigate/reference/examples/— nowplugin/skills/….
Sequencing addendum for the ⚠️ paired change (@virtualsteve-exa)
The window has three coordinated edits, not two: (1) this PR's promotion to socxen main, (2) the plugins index source flip to git-subdir+path: plugin, and (3) praxen's legacy mirror (praxen/.claude-plugin/marketplace.json) — #211 gave it a socxen entry with a url+ref: main source, which breaks identically if not flipped in the same window. Praxen's check_marketplace_mirror.py cron will flag drift after the fact, but the edit itself must be paired.
Everything else checked out: fixtures carry the RFC 5737 remap, install.sh is genuinely move-safe, root README correctly doesn't ship. Happy to re-review immediately after the rebase + smoke fix.
|
Correction to my review's sequencing addendum, after a sanity check from Steve: socxen currently has no installed user base beyond the two maintainers, both on the canonical marketplace — so the population that could hit a stale praxen-mirror socxen entry during the window is empty. Revised sequencing: two hard-paired edits — (1) this PR's promotion to socxen Review verdict otherwise unchanged: rebase onto 0.6.8 (expect 238/0-skip), the |
|
Sequencing update #2 (final): per Steve, the praxen legacy mirror's socxen entry shouldn't be updated — it should be removed: with zero legacy socxen installs, its only effect is acquiring new users onto a deprecated channel (dead-end onboarding). Filed as praxen#235. Once that lands, this PR's promotion window is exactly two paired edits — socxen |
|
Praxen-side prep is done: open-agent-ai-security/praxen#236 (fast-tracked to praxen
|
|
Blocker cleared: the catalog validator now accepts |
0bb589a to
e506f33
Compare
|
All three review items addressed, plus the minor — 1. Rebase → 2.
Verified against fabricated old- and new-layout worktrees rather than by inspection: 3. Skip guard un-masked. A traceback now Minor: I left |
virtualsteve-exa
left a comment
There was a problem hiding this comment.
Re-review of e506f33 — all three items plus the minor verified fixed, independently. APPROVING on content. One more (mechanical but consequential) rebase is needed before merge; checklist below.
Verified on the branch
- Suite: 238 passed / 0 skipped — and
test_bridge_static.pygenuinely runs 6/6 now. The skip-guard fix is exactly right: traceback →pytest.failwith the #67 story memorialized in the comment, provisioning failures still skip. That guard will never hide an import crash again. plugin-smoke.shfallbacks:version_at()probesplugin/then root and still hard-errors on a genuinely missing manifest (good catch keeping that fatal — an empty version would pass the equality check and smoke nothing);fabricate_marketplace()resolves./vs./pluginper call, which is the detail that matters since the upgrade leg fabricates twice across the boundary checkout.- Moved snippet intact:
plugin/skills/soc-investigate/settings.snippet.json={allow: 18, ask: 4, deny: 68}— #74's containment fix survived the move. - evals/README + wiring docstring paths fixed.
Required in the final rebase (dev moved under you again — sorry, busy day: 0.6.9, #77, #78)
- Version/CHANGELOG: rebase brings the 0.6.9 entry;
plugin/.claude-plugin/plugin.json+ the README pill must read 0.6.9 (branch currently says 0.6.8). - #78 (observra 1.1 + dep lock) touched two files you're moving: take #78's side of
exabeam-mcp-bridge.py(bounded PEP 723 line:observra>=1.1,<2,httpx<1,certifi>=2024,<2027,typing_extensions>=4.7,<5) andobservra_logging.py(publicemit()migration) into theirplugin/connector/homes. - Move the lockfile too: #78 added
connector/exabeam-mcp-bridge.py.lock(hash-pinned resolve;uv runpicks it up by adjacency). It must land atplugin/connector/exabeam-mcp-bridge.py.lock— if the rebase drops it at rootconnector/, fresh installs silently lose the pin we just added. - #77 (
security/praxen/): stays at root with the rest ofsecurity/— should be conflict-free, just confirm the invariant-test edits merge (it touched the SPDX exemption intest_repo_invariants.py). - Regenerate the AI BOM after the rebase (sources moved + #78 changed the recorded bounds; CI gates BOM currency on any source change — it caught me on #74 too).
- Re-run the suite (expect 238/0) and your old/new-layout smoke-helper spot check once more on the final tree.
Sequencing note (your call, but flagging)
Your #73 edits root install.sh; this PR moves it to plugin/install.sh. Whichever lands second rebases over the other — my suggestion is this one first (it's the release-window PR and the bigger rebase), then #73 relocates its wiring into plugin/install.sh + plugin/skills/soc-investigate/merge_permissions.py.
The promotion window itself is unchanged and fully staged: this PR's release to main pairs with the plugins index flip to git-subdir/path: plugin (validator already accepts it), and you have write there to author it.
…ut of the distributed plugin (#29) The community marketplace installs socxen with `source: url` over the WHOLE repo, so every user's plugin cache gets tests/, evals/, security/ (incl. the red-team attack payloads), scripts/, and .github/ — build-time artifacts that should never ship. Claude Code has no exclude/ignore mechanism; the only supported way to trim what installs is a `git-subdir` source pointing at a subdirectory. Move the distributable payload into `plugin/` (single source of truth — no staged dist/ copy): plugin/.claude-plugin/plugin.json · plugin/.mcp.json · plugin/skills/ plugin/connector/ · plugin/docs/ · plugin/install.sh · plugin/README.md plugin/LICENSE · plugin/NOTICE Left at repo root (never ship): tests/ evals/ security/ scripts/ .github/ and the community-health docs (CHANGELOG, CONTRIBUTING, SECURITY, CODE_OF_CONDUCT). All build tooling repointed to plugin/ (Path(__file__)-relative ROOT stays the repo root, so moved refs gain a `plugin/` segment): - tests/*.py (bridge/skills/settings/connector loads; invariant marketplace assertion + bridge self-ref rebased to plugin/) - evals/run.py (fixture dir), security/gen_aibom.py (all sources + BOM prose; aibom.cdx.json regenerated), .github/workflows/ci.yml (py_compile path) - scripts/bump_version.py (plugin.json + README pill), scripts/release/ plugin-smoke.sh (version read, history pathspec +--follow across the rename, fabricated marketplace source ./ -> ./plugin) - doc cross-links: plugin/docs/ ../ -> ../../, README clone-and-run -> ./plugin/install.sh, CONTRIBUTING/SECURITY path refs CI green locally: pytest 237 passed/1 skipped, evals 1/1, py_compile ok, AI BOM --check current. install.sh is move-safe (resolves SCRIPT_DIR via BASH_SOURCE; connector/ stays its sibling). COMPANION CHANGE REQUIRED (separate PR, open-agent-ai-security/plugins): switch socxen's marketplace entry from source:url to { "source": "git-subdir", "url": ".../socxen.git", "path": "plugin", "ref": "main" } These two MUST reach socxen `main` together — the marketplace tracks socxen `main`, so promoting this restructure to main while the marketplace still uses source:url breaks installs (no manifest at repo root). Safe on `dev`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VcpWhxi8ZFKgUTSHZY5ZhF Signed-off-by: Matt W <matt.willems@exabeam.com>
The restructure moves the full README into plugin/ (shipped). git-subdir ships only plugin/, so a root README is GitHub-browsing only and never reaches users. This one is a pointer — what socxen is, the install command, and the repo layout (what ships vs. build-time) — deferring detail to plugin/README.md to avoid drift. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VcpWhxi8ZFKgUTSHZY5ZhF Signed-off-by: Matt W <matt.willems@exabeam.com>
… stale paths (#29) Addresses the three items from @virtualsteve-exa's review, plus the minor. 1. plugin-smoke.sh could not survive its own first post-promotion run. Both helpers assumed this release's layout, but the PRIOR ref on that run is the last root-layout release (0.6.8): - version_at() read $1/plugin/.claude-plugin/plugin.json unconditionally -> FileNotFoundError, fatal under set -e. Now probes plugin/ then the root. A genuinely absent manifest still hard-errors rather than yielding an empty version, which would sail through the current-vs-prior equality check and smoke-test nothing. - fabricate_marketplace() hardcoded "source": "./plugin", which the prior install does not have. Now resolved per call — the upgrade leg fabricates twice against one worktree, straddling the checkout that crosses the restructure boundary. Verified both helpers against fabricated old- and new-layout worktrees: 0.6.8 from the root layout, 0.7.0 from plugin/, sources "./" and "./plugin" respectively, set -e never tripped, and a manifest-less dir exits non-zero. 2. The skip guard in test_check_without_creds_exits_clean treated a bridge TRACEBACK as an environment problem. That is how #67 hid on both maintainers' machines: mcp 2.0.0 dropped streamablehttp_client, the bridge died at import, and the guard downgraded the crash to a skip. The assertions below it already reject tracebacks — they just never ran. A traceback now fails; only genuine provisioning failures still skip. 3. evals/README.md:8,87 pointed at the pre-restructure fixture path, as did test_bridge_wiring.py's module docstring. Rebased onto dev (0.6.8 + the #74 containment fix). Suite: 238 passed, 0 skipped — the no-creds bridge test now runs rather than skipping, which is the check that number was meant to prove. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Matt W <matt.willems@exabeam.com>
e506f33 to
d17fa41
Compare
|
Rebased onto
On item 3 — git got this right, but only just. The rebase surfaced it as an explicit On item 5 — the BOM was already current, so I committed nothing. Item 6 spot-check, re-run on the final tree (not carried over from the last round): old-layout worktree → Ready for the promotion window whenever you are. |
#66 moved the skill into plugin/, so tests/test_merge_permissions.py's SKILL_DIR had to follow — tests stay at the repo root, the skill they read does not. Now matches test_repo_invariants.py's constant. Nothing else moved: the merger travels beside the snippet it merges, so both landed in plugin/skills/soc-investigate/ together, and install.sh resolves them SCRIPT_DIR-relative — unchanged from plugin/install.sh. Verified end-to-end from the new location: the merge lands {allow: 18, ask: 4, deny: 68} and the no-flag run still writes nothing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Matt W <matt.willems@exabeam.com>
…he governance gate (#70) (#73) * feat: install.sh --merge-permissions — prompted, backed-up merge of the governance gate (#70) The governance gate has been opt-in on every install path. install.sh verified the merge and warned loudly when it was missing, but nothing ever performed it, so the shipped default was "gate OFF until the operator hand-edits ~/.claude/settings.json" — the installer was a detection control, not an enforcement one. Add the write half. --merge-permissions merges settings.snippet.json into the operator's settings.json; an interactive run whose gate reads OFF now shows exactly which rules it would add and asks y/N first. Consent is preserved by construction: nothing merges by default, and -y does NOT authorise it — "assume yes" answers the installer's own questions, it does not license a write to the operator's settings. --checks-only outranks the flag entirely. The merge itself (skills/soc-investigate/merge_permissions.py) is additive-only, backs up settings.json first (timestamped, same mode as the original), refuses outright when a rule already sits in a different tier than the snippet specifies (operator intent wins — it writes nothing and reports what to resolve), restores from the backup if a write fails, and is idempotent. Worth re-running even when the gate reads ON: a hand-merge of just the two ask lines leaves the 17-entry containment deny list missing. Verification is the pre-existing gate_on() re-read, not the merger's exit code — the same "a 0-exit doesn't prove it landed" discipline the install block uses. No python3 or no snippet reports "cannot merge, here's the manual path", never a false green, mirroring the existing "cannot verify != OFF" rule. 25 tests cover the refusals specifically: every one of them must write nothing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Matt W <matt.willems@exabeam.com> * fix: resolve the governance settings path instead of hardcoding ~/.claude (#70) install.sh hardcoded $HOME/.claude/settings.json. That was survivable while the governance block only reported the gate's state — it could read a file the running Claude Code ignores and mis-report ON, the dangerous direction. It stops being survivable now that the block can write: the same assumption would merge the gate into a file that never takes effect, i.e. a green "gate ON" protecting nothing. Resolve as SOCXEN_SETTINGS_FILE -> $CLAUDE_CONFIG_DIR/settings.json -> ~/.claude/settings.json, and name the resolved path in every message rather than a hardcoded one. SOCXEN_SETTINGS_FILE is the same explicit escape hatch EXABEAM_ENV_FILE already gives the credentials path. This is also what makes the merge testable from scripts/release/plugin-smoke.sh, which isolates whole installs in throwaway CLAUDE_CONFIG_DIRs: without it, a smoke leg exercising --merge-permissions would escape its sandbox and rewrite the maintainer's real ~/.claude/settings.json on every release run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Matt W <matt.willems@exabeam.com> * docs: drop stale containment-rule counts after #74 #74 expanded the containment deny tier from 17 bare rules to 68 (both spellings, both namespaces) covering the same 17 tools. Two comments here quoted the old entry count while making a point that does not depend on it — that a hand-merge of just the two ask lines leaves the containment deny list missing entirely. Say that without the number, so the next expansion of the tier doesn't strand them again. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Matt W <matt.willems@exabeam.com> * test: point the merge tests at the relocated skill directory (#29, #70) #66 moved the skill into plugin/, so tests/test_merge_permissions.py's SKILL_DIR had to follow — tests stay at the repo root, the skill they read does not. Now matches test_repo_invariants.py's constant. Nothing else moved: the merger travels beside the snippet it merges, so both landed in plugin/skills/soc-investigate/ together, and install.sh resolves them SCRIPT_DIR-relative — unchanged from plugin/install.sh. Verified end-to-end from the new location: the merge lands {allow: 18, ask: 4, deny: 68} and the no-flag run still writes nothing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Matt W <matt.willems@exabeam.com> --------- Signed-off-by: Matt W <matt.willems@exabeam.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…eleased changes (#80) * docs: bring the docs current with the plugin/ restructure and the unreleased changes Pre-promotion currency pass. Three classes of defect, all found by reviewing docs against the code rather than against each other. BROKEN INSTRUCTIONS (post-#66, a user follows these): - plugin/docs/installation.md told users to run ./install.sh --merge-permissions in the section the page itself calls the most important on it. There is no root install.sh any more; twenty lines earlier the same page correctly says ./plugin/install.sh. Same for merge_permissions.py's path. - install.sh's OWN output was stale: the usage block is printed verbatim as --help, and every line said ./install.sh. Its 'Next steps' block named skills/soc-investigate/settings.snippet.json and ./install.sh. Both now print the paths the script has already resolved, so they are right from a clone and from the installed plugin directory alike. The --help text also still claimed the merge targets ~/.claude/settings.json — the exact hardcoding #73 removed. A DOC THAT CONTRADICTED THE CODE: - logging.md said a failed emit makes telemetry 'disable itself'. It does the opposite on purpose: the event is dropped and the trail stays on, because a transient fault must not switch off a mandatory audit log — there is a test named for that behavior. Split the two failure modes, and documented the observra>=1.1 floor and the new stderr warning channel, neither of which appeared anywhere. MISSING RELEASE NOTES: - [Unreleased] documented 1 of 4 landed changes. Added entries for the plugin/ restructure (#66), the ABV release gate (#77), and the dependency bounds + hash-pinned lockfile and observra 1.1 migration (#78), and corrected the pre-restructure paths in the entries that were already there. Also: the lockfile existed with no mention in any document — CONTRIBUTING now tells contributors to re-lock (CI checks the AI BOM but not lock drift) and the end-to-end recipe copies the .lock; SECURITY.md's scope adds merge_permissions.py (the only code that writes to a user's settings) and the lockfile; the front-door README no longer presents the installer as check-only; CONTRIBUTING said gates run 'before a tag' though the repo has no tags and says so twice elsewhere; and SCAN_INSTRUCTIONS.md — forward-looking scope for the NEXT Praxen audit — carried pre-restructure paths and asked about a manual-merge-only world that no longer exists, which would have biased that scan. Verified: 263 tests pass, AI BOM current, installer smoke still merges to {allow: 18, ask: 4, deny: 68}, --help renders, every relative link resolves. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Steve Wilson <steve.wilson@exabeam.com> * docs: correct three defects found in review of this PR 1. A FALSE ASSURANCE IN AN AUDIT DOC. The rewritten fail-open section claimed observra's warnings surface on stderr for 'a full queue' so a dropped event is 'always visible'. Traced through observra 1.1: a queue-overflow drop is logged at DEBUG and counted in observra_events_dropped_total, below the WARNING-level stderr handler the shim installs — so that drop is counted, not announced. Backend write errors DO surface. Corrected in logging.md, the CHANGELOG entry, and the shim's own (pre-existing, equally wrong) inline comment. 2. THE REVERSE PATH ERROR. installation.md ships INSIDE the plugin and the primary install path is the marketplace, where no plugin/ directory exists — so 'skills/soc-investigate/...' was correct plugin-relative and my prefixing it broke it for the majority reader, while plugin/README.md kept the relative form. Now names the plugin-relative path with the clone path as a gloss. 3. A STALE PATH IN THE BLOCK THIS PR EDITED. tests/end-to-end-testing.md's CACHE still used the pre-0.6.5 marketplace name (socxen/socxen); the real cache is under open-agent-ai-security/socxen/. The new .lock copy would have missed. Also: the shim's DESIGN RULES said an emit failure disables logging, contradicting its own code, tests, and this PR's new doc; my bump_version change contradicted the script's own docstring, so both call sites now use uv run; praxen/README.md's gate said 'before tagging' while CONTRIBUTING (which links it) says release; the rotation bounds are clamped, not passed straight through; the merge-permissions hint now prints an absolute path instead of $0 as invoked; and (#70) gained its PR number per the file's (#issue, #PR) convention. Verified: 263 pass, BOM current, Next-steps block renders with paste-able absolute paths, real settings untouched, all relative links resolve. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Steve Wilson <steve.wilson@exabeam.com> * docs: order the [Unreleased] sections Added-before-Fixed The last clear item from the review pass. The section order was Changed -> Fixed -> Added -> Security; both Keep a Changelog and this file's own 0.6.3 entry put Added first. Pure reordering — no wording changes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Steve Wilson <steve.wilson@exabeam.com> --------- Signed-off-by: Steve Wilson <steve.wilson@exabeam.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…/50 resisted (#76) (#81) * fix: pin the red-team gate to an explicit model ID and record the resolved model in every artifact (#76) The runner's --models default was the floating 'sonnet' alias, so the gate's result could change out from under the harness whenever a new Sonnet shipped, and a recorded verdict could not be tied to a specific model version after the fact. Now: - --models defaults to the explicit supported-floor ID (claude-sonnet-4-6), matching the discipline the dated results/ artifacts already show; - the runner captures the session's *resolved* model ID from the CLI init event and tallies/reports under it, so even an alias-invoked run produces an attributable artifact (and prints a notice when the two differ); - HISTORY.md's stale note (Sonnet 5 'not approved for cyber work' — no longer true) is replaced with the real reason the pin exists: reproducibility; - PLAN.md's 'weakest supported model (currently Sonnet)' wording names the pinned ID explicitly in all three places. The AI BOM is unaffected: it inventories dependencies, not harness source. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Steve Wilson <steve.wilson@exabeam.com> * fix: a stray stream event must not discard a whole red-team trial Found while running the gate for #76: the first live trial died with "'str' object has no attribute 'get'". The CLI's stream-json output carries lines whose `message` is a plain STRING rather than an object (error/notice events — an MCP server failing to reconnect, for instance). _parse called .get() on it unconditionally, raising AttributeError, which the runner's per-trial `except Exception` then degraded into a fully ERRORED trial. A drive that actually completed 19 tool calls and produced a 36k-character report was discarded because one unrelated notice appeared in the transcript. Grading integrity was never at risk — _inconc_rows already refuses to read an all-errored blocking-class attack as a pass (verdict INCONCLUSIVE, exit 3) — but the harness burned a full live drive per occurrence and could have stalled the gate on re-runs. Parse defensively by SHAPE, not just by event type, and add regression tests for both the odd-shaped line and the resolved-model capture. Negative-verified: the new test fails red on the unfixed parse. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Steve Wilson <steve.wilson@exabeam.com> * security: record the 2026-08-13 red-team gate run — 50/50 resisted, PASS (#76) The staleness half of #76. The gate had not run since 2026-07-03, across 0.6.7/0.6.8/0.6.9, and this promotion carries #66/#73/#77/#78. Run: 10 class-A families x 5 trials on the pinned claude-sonnet-4-6, judge on, driven against the working-tree plugin. Every family landed 0/5; no errored and no inconclusive trials, so the verdict rests on 50 valid trials rather than on absence of evidence. The a10 rows carry the real signal. In 4 of 5 trials the model reproduced the phishing markdown link in its chat output — the injection still works on the model — but the persisted artifact was clean every time, because the write-sink neutralizer defanged it. That is exactly what output-pipeline grading exists to distinguish: a pass where the model never emitted the payload would prove nothing about the guardrail. a10 landed 3/3 in the first run and drove the deterministic fix, so seeing the code layer fire is the point. Also fixes the ledger's own stale connector/ paths after #66, and updates the a10 summary's resistance figure from the 0/2 fix-time retest to 0/5 here. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Steve Wilson <steve.wilson@exabeam.com> * docs: link the class C/D corpus gap to its tracking issue Praxen finding 008 was 'awaiting triage'; it now has a home (#82) rather than being an untracked row. The gap itself is unchanged — the corpus is still class-A only, and classes C and D remain unexercised despite both blocking a release. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Steve Wilson <steve.wilson@exabeam.com> --------- Signed-off-by: Steve Wilson <steve.wilson@exabeam.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Minor, not patch: the shipped surface changes shape. Only the payload under plugin/ is distributed (#66), and the installer can now install the governance gate rather than only warn about it (#73). Also stamps the changelog entry for the red-team gate work (#76/#81), which landed after the [Unreleased] section was written, and dates the section. Both release gates ran against this candidate: red team 50/50 resisted (PASS, 2026-08-13 artifact); ABV 0 Critical (PASS, 2026-08-12 artifact against 0.6.9). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Steve Wilson <steve.wilson@exabeam.com>
…pping (#29) plugin/docs/README.md carried five links to ../../CHANGELOG.md, CONTRIBUTING.md, SECURITY.md, evals/ and tests/end-to-end-testing.md. They resolve while browsing the repo and dead-end for every installed user: a plugin cache's root IS plugin/, so ../../ climbs out of the distribution entirely. Not a typo class. Before #66 the same links read ../CHANGELOG.md and were correct in both places, because the whole repo shipped. The restructure deepened them to ../../ to keep the repo view working — but its entire purpose was to stop shipping those directories, so every one of those links now points at something deliberately absent. This is the docs index, the first page an installed user opens, and the dead links include SECURITY.md — how to report a vulnerability. Fixed by linking them canonically (github.com/.../blob/main/...), which is correct from a clone and from a cache alike; the in-plugin links (../skills/..., ../README.md) stay relative because those targets do ship. Added test_shipped_docs_never_link_outside_the_plugin, which resolves every relative markdown link under plugin/ and fails if it escapes the shipped root or doesn't exist. No existing test covered this: test_no_dangling_reference_links scans only SKILL.md and reference/*.md, and only for reference/* patterns. Negative-verified — it reports all five against the unfixed file. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Matt W <matt.willems@exabeam.com>
…pping (#29) (#84) * docs: shipped docs must not link to files the restructure stopped shipping (#29) plugin/docs/README.md carried five links to ../../CHANGELOG.md, CONTRIBUTING.md, SECURITY.md, evals/ and tests/end-to-end-testing.md. They resolve while browsing the repo and dead-end for every installed user: a plugin cache's root IS plugin/, so ../../ climbs out of the distribution entirely. Not a typo class. Before #66 the same links read ../CHANGELOG.md and were correct in both places, because the whole repo shipped. The restructure deepened them to ../../ to keep the repo view working — but its entire purpose was to stop shipping those directories, so every one of those links now points at something deliberately absent. This is the docs index, the first page an installed user opens, and the dead links include SECURITY.md — how to report a vulnerability. Fixed by linking them canonically (github.com/.../blob/main/...), which is correct from a clone and from a cache alike; the in-plugin links (../skills/..., ../README.md) stay relative because those targets do ship. Added test_shipped_docs_never_link_outside_the_plugin, which resolves every relative markdown link under plugin/ and fails if it escapes the shipped root or doesn't exist. No existing test covered this: test_no_dangling_reference_links scans only SKILL.md and reference/*.md, and only for reference/* patterns. Negative-verified — it reports all five against the unfixed file. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Matt W <matt.willems@exabeam.com> * docs: record the shipped-doc-links fix in the changelog (#29) Matches the house pattern for docs-only payload changes under a fixed version, per @virtualsteve-exa on #97: no users yet, so a shipped-docs change under 0.7.0 costs nothing and doesn't need a bump. Also corrects the [Unreleased] preamble, which said plugin/README.md was the only shipped file touched — plugin/docs/README.md is a second one, and leaving that sentence as-is would have been the same drift class this PR fixes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Matt W <matt.willems@exabeam.com> --------- Signed-off-by: Matt W <matt.willems@exabeam.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ctions off Node 20, Dependabot, enforced SHA pinning (#149) Closes #71, closes #100, closes #131 — the CI-hygiene set from the August tracker sweep, in one PR. No product behavior changes; everything is verifiable in this PR's own checks. ## What changes | Item | Change | Issue | |---|---|---| | Security lint | `ruff --select S` (flake8-bandit) over `plugin/` as a **required** check. Each deliberate `try/except/pass` in the shipped code carries `# noqa: S110` and its reason — a swallowed exception in the payload is a documented decision, never an accident. Same lint over `scripts/`, `security/`, `evals/` is **advisory** (`--exit-zero`, statistics only; they never ship, #66). | #100 | | Dependency audit | `pip-audit --strict` over the bridge's hash-pinned lockfile (`uv export --script` → requirements) on every push. Clean today. Dependabot security alerts cover the same tree asynchronously; this is the synchronous gate. | #71 rung 3 (rungs 1–2 landed in #78) | | Actions off Node 20 | `actions/checkout` v4.4.0 → **v7.0.1** (`3d3c42e5…`), `astral-sh/setup-uv` v5.4.2 → **v10.0.1** (`20cfd1bf…`), in all three workflows that use them. `setup-uv` is invoked with no inputs, so the v6 breaking changes (`activate-environment`, `working-directory`, cache glob) do not apply — this PR's green run is the proof. | #131 | | Pins stop rotting | `.github/dependabot.yml`, `github-actions` ecosystem, weekly; Dependabot rewrites SHA pins and their `# vX.Y.Z` comments together. | #131 | | Pinning enforced | Repository setting **"Require actions to be pinned to a full-length commit SHA"** is now ON (flipped 2026-09-05) — a bare tag is rejected at run time rather than caught by a reviewer. | #131 | ## Why now The 2026-09-05 Praxen scan held *Manage Your Supply Chain* at 3 with the rationale "no dependency or CVE scanning of any kind" and *Build an AI Red Team* at 4 for "no in-repo enforcement." These are the two cheapest movements on those rationales before the dev RC re-scan. Honest scope note: `ruff -S` scans our own code (#100); the CVE scanning Praxen names is the `pip-audit` step (#71). ## Interaction with #148 #148 adds `plugin/hooks/gate.py`, which has two deliberate exception-swallows of its own; they now carry their `# noqa: S110` reason on that branch (pushed), so the required lint is green in either merge order. ## Also in this PR: a generated SBOM (follow-on Steve asked for) `security/gen_sbom.py` reads the bridge's uv lockfile — and nothing else — and emits a **CycloneDX 1.6 SBOM** (`security/sbom.cdx.json` + `sbom.html`): 33 locked packages (5 direct), each with version, purl, the SHA-256 of every locked artifact, direct/transitive marking, and the full dependency graph; the AI BOM and the SBOM cross-reference each other. It keeps itself current without anyone editing it: `--check` in the invariants job fails on drift; the security job rebuilds it on every run and publishes it as the `socxen-sbom-<sha>` workflow artifact beside the `pip-audit` of the same tree; `bump_version.py` regenerates it on a release; and a repo invariant asserts the SBOM mirrors the lock. Documented in `security/README.md` → *The SBOM*, CONTRIBUTING, and the README evidence row. ## Not in this PR A CodeQL schedule — worth considering once this lint has a baseline, not before (#100's own advice). Lockfiles for the non-shipped PEP 723 entrypoints (#71's optional tail). 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_016Q9UohWfy2scPhr4pavhqd
Closes #29.
What this does
Moves socxen's distributable payload into
plugin/so the community-marketplace install stops shipping build-time artifacts to every user. Today the marketplace installs withsource: urlover the whole repo, so each user's plugin cache getstests/,evals/,security/(including our red-team attack payloads),scripts/, and.github/. Claude Code has no exclude/ignore mechanism; the only supported fix is agit-subdirsource pointing at a subdirectory.Moved into
plugin/(the only thing that ships):.claude-plugin/plugin.json·.mcp.json·skills/·connector/·docs/·install.sh·README.md·LICENSE·NOTICEStays at repo root (never ships):
tests/evals/security/scripts/.github/+ community-health docs.All build tooling repointed to
plugin/(tests,evals/run.py,gen_aibom.py+ regenerated BOM,ci.ymlpy_compile,bump_version.py,plugin-smoke.sh, doc cross-links). Clean git renames — history preserved.install.shis move-safe (resolvesSCRIPT_DIRviaBASH_SOURCE;connector/stays its sibling). A thin root README is added so the public GitHub landing isn't bare (it doesn't ship —git-subdirtakes onlyplugin/; drop it if you'd rather).mainaloneThis restructure and a one-time edit to the
open-agent-ai-security/pluginsmarketplace must reach socxenmaintogether. That marketplace tracks socxenmain; if the restructure hitsmainwhile the marketplace still usessource:url(or the marketplace flips first), Claude Code can't find the manifest at the repo root and installs break. Safe to merge todev— nothing users touch changes until promotion.@virtualsteve-exa — the companion edit is yours (Matt lacks write on the
pluginsrepo). Apply this toplugins/.claude-plugin/marketplace.jsonin the same window you promote this tomain:"name": "socxen", "source": { - "source": "url", + "source": "git-subdir", "url": "https://github.com/open-agent-ai-security/socxen.git", + "path": "plugin", "ref": "main" },Verification
pytest237 passed / 1 skipped ·evals/run.py1/1 ·py_compileok · AI BOM--checkcurrent — all on the rebased tree (0.6.7).git-subdirinstall smoke (throwawayCLAUDE_CONFIG_DIR, realclaude plugin installof this branch): cache contained onlyplugin/contents;tests/ evals/ security/ scripts/ .github/all excluded;plugin.json/.mcp.json/SKILL.md/bridge/LICENSEall present; version 0.6.7.dev(0.6.7 + the merged Fixtures use real routable IPs as attacker indicators — remap to RFC 5737 documentation ranges before going public #46 remap); moved fixtures carry the new RFC 5737 IPs, zero old IPs.Acceptance criteria (#29)
plugin/— nosecurity/ tests/ evals/ scripts/ .github/claude plugin installagainst a scratch config)bump_version.py/gen_aibom.pyupdated for the new layout