CoalMine is verified under the same framework as CoalTipple: all execution hooks follow the Phoenix-13 commandments, the plugin build (node scripts/build-plugin.mjs) is reproducible from source byte for byte (the claude.ai ZIPs are not: filesystem entry order and stored file times differ between builds), and security scans are event-driven, not run on every release (the last scan actually verified is pinned under Independent Scanning below).
Report a security issue in this repo through GitHub's private vulnerability reporting — Security → Report a vulnerability — never a public issue. In scope: everything this repo ships — the canary skills, the shipped hooks, the installer and other scripts/, the plugin/ dist, and the PowerShell fallback hooks and platform hook-config templates we publish for manual install. Out of scope: a vulnerability in a third-party skill or codebase a canary merely scans — report that to its own maintainer. This is a one-person-maintained project, with no fixed response-time SLA: expect the report to be read and acknowledged, triaged against the scope above, and disclosed once a fix ships — or, where we decide not to fix, told that and why. A public GitHub issue remains the right channel for an ordinary, non-security bug.
What an attacker needs. A repository you clone that carries a planted symbolic link (on Windows, a junction or symlink), FIFO or device file at a path CoalMine reads or writes. That is the whole precondition: no other access. The hooks fire on ordinary session events in that clone; install.mjs and configure.mjs act only when you run them there. No CVE id is claimed; none exists. Found by a blind automated security review (2026-09-24).
Affected versions. Every release from 1.0.0 (the first release; it has no git tag, its CHANGELOG.md heading is dated 2026-06-09) through v3.20.1. Because 1.0.0 is untagged, the exact commit a 1.0.0 user has cannot be identified, so 1.0.0 is stated by commit. 1.0.0 commits from 2aeb8de (2026-06-05, the first commit with an installer) carry the skills-directory write-through of defect 2. Commits from 2026-06-09 (112b0ce, 33013d3) carry every other 1.0.0 site: the hooks' language probe, the rules-file and git-hook writes, and verify.mjs <target>'s read. Only the 1.0.0 commits before 2aeb8de carry none, and they have no installer. The range was derived by walking versions, not tags: .claude-plugin/plugin.json history has 77 versions, CHANGELOG.md headings 76, and git tags 76. 1.0.0 is the only version with no tag; CHANGELOG.md has no 2.2.0 heading, though tag v2.2.0 and a plugin.json 2.2.0 exist (2.2.0 is inside the range, so no claim here is affected). Each per-site first version below is the first version whose tree contains that call shape, checked at the previous release too. The defects were measured on the earliest tree where each was executed (WSL Ubuntu, Node 24.19.0, a fake HOME, hooks under a 3 GB address-space cap and a 30 s timeout): a 2026-06-06 1.0.0 tree (27a542a, the skills-directory write; also 2aeb8de), the newest 1.0.0 tree (90d4b12), v2.0.0, v3.0.0 and v3.3.0. Fixed in: v3.20.2 (2026-09-24), by commit a4f0cff. If you use CoalMine, update: on Claude Code run claude plugin update coalmine@coalmine; if you installed to another agent with install.mjs, update your CoalMine checkout and re-run node scripts/install.mjs <agent>. v3.20.1 and every earlier release stay affected.
| What happens | A link to /dev/zero at AGENTS.md, MEMORY.md, README.md, a rules file or the project coalmine.json made the hook crash (std::bad_alloc, exit 134, measured on Linux). A FIFO at one of those paths, or a .claude/rules link to /, made the hook never return. On Windows a junction on .claude/rules made the conductor scan rule stamps outside the project (an outside stamp silenced the onboarding offer). Nothing is disclosed and no file is changed: the hook dies or hangs. |
| First affected | The stop hook's language probe reading <cwd>/AGENTS.md, MEMORY.md and README.md: 1.0.0 (crash measured on the newest 1.0.0 tree, v2.0.0 and v2.3.0; the probe is skipped when LANG already names Thai, Japanese, Chinese or Spanish). The stop and touch hooks reading the project .coalmine.json: v3.0.0 (crash measured; v2.8.0 exits normally). The conductor scanning AGENTS.md and .claude/rules/** for rule stamps: v3.7.5 (the onboarding-stamp scan: v3.11.2). verify.mjs <target> reading each installed SKILL.md: 1.0.0 (by source: an earlier draft of this entry said v2.2.1, from a search on an error-message string added later); its manifest check: v3.5.0. |
| What happens | With the Copilot instructions file (copilot-instructions.md, in the project's .github directory) linked to ~/.bashrc, install.mjs copilot appended CoalMine's own rules block, between COALMINE:START and COALMINE:END markers, to the shell rc and reported success (measured on v2.0.0 and v3.20.1). The attacker does not choose the appended text; it is CoalMine's template, but it lands in a file outside the project. The same write-through applied to an existing git hook (its bytes are replaced, and a hook that is a link had its target's bytes copied into the .pre-coalmine backup slot), the default project config, the manifest, and a link on any project-level agent folder install.mjs writes into, which carried the skills install and the default config outside the project. As of v3.20.1 that is nine folders (derived from scripts/lib/targets.mjs at 59ee1e7): .github, .agents, .claude, .gemini, .cursor, .windsurf, .junie, .kiro and .augment; earlier releases target a subset. Measured on v3.20.1 with .claude, .agents and .gemini each linked to an outside directory: the skills tree, its manifest and the default config landed there (and, for .agents and .gemini, the trigger rules file). |
| First affected | The platform rules file create and append: 1.0.0 (append measured on the newest 1.0.0 tree, v2.0.0 and v3.20.1); its rewrite on uninstall: v2.4.0. The git hook write: 1.0.0 (by source); its backup copy: v2.2.1. The skills directory install: 1.0.0, from commit 2aeb8de (2026-06-05), measured on a 2026-06-06 1.0.0 tree: with .github linked to a directory outside the project, install.mjs copilot wrote the whole skills tree there. The manifest write: v2.6.0. The default project config copy: v3.7.0. |
| What happens | With the project config linked to ~/.bashrc, configure.mjs treated it as malformed, copied its bytes into a .bak inside the repository, then overwrote the link target. Measured on v3.3.0 and v3.20.1: the rc file ended as { "language": "en" }, and the .bak held the original bytes, which a later git add -A in that clone could commit. |
| First affected | v3.3.0, the release that introduced configure.mjs. |
What the fix does. Reads of a repo-derived path go through readRepoFileBounded (in the three hooks and in scripts/lib/repo-fs.mjs). It does lstat first. A regular file proceeds. A symlink proceeds only when its realpath.native target lies inside the project and is a regular file (the v3.20.2 rule; v3.23.3 narrows it, see the follow-up record below). A FIFO, device, socket, directory, or a link that escapes the project or dangles is skipped before open. The open uses O_NONBLOCK, the descriptor is re-checked, and a file over its bound (MAX_CONFIG_BYTES = 1 MiB for configs, MAX_DOC_BYTES = 4 MiB for governance docs) is skipped, never truncated. The conductor's rule walk is capped at MAX_RULE_WALK_ENTRIES = 5000 entries and MAX_RULE_WALK_DEPTH = 16 levels. Writes go through writeRepoFile: the nearest existing ancestor must resolve inside the project, the target must not be a link, and the bytes go to a sibling temp opened with wx and are renamed into place. A refused write prints [refused] <path>: <reason> and exits 1. configure.mjs --global keeps its follow-through write to ~/.claude/.coalmine.json, because dotfile managers link that file; its read is bounded. The PowerShell fallback hooks refuse every reparse point (Test-CoalmineSafeFile), stricter than Node by design. Not covered, named: a regular file swapped in between the lstat and the open may lie outside the project (the descriptor re-check still holds the read to a bounded regular file); the write-side in-place fallback (Windows only in practice: it runs when the rename is refused with EPERM, EBUSY or EACCES, for a single-link regular file) — in v3.20.2 it checked the path before opening, so a link swapped in between the check and the open was followed; the release after v3.20.2 opens the target without truncating (O_NOFOLLOW and O_NONBLOCK where the platform has them, which is POSIX), checks isFile and nlink === 1 on the open descriptor, then writes through it, so a swapped-in link or FIFO is refused on POSIX, while on Windows, which has no O_NOFOLLOW, a symlink swapped in during that window is still followed (it must still resolve to a single-link regular file, and creating a symlink on Windows needs a privilege); an agent's own file reads through its tools are the host's permission system; and a tree delivered as an archive rather than a git clone can carry a planted .git/config with an outside core.hooksPath (or a .git file naming an outside gitdir). install.mjs treats a hooks directory outside the worktree as its own root, on the reasoning that git configuration chose it, and that reasoning does not hold for such a tree, so the installer may replace git hooks in a directory it should not. The bound: the bytes written are CoalMine's own fixed gate script, never attacker text, and an existing hook is first kept as <hook>.pre-coalmine.
Follow-up record, 2026-10-16 (v3.23.3, ticket CWK2-037; found by the sibling CoalLedger's sweep). The v3.20.2 rule above tested where a link's target lands, not whether a folder on the way to the file was itself a link. A link (a Windows junction or a POSIX symlink) planted at a folder of the project-config chain (.claude, .claude/coal, or any folder between the project root and the file) whose target was another folder inside the project was still followed, because the file inside it looked like a plain file. A planted in-project link could therefore redirect which in-project file the hooks, install.mjs or configure.mjs read as the config or rule file; it never reached outside the project. Fixed in v3.23.3: any link at any component between the project root and the file's own folder is refused wherever it points, and the file's own realpath must stay inside its own folder. The project root itself is not walked, so a project opened through a linked path still reads its own files. The surfaces are the three hooks and scripts/lib/repo-fs.mjs (used by install.mjs and configure.mjs); no rule file under a .claude/rules junction is read. Environments verified: the Windows junction legs ran locally; the POSIX symlink and file-symlink legs run on this release's CI matrix (ubuntu-latest and macos-latest, Node 22 and 24). Still not covered: a link swapped in between the check and the open. No new advisory is issued and no CVE id is claimed.
Follow-up record, 2026-10-17 (ticket CWK2-042; the write side of the 2026-10-16 record). A project write used to refuse a link only when it led outside the project, so a write through an in-project link at a folder of the chain (an in-project .claude junction or symlink) was made while the hooks refused to read the same file. Fixed in the release after v3.23.4: writeRepoFile and the pre-checks of configure.mjs and install.mjs (rules files, git hooks, the skills folder, the manifest, uninstall) refuse a link at any component between the project root and the file's folder, wherever it points, and make missing folders one at a time, checking each; configure.mjs --global keeps its follow-through write. Environments verified: the Windows junction legs ran locally; the POSIX symlink and file-symlink legs run on the release's CI matrix. Still not covered: the check-then-make race, a local process swapping a folder for a link between the last check and the write. The window is narrow: checkRepoDirTarget refuses before anything is written and mkdirRepoParents lstat-checks each folder it makes, so what is left is the moment between the last check and the file's own write, and it needs a concurrent local process with write access to the project. No new advisory is issued and no CVE id is claimed.
What to check if you ran CoalMine in a clone you did not write.
- A stray
.bakbeside a CoalMine config:<project>/.claude/coal/coalmine.json.bakor<project>/.coalmine.json.bak. Its contents are the file the config path was linked to, not a config. Delete it and do not commit it. - A
COALMINE:STARTblock in a file outside the repository, for example a shell rc:grep -l "COALMINE:START" ~/.bashrc ~/.zshrc ~/.profile. Remove the block. Also compare any hook or config fileinstall.mjsorconfigure.mjsreported writing against what you expect, since those writes replace the file. A git hook replaced by CoalMine carries# Generated by CoalMine, and the hook it replaced is kept beside it as<hook>.pre-coalmine. - A stray CoalMine install in the directory a project-level agent folder pointed at.
install.mjswrites CoalMine's skill folders, and a default config, into the project's agent folder; if any of.github,.agents,.claude,.gemini,.cursor,.windsurf,.junie,.kiroor.augment(the nine as of v3.20.1; earlier releases target a subset) in that clone was a link to a directory elsewhere, the install landed in the link's target. Look there for askillsdirectory of CoalMine skill folders (each holding aSKILL.md, with a.coalmine-manifest.jsonbeside them), for acoalmine.jsoninside acoaldirectory (the default config, written into a linked.claude,.agentsor.gemini), and for acoalmine-triggerfile inside arulesdirectory. Remove what you did not mean to put there. The skills write-through is measured on 1.0.0 trees from2aeb8de(2026-06-05) and applies to every release through v3.20.1; the default config and trigger file were measured on v3.20.1. - Links in a clone you already ran a tool in:
find . -not -path './.git/*' \( -type l -o -type p \)(POSIX) orGet-ChildItem -Recurse -Force | Where-Object { $_.Attributes -band 'ReparsePoint' }(PowerShell) — a link at a path CoalMine reads or writes is the tell.
Every release tag and maintainer commit is SSH-signed (gpg.format=ssh); GitHub shows the Verified badge on them. Automated Dependabot / CI commits are not signed with the maintainer key (GitHub signs these with its own), so verify a signed release tag — the artifact a release consumer trusts:
echo "* ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEtqTWGKhX1Dk9nZP8ns13Wl5zsO1Cz3VlTS6m1p2fP9" > coalmine_signers
git config gpg.ssh.allowedSignersFile ./coalmine_signers
git tag -v "$(git describe --tags --abbrev=0)"The plugin/ distribution directory is generated output gated by checks:
- Pre-commit/Pre-push Gates:
node scripts/verify.mjsautomatically re-renders skills from source and byte-compares the committed output to prevent drift. - Reproducible Builds: Any user can clone, run
node scripts/build-plugin.mjs, and verify the output is byte-identical. - Test Suite:
node --testruns the zero-dependency unit and hermetic-hook tests (an explicit file list, wired into the git hooks).
CoalMine is evaluated against NVIDIA SkillSpector, run locally via uvx (no install). Score 100/100 (CRITICAL static)—driven by consent-gated Self-Updating, which the static RA1 self-modification rule flags (×10) as a false positive; not a defect. 27 findings, every one read at its source line, none a defect: the 20 of the v3.20.2 scan (RA1 ×10 · RA2 ×3 · TM3 ×3, which match the word "world-writable" in comments describing a residual the code mitigates with 0o700 directories, 0o600 files and a symlink refusal · AR1 · AS1 · P2 · BH1, the scanner's own note that hooks/hooks.json registers hooks), plus seven that first appear between v3.20.2 and v3.22.1: one more AS1 (commands/update.md:29) and one AS3 (README.md:35), both documentation of a read the plugin already did, and five more RA2 (one sentence of the plugin README and four comments). v3.22.2, v3.22.3, v3.23.0, v3.23.1, v3.23.2, v3.23.3 and v3.23.4 add none: the rule-and-file set and the matched source text are identical across the seven. The score is not comparable to the 95 recorded at v2.3.9: the previous dist (v3.8.4) re-scanned with v2.11.2 also scores 100, and the v3.20.2 dist re-scanned with the current scanner pin also scores 100 with the same 20 findings.
Scan provenance: SkillSpector v2.12.0 (self-reported version string; scan pinned to commit 2226747, upstream's untagged HEAD of 2026-09-30, 58 commits after upstream's tag v2.12.0; the v3.20.2 scan of 2026-09-24 was pinned to c7958a3, upstream's tag v2.12.0) · CoalMine v3.23.4 (commit eec6e15, the v3.23.4 tag; v3.22.3 5530ac1, v3.23.0 5c58b0a, v3.23.1 cc4314f, v3.23.2 1520dc0 and v3.23.3 b4799ca were scanned on the way) · 2026-10-17 · static stage (--no-llm). Static coverage was partial (44 of 49 files fully inspected): three hook files (hooks/coalmine-conductor.js, hooks/rot-canary-stop.js, hooks/rot-canary-touch.js) reached the scanner's parser span limit, hooks/hooks.json is opaque to it, skills/gold-standard/SKILL.md was reported as text it could not fully evaluate, and a runtime limit on one directory of the dist moves between scans (a timing artifact). Because the files that act are among those the scanner did not fully inspect, the whole v3.20.2 to v3.23.0 change was read by hand: the three hooks' hardening (an unusable project config value is dropped so the global value or the default decides; the session markers move into an owner-only coalmine/ directory under the temp folder, read bounded and non-blocking, appended with O_NOFOLLOW and replaced by a temp-file rename; stamp scans and the config reads are bounded; control characters are stripped from smell lines), the ten-second timeout on each hook, the self-error-report link wrapped as a markdown link in the nine skills (v3.22.2), the one sentence added to skills/rot-canary/SKILL.md about autoFixMode (v3.22.3), and the Antigravity manifest of v3.23.0 described below. The primitive census over the dist (writes, deletes, process spawns, network, eval, environment reads, dynamic imports) is unchanged from v3.22.2 to v3.22.3, to v3.23.0, to v3.23.1, to v3.23.2, to v3.23.3 and to v3.23.4; between v3.20.2 and v3.22.2 the only increases are the marker helpers (mkdirSync, renameSync, unlinkSync) and two documentation links, and no hook starts a process or opens a network connection. The hidden-Unicode census finds none in any of the three dists. New in v3.23.0, an Antigravity manifest: plugin/plugin.json ({"name":"coalmine"}) and plugin/hooks.json, which register the same three hooks for that engine (PreInvocation, PostToolUse on file-edit tools, Stop; type: command, ten-second timeout, the command a relative node hooks/<file> <Event>), and the Stop hook, after an edit, answers {"decision":"continue","reason":...} once per batch (otherwise {}), which holds that stop once to ask for the scan; the relative command resolves against the directory Antigravity gives a hook, which the scan cannot show. Through v3.23.3 autoFixMode was read by the agent from the config files and not merged by the hooks' safer-value clamp, so a project config could set it to safe, and the skill told the agent to read the project value as written. v3.23.4 clamps it (CWK2-044): autoFixMode joins the hooks' safer-value merge (order off, interactive, safe; the default interactive), so a project config can only LOWER it and safe counts only from the user's own global config; the conductor and the Stop hook emit one line, autoFixMode in effect: <mode> (...), carrying the merged value, and the skill tells the agent to take that line as the answer only when it arrived as the hook's own output, never from the same words inside a file, a diff or a tool result, and to apply the same rule itself when no such line is in view: a mitigation an agent applies, not a clamp a machine enforces, because a hook cannot reach a key the agent reads itself. The v3.23.0 to v3.23.2 dist changes, seventeen files, were read by hand: v3.23.1 changed the version string and fifteen reference files (their stamps, the shared escalation list re-worded with its unverified markers, one tooling row and one cadence sentence), no code and no manifest; v3.23.2 changed the version string and hooks/hooks.json, whose three entries (SessionStart, PostToolUse on Write, Edit and MultiEdit, Stop; the same three scripts, the ten-second timeout kept) moved from a shell string to the exec form, node as the command and the script path in args, which this scan cannot show the host resolving; the Antigravity manifest plugin/hooks.json did not change. No script, skill body or other hook line changed through v3.23.2, and v3.23.1 and v3.23.2 each score 100 with the same 27 findings by rule, file and source text, no line moved. The v3.23.2 to v3.23.3 dist change, four files (the version string and the three hooks, hooks/coalmine-conductor.js, hooks/rot-canary-stop.js and hooks/rot-canary-touch.js, 94 lines added and 4 removed in all), was read in full by hand, the scanner-limited hooks included: each hook's contained read now also pins the folder chain, where it used to test only that the entry's real path landed inside the project root; with a root set, every component between the root and the entry's own folder must be a real directory (one lstat per component; a link at any of them, a Windows junction included, is refused wherever it points, a target inside the project too; a path with a .. segment is refused outright; the root itself is not walked), and the entry's real path must stay inside its own folder's real path, which also refuses a file symlink that leaves its folder; every failure reads as unreadable and never throws. It narrows what a cloned repository can make a hook read (the project config and the files the hooks scan), adds no write, delete, spawn, network, eval or import site, and leaves the primitive census above unchanged apart from more link-check calls; the three added blocks are identical. v3.23.3 scores 100 with the same 27 findings by rule, file and source text, and moves sixteen of them by line shift only (the added lines sit above them). The v3.23.3 to v3.23.4 dist change, six files (the version string, README.md, the three hooks, hooks/coalmine-conductor.js, hooks/rot-canary-stop.js and hooks/rot-canary-touch.js, and skills/rot-canary/SKILL.md, 135 lines added and 29 removed), was read in full by hand, the scanner-limited hooks included: besides the autoFixMode clamp above (the loudest value, safe, is last in its order, a project value is only ever moved toward off, an absent or unusable global reads as interactive, and the emitted line can carry only one of the three enum values and fixed words, never repository text), the Stop hook, when Antigravity names the event on its command line, reads its stdin before the mode gates and takes the project from the payload's first workspace path (the touch hook and the conductor already did), where it had used the hook folder's project; the touch hook refuses to append a marker entry that would take its file past the read bound, dropping that entry silently so the list already written stays readable; and README.md and skills/rot-canary/SKILL.md state the clamp. It adds no write, delete, spawn, network, eval or import site, and the primitive census above is unchanged; v3.23.4 scores 100 with the same 27 findings by rule, file and source text, sixteen of them moved by line shift only. Scanning is event-driven (this repository's own version bump, or a genuinely new attack surface)—this pins the last version actually verified.
- Static Scan (100/100 - CRITICAL): 27 findings, none a defect on adjudication:
HIGH · RA1 Self-Modification×10 (commands/update.md:2+hooks/coalmine-conductor.jsat:534 :609 :616 :618 :620 :626 :854 :1002 :1061)—the series consent-gated Self-Updating: the hook only SCHEDULES (no network), the agent offers the platform's ownclaude plugin update; the skill never rewrites its own files.HIGH · AS1 Agent Snooping×2 (commands/stats.md:12,commands/update.md:29)—a read-onlygrepof the project's own rules home (.claude/rules/,.agents/rules/,AGENTS.md,STANDARDS.md) for CoalMine's freshness stamps, and the sentence describing the free local rule-freshness nudge that does the same; the stats command ends "Do not modify any file."HIGH · AR1 Anti-Refusal(commands/update.md:5)—matched "Always answer"; the sentence is "Always answer in the user's language"—a localization rule, not refuse-suppression.HIGH · P2 Hidden Instructions(skills/gold-standard/references/method.md:1)—the metadata rule-freshness stamp (an instruction-shaped HTML comment carrying no command or exfil directive).MED · TM3 Tool Misuse×3 (hooks/coalmine-conductor.js:926,hooks/rot-canary-stop.jsat:748 :806)—match the word "world-writable" inside comments naming the residual a third party could pre-create; the code mitigates it with0o700directories,0o600files and a symlink refusal.MED · RA2 Session Persistence×8 (README.md:21;hooks/coalmine-conductor.jsat:45 :592 :1061;hooks/rot-canary-stop.jsat:61 :1011×2;hooks/rot-canary-touch.js:43)—the README sentence naming what the hooks write (the OS temp folder's session markers and, under~/.claude/, the update-check date and the mode files you set yourself), comments describing the update-stamp / session-temp mechanism, and the stop hook's reason text that names the~/.claude/.rot-canary-offswitch; a throttle/kill-switch stamp, not an OS-persistence mechanism.MED · AS3 Agent Snooping(README.md:35)—the plugin README's disclosure thatgold-standardreads a project's memory or decision log only to verify it, quoting a sentence that is already inskills/gold-standard/SKILL.md; it writes there only after you choose the change at its choice gate.MED · BH1 Bundled Execution Surface(hooks/hooks.json:1)—the scanner's own standing note that a plugin registering lifecycle hooks should have its reach reviewed; the three registered hooks areSessionStart/PostToolUse[Write|Edit|MultiEdit]/Stop, alltype: commandwith a ten-second timeout, and the Antigravity manifest registers the same three.
- Method:
uvx --from git+https://github.com/NVIDIA/skillspector.git skillspector scan <plugin> --format json—uvx fetches its own ephemeral Python, so no manual Python/pip install is needed; a JSON report is written even when the optional LLM stage is skipped. - LLM Semantic Scan: not run this pass (
--no-llm—static-only is the documented, FP-prone baseline: pattern-match without the skill-contract context).
Security is built structurally. Every hook obeys the Phoenix-13 rules (zero-dependency, no network, no child processes, fail-silent, session cleanup). No data-exfiltration path exists.
The same three Node hooks also carry an Antigravity mode (selected by the event-name argument wired in platform-configs/hooks/antigravity-hooks.json): identical Phoenix-13 posture, output restricted to a single {"injectSteps":[{"ephemeralMessage":...}]} JSON line from the conductor (the current AG PreInvocation output contract; the Stop hook, after an edit, emits {"decision":"continue","reason":...}, which holds that stop once to ask for the scan, the same as Claude Code's block, and otherwise the no-op {}: it never does so after an error or max_steps_exceeded stop, nor a second time for a batch already acknowledged), and apart from that held stop, at most once per edit batch, CoalMine never blocks on Antigravity.