
Security News
Re-Enabled GitHub Actions Expose Thousands of Repositories to Mini Shai-Hulud
Two compromised GitHub Actions were re-enabled with malicious tags intact, exposing thousands of downstream repositories to Mini Shai-Hulud.
@codai/axiom-mcp
Advanced tools
AXIOM v2 MCP server (stdio) and CLI: validate, compile, check and transactionally apply content-addressed manifest bundles inside an allowlisted set of roots
MCP server (stdio or Streamable HTTP) and CLI for AXIOM v2 — the transactional write gate for coding agents:
Plan → canonical ManifestBundle → set-level checks → hash-gated two-phase apply → journal.
dist/cli.js (thin entry) + dist/cli-main.js (lazy-loaded engines; the MCP SDK v2 and zod are
bundled into the mcp-lazy / http-lazy chunks); no runtime dependencies.
npx @codai/axiom-mcp mcp --root /abs/path/to/repo # stdio MCP server
npx @codai/axiom-mcp mcp --root /abs/path/to/repo --http 127.0.0.1:3411 # Streamable HTTP at /mcp
npx @codai/axiom-mcp --help # CLI verbs
--root may repeat. Every tool root argument must equal or lie inside one of them; with exactly one
root it is the default. There is no env-var or cwd fallback (ERR_ROOT_REQUIRED / ERR_ROOT_NOT_ALLOWED).
--wire 2026|2025|2026-onlyBuilt on MCP TypeScript SDK v2 (@modelcontextprotocol/server 2.0.0). The server speaks the
2026-07-28 revision (no initialize, per-request _meta envelope, server/discover,
ttlMs/cacheScope on list results) and the 2025-era revisions (initialize handshake,
HTTP sessions) from the same entry — the SDK pins each stdio connection, or routes each HTTP
request, to the era the client opened with. --wire 2026 (default) and --wire 2025 both serve
both; --wire 2026-only refuses 2025 openings with the unsupported-protocol-version error. Clients
on SDK v1 need no change. Details: docs/reference/mcp-tools.md.
.vscode/mcp.json{
"servers": {
"axiom": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@codai/axiom-mcp", "mcp", "--root", "${workspaceFolder}"]
}
}
}
--http <host:port>axiom mcp --root /abs/repo --http 127.0.0.1:3411 # loopback, no token needed
axiom mcp --root /abs/repo --http 0 # random port; URL logged at info level
AXIOM_HTTP_TOKEN=$(openssl rand -hex 32) axiom mcp --root /abs/repo --http 0.0.0.0:3411 --log-level info
POST /mcp (2026-07-28 requests are served statelessly; a 2025 initialize opens a
session and returns Mcp-Session-Id, which every later 2025 request must send), GET /mcp
(standalone SSE stream, one per 2025 session), DELETE /mcp (close the session), GET /health →
{ ok, name, version } (unauthenticated). Anything else is 404 JSON.--http-token-env <NAME> (default AXIOM_HTTP_TOKEN, ≥ 16 chars). Clients send
Authorization: Bearer <token>; the compare is constant-time. A token is optional on loopback.Host must be <host>:<port>, localhost:<port>
or 127.0.0.1:<port>; otherwise 403). Request bodies over 4 MiB are 413.dist/http-lazy.js, loaded only with --http, and is plain node:http — no
express/hono at runtime.{ "type": "http", "url": "http://127.0.0.1:3411/mcp" }; add
"headers": { "Authorization": "Bearer ${input:axiom-token}" } when a token is set.Conformance: packages/conformance runs @modelcontextprotocol/conformance server against this transport
in CI with an expected-failures baseline (packages/conformance/baseline.yml).
claude_desktop_config.json{
"mcpServers": {
"axiom": { "command": "npx", "args": ["-y", "@codai/axiom-mcp", "mcp", "--root", "/abs/path/to/repo"] }
}
}
| tool | risk | input | output |
|---|---|---|---|
axiom_plan_validate | READ | { plan } | { ok, planDigest?, errors[] } |
axiom_plan_compile | ACT | { plan, store?: inline|cas, root? } | ManifestBundle (writes only under <root>/.axiom/ — CAS blobs and the stored manifest — when a root is given) |
axiom_manifest_verify | READ | { bundle, root? } | { ok, manifestDigest, canonical, signed, missing[], errors[], signatures?: { trustFile, keyids[], findings[], ok, code? } } — signatures only when root has .axiom/trust/keys.json; code is ERR_SIGNATURE_MISSING | ERR_SIGNATURE_INVALID when not ok |
axiom_check | READ | { bundle, profile?, root? } | CheckReport (verdict: pass|fail|error, preImage: verified|drifted|unverified) |
axiom_check_start | READ | { bundle, profile?, root? } | { taskId, tool, status: "working", pollIntervalMs, ttlMs, elapsedMs } — same evaluation as axiom_check, returned immediately as a task so long guard.external suites (up to 15 min per guard) outlive the client's per-call timeout (S-406 / D-24) |
axiom_task_get | READ | { taskId } | descriptor + result: CheckReport once completed, or error: { code, message } once failed/cancelled; unknown/expired id → ERR_TASK_NOT_FOUND |
axiom_task_cancel | ACT | { taskId } | descriptor; kills every running guard tree, task ends cancelled with ERR_TASK_CANCELLED (idempotent on terminal tasks) |
axiom_plan_begin | ACT | Plan header: { name, intent, profile?, capabilities?, checks?, counter?, metadata? } | { sessionId, artifacts: 0, bytes: 0, limits: { maxArtifacts, maxBytes }, ttlMs } — opens a chunked plan session for Plans whose JSON would exceed the 4 MiB call cap |
axiom_plan_add | ACT | { sessionId, artifacts[] } (each call ≤ 4 MiB) | session descriptor; duplicate path across chunks → ERR_INVALID_PLAN, over budget (2000 artifacts / 64 MiB) or sealed → ERR_PLAN_SESSION_STATE |
axiom_plan_seal | ACT | { sessionId, store?: inline|cas, root? } | ManifestBundle — compiled by the same code path as axiom_plan_compile, so the digest equals a one-shot compile of the assembled Plan (property-tested); the session is consumed |
axiom_apply_dry_run | READ | { bundle, root, profile? } | ApplyResult{mode:"dry-run", diff} |
axiom_apply | SENSITIVE | { bundle, root, profile?, confirmDigest } | ApplyResult |
axiom_rollback | SENSITIVE | { root, manifestDigest } | { status:"rolled-back", phase, steps } |
axiom_manifest_diff | READ | { a: bundle|"sha256:…", b } | { added[], removed[], changed[] } |
axiom_axm_parse | READ | { source } (.axm text) | { plan?, diagnostics: [{ severity, code, message, range: { start: {line, column}, end } }] } |
axiom_roots_list | READ | {} | { roots: [{ path, writable, hasGit }] } |
axiom_repo_snapshot | READ | { root?, include?[], exclude?[], maxFiles? (20000, cap 50000), maxBytes? (64 MiB), respectGitignore? (true), withContentDigest? (true) } | RepoSnapshot { snapshotDigest, body: { files: [{ path, bytes, sha256?, mode, kind }], truncated, counts } } — sorted, no timestamps/absolute paths; .git/, .axiom/ always skipped; symlinks recorded, never followed (docs/guides/snapshot.md) |
Every tool carries MCP annotations (readOnlyHint, destructiveHint, idempotentHint, openWorldHint)
and an outputSchema; structuredContent is the full result, content[0].text a small summary (digest,
verdict, counts, first 20 findings). Errors come back as isError: true with { code, message, path? }
from the closed ERROR_CODES enum — a handler never throws. spec/tools.json is generated from the same
registry (pnpm build:spec) and guarded by a parity test. spec/codai-tools.json is the same registry in
codai's packages/agent-core/spec/tools-v2.json entry shape ({ name, risk, description, parameters }) —
see docs/integration/codai.md.
Resources: axiom://manifest/{sha}, axiom://report/{sha}, axiom://applied/{sha},
axiom://profile/{name}, axiom://schema/{Plan|Manifest|ManifestBundle|CheckReport|ApplyResult|Profile|Journal|RepoSnapshot},
axiom://emitters (template emitters available to axiom_plan_compile — web@2.0.0, see docs/guides/emitters.md).
axiom_apply requires confirmDigest === bundle.manifestDigest — echo the digest you saw in dry-run.
Pre-apply checks run against the profile (default default, or <root>/.axiom/profiles/<name>.json);
a non-pass verdict aborts with ERR_CHECKS_FAILED before any write.ERR_BUNDLE_TOO_LARGE).ERR_EBUSY beyond that). Stopping
the server aborts every running task and kills its guard trees..axiom/lock makes apply single-writer per root; the journal makes it crash-safe and reversible.--log-level error|warn|info|debug, default warn).guard.external) are off unless the process is started with --allow-guards
and the profile sets facts.allowGuards: true. Relative commands must live under <root>/scripts/;
absolute ones must be listed exactly via --guard-allowlist <abs> (repeatable). Guards are spawned
with an args array (never a shell), a scrubbed environment, a wall-clock timeout, and must print
GuardOutput JSON — see docs/guides/checks.md.docs/guides/signing.md): a root can pin Ed25519 public keys in
.axiom/trust/keys.json; a profile with manifest.requireSigned then refuses unsigned, tampered or
untrusted bundles, and with antiRollback: true refuses any counter ≤ .axiom/trust/state.json#lastCounter.
axiom_apply advances that state only on status: "applied". Private keys never enter the server:
signing is axiom sign with AXIOM_SIGNING_KEY or --key-file.axiom mcp [--root <abs>]... [--allow-guards] [--guard-allowlist <abs>]... [--log-level warn]
[--http <host:port>] [--http-token-env AXIOM_HTTP_TOKEN]
axiom compile <plan.json> [-o out.json] [--store cas --root .] [--allow-net [--net-allow host[,host]]] [--allow-file]
axiom verify <bundle.json> [--root .] (--root: also verify signatures against .axiom/trust/keys.json)
axiom verify <bundle.json> --tree <root> [--pre] [--attest out.intoto.json] (tree matches manifest? docs/guides/verify-tree.md)
axiom check <bundle.json> --root . [--profile p] [--json] [--allow-guards] [--guard-allowlist <abs>]...
axiom apply <bundle.json> --root . [--dry-run] [--profile p] [--confirm <digest>] [--allow-guards] [--guard-allowlist <abs>]...
axiom rollback <digest> --root .
axiom gc --root . [--dry-run] [--older-than 30d] [--keep all-manifests|journal] (CAS garbage collection; CLI only, no MCP tool)
axiom diff <a.json> <b.json>
axiom schema <Plan|Manifest|ManifestBundle|CheckReport|ApplyResult|Profile|Journal|RepoSnapshot>
axiom emitters [--json]
axiom keygen [--out <dir>] [--name <label>] (ed25519; private key → <dir>/axiom-signing-<id>.key 0600, public entry → stdout)
axiom sign <bundle.json> [--key-file <path>] [-o out.json] [--root-id <id>] (key from --key-file or $AXIOM_SIGNING_KEY; --root-id = root-bound envelope)
axiom trust add <pub.json> --root . | remove <keyid> --root . | list --root . | root-id [<id>|--clear] --root .
axiom gate --stdin [--root <dir>] [--profile <file>] [--fail-open] [--no-shell-scan] [--no-root-discovery] [--log-level warn]
axiom migrate v1 <manifest.json> [-o plan.json] [--profile default] [--cas <root>] [--content <dir>] [--overwrite]
(v1 manifest → v2 Plan, lazy chunk; exit 1 = migrated with warnings — docs/guides/migrate.md)
axiom snapshot --root . [-o snap.json] [--include <glob>]... [--exclude <glob>]... [--max-files n] [--max-bytes n] [--no-gitignore] [--no-digest]
axiom snapshot-diff <a.json> <b.json> (RepoSnapshot → { added, removed, changed })
Exit codes: 0 ok · 1 verdict fail / apply failed · 2 usage or error. Non-mcp verbs print JSON to stdout.
ref sources ({ type: "ref", uri, digest }) are offline by default: a digest already in
<root>/.axiom/cas resolves without network, anything else is ERR_NET_DISABLED. --allow-net
fetches https: only (no redirects, 30 s timeout, 32 MiB cap), optionally restricted to
--net-allow hosts (*.example.com wildcards), verifies the pinned digest and stores the blob in
the CAS — a mismatch stores nothing (ERR_DIGEST_MISMATCH). apply never fetches. The MCP
axiom_plan_compile tool has no network switch. See docs/reference/plan-format.md
and docs/concepts/cas.md.
axiom gate --stdinA PreToolUse hook for Claude Code, Copilot CLI and VS Code agent hooks. It reads one harness
payload from stdin (both {tool_name, tool_input, cwd} and {toolName, toolArgs, cwd} casings;
toolArgs may be a JSON string), extracts the write target(s) of Write|Edit|MultiEdit|NotebookEdit,
create_file|replace_string_in_file|insert_edit_into_file|apply_patch|multi_replace_string_in_file|edit_notebook_file
and generic write|edit, scans shell tools (Bash, run_in_terminal, …) for write
primitives (>, >>, tee, rm, mv, cp, sed -i, git checkout|reset|clean, PowerShell
Set-Content/Remove-Item, … — a heuristic, documented in docs/getting-started/hooks.md), and runs only the
fast predicates: containment + RelPath rules (.., CON, NTFS ADS → ERR_CONTAINMENT /
ERR_PATH_*), path.deny, path.allow, content.noSecrets and content.maxBytes on the new
content when the payload carries it. Fail-closed (D-18): a non-answer is a deny.
| outcome | exit | stdout | stderr |
|---|---|---|---|
| allow / non-write, non-shell tool | 0 | — | — |
| deny | 2 | one object: hookSpecificOutput{…} (Claude) + flat permissionDecision/permissionDecisionReason (Copilot) + axiom{verdict, code, path, toolClass, standard:"owasp-acs/0.1"} | AXIOM GATE DENY <code>: <reason> (<relpath>) |
| write tool with no recognised path key | 2 | deny JSON | AXIOM GATE DENY ERR_UNSUPPORTED_OP: … |
| malformed payload, stdin timeout (2 s), internal error | 2 (fail closed) | deny JSON | AXIOM GATE DENY ERR_INTERNAL: … (fail-closed; pass --fail-open to allow) |
same, with --fail-open | 0 | — | AXIOM GATE WARN: … — failing open (--fail-open) |
Root = payload cwd, walked up to the nearest .git / repository .axiom/ ancestor (never the
home dir; --no-root-discovery disables), else --root, else the process cwd (the hook is the
one place where cwd is acceptable: the harness spawns the hook in the project directory and owns
that value). Relative targets stay relative to cwd.
Profile = --profile <file> → <root>/.axiom/gate-profile.json → ~/.axiom/gate-profile.json →
built-in { deny: [".git/**", ".axiom/**", "**/*.lock", "pnpm-lock.yaml", ".env", ".env.*", "**/node_modules/**"], noSecrets: true, pii: false }.
Schema: { deny: string[], allow?: string[], noSecrets: boolean, pii: boolean, maxBytes?: number } (strict).
pii: true additionally scans for personal data (cnp, email, phoneRo, card) — off by default
since S-414 (maintainer e-mails in pyproject.toml denied real edits; see docs/guides/checks.md).
gate is a separate lazy chunk (dist/gate-lazy.js, no MCP SDK): in-process p95 ≈ 5 ms per payload,
end-to-end ≈ 150–200 ms including node startup; check-gate-latency guards p95 ≤ 250 ms.
Wiring for each harness is in docs/getting-started/hooks.md.
FAQs
AXIOM v2 MCP server (stdio) and CLI: validate, compile, check and transactionally apply content-addressed manifest bundles inside an allowlisted set of roots
The npm package @codai/axiom-mcp receives a total of 0 weekly downloads. As such, @codai/axiom-mcp popularity was classified as not popular.
We found that @codai/axiom-mcp demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Security News
Two compromised GitHub Actions were re-enabled with malicious tags intact, exposing thousands of downstream repositories to Mini Shai-Hulud.

Research
/Security News
A malicious Firefox extension fetches its payload after installation to evade detection, steal Google session cookies, and automate account takeover.

Research
/Security News
The compromise affects MemTensor's MemOS, an open source memory framework for large language models (LLMs) and AI agents. Both npm package @memtensor/memos-cloud-openclaw-plugin and the PyPI package MemoryOS are compromised. They drop cross-platform Go binaries that exfiltrate developer secrets.