{
  "markdown": "# ShimGuard\n\n[![CI](https://github.com/RudrenduPaul/ShimGuard/actions/workflows/ci.yml/badge.svg)](https://github.com/RudrenduPaul/ShimGuard/actions/workflows/ci.yml)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)\n[![npm](https://img.shields.io/npm/v/shimguard-cli)](https://www.npmjs.com/package/shimguard-cli)\n[![PyPI version](https://img.shields.io/pypi/v/shimguard-cli.svg)](https://pypi.org/project/shimguard-cli/)\n\nVerify that a GitHub issue closed as \"fixed\" actually has a merged fix, before you trust the tracker.\n\n![Installing shimguard-cli from npm and running its first verify command against the real sybil-solutions/codex-shim repo, reporting 2 MISMATCH results](./docs/demo.gif)\n\n```bash\nnpx shimguard-cli verify sybil-solutions/codex-shim --issues 38,41,42,43,45,46\n```\n\nThat single command against the real `sybil-solutions/codex-shim` repo (1,000+\nstars) turns up 6 MISMATCH results: 6 security issues, each closed with a\n\"Fixed in PR #52\" comment, where PR #52 was never actually merged. The\nvulnerable code is still in `main` today. Nobody reading the closed issues\nwould know.\n\n## Contents\n\n- [Why this exists](#why-this-exists)\n- [Install](#install)\n- [Quickstart](#quickstart)\n- [CLI reference](#cli-reference)\n- [Library API](#library-api)\n- [How it compares](#how-it-compares)\n- [What is ShimGuard, and why does it exist](#what-is-shimguard-and-why-does-it-exist)\n- [FAQ](#faq)\n- [Contributing](#contributing)\n- [License](#license)\n\n## Why this exists\n\nReading an issue tracker, you trust two signals: the issue's `state` (open\nor closed) and the maintainer's closing comment (\"fixed in #N\"). Neither\nsignal is verified against reality by GitHub itself. A maintainer can close\nan issue citing a PR that never merged, an automated bot can close on a\n\"fixes #N\" keyword in a PR description before that PR lands, or a fix can\nget reverted after the issue was already closed. Any of these leaves a\ntracker saying \"fixed\" about a bug that is still live.\n\nShimGuard checks the one thing a human skimming issues does not: does the\nPR the tracker cites as the fix actually show `merged: true`? It is a small,\nmechanical, unambiguous check, not a heuristic or a guess.\n\n## Install\n\nShimGuard ships two independent, equally first-class packages -- pick\nwhichever fits your toolchain, or install both. Neither is deprecated in\nfavor of the other; both implement the same \"closed issue cites Fixed in PR\n#N, is #N actually merged\" check against the same GitHub REST API.\n\n```bash\n# npm -- JavaScript/TypeScript CLI + library\nnpm install -g shimguard-cli\n# or run it once with no install\nnpx shimguard-cli verify <owner>/<repo> --issues <numbers>\n\n# PyPI -- Python CLI + library (genuine port, not a wrapper around the Node binary)\npip install shimguard-cli\n```\n\nThe npm CLI requires Node.js 18 or later (uses the built-in `fetch` API).\nThe Python package's CLI entry point is also `shimguard` (e.g. `shimguard\nverify sybil-solutions/codex-shim --issues 45,46`); see\n[`python/README.md`](./python/README.md) and\n[docs/getting-started.md](./docs/getting-started.md) for the Python-specific\nwalkthrough, and [CHANGELOG.md](./CHANGELOG.md) for each distribution's\nversion history.\n\n## Quickstart\n\n```bash\nshimguard verify sybil-solutions/codex-shim --issues 38,41,42,43,45,46\n```\n\n```\nShimGuard v0.1 -- Tracker Verification: sybil-solutions/codex-shim\n\n[MISMATCH] Issue #45 \"_resolve_api_key silently falls back to Cursor API key for any model with an empty api_key, forwarding it to arbitrary upstream URLs\"\n  https://github.com/sybil-solutions/codex-shim/issues/45\n  Cited fix: PR #52 (open, not merged)\n  Issue is closed and cites PR #52 as the fix, but that PR is open and was never merged.\n\nSummary: 6 MISMATCH, 0 MATCH, 0 UNVERIFIED (6 checked)\n```\n\nExit code is `1` when any MISMATCH is found (useful for gating CI), `0` when\nevery checked issue's claimed fix actually merged, `2` on a usage or network\nerror.\n\nShimGuard is not just a gotcha finder: run it against a mix of issues and it\ncorrectly reports MATCH, UNVERIFIED, and MISMATCH side by side, exiting `0`\nwhenever nothing is provably broken -- exactly the signal a CI gate needs.\n\n![Running shimguard verify against a mix of issues in the real sybil-solutions/codex-shim repo: one MATCH (merged fix), one UNVERIFIED (no cited fix), exit code 0 since nothing is provably broken](./docs/demo-3-mixed-verdicts.gif)\n\n### Optional: verify the code, not just the merge status\n\nFor an even stronger check, point ShimGuard at the specific file and pattern\nan issue named as the vulnerable code:\n\n```bash\ncat > patterns.json <<'EOF'\n{\n  \"45\": { \"path\": \"codex_shim/settings.py\", \"pattern\": \"cursor_key_fallback\" }\n}\nEOF\n\nshimguard verify sybil-solutions/codex-shim --issues 45 --patterns patterns.json\n```\n\nIf the PR is merged but the cited pattern is still present in the file at\n`HEAD`, ShimGuard still reports `MISMATCH`: a merged PR does not guarantee\nthe specific vulnerable line was actually removed.\n\n> [!WARNING]\n> `pattern` is compiled as a JavaScript `RegExp`, and `path` is validated to\n> stay within the target repo (no `..` traversal to a different repo or API\n> endpoint). Only point `--patterns` at files you wrote or reviewed\n> yourself. See [SECURITY.md](./SECURITY.md).\n\n## CLI reference\n\n```\nUsage: shimguard [options] [command]\n\nVerify that GitHub issues closed as \"fixed\" actually have a merged fix. Catches\nsecurity issues marked fixed whose PR was never merged.\n\nOptions:\n  -V, --version            output the version number\n  -h, --help               display help for command\n\nCommands:\n  verify [options] <repo>  Check whether closed issues in a repo actually have\n                           a merged fix\n  help [command]           display help for command\n```\n\n```\nUsage: shimguard verify [options] <repo>\n\nCheck whether closed issues in a repo actually have a merged fix\n\nArguments:\n  repo                target repo as <owner>/<repo>, e.g.\n                      sybil-solutions/codex-shim\n\nOptions:\n  --issues <numbers>  comma-separated issue numbers to check, e.g. 38,41,42\n  --patterns <file>   JSON file mapping issue number -> {path, pattern} for an\n                      optional code-pattern check\n  --token <token>     GitHub token for higher API rate limits (defaults to\n                      $GITHUB_TOKEN)\n  --format <format>   output format: text or json (default: \"text\")\n  -h, --help          display help for command\n```\n\n`--format json` output is stable and designed for scripts and AI agents to\nparse directly:\n\n```json\n{\n  \"repo\": \"sybil-solutions/codex-shim\",\n  \"checked\": 1,\n  \"summary\": { \"mismatch\": 1, \"match\": 0, \"unverified\": 0 },\n  \"results\": [\n    {\n      \"issue\": { \"number\": 45, \"title\": \"...\", \"state\": \"closed\", \"htmlUrl\": \"...\" },\n      \"citedPullRequest\": { \"number\": 52, \"state\": \"open\", \"merged\": false, \"htmlUrl\": \"...\" },\n      \"patternCheck\": null,\n      \"verdict\": \"MISMATCH\",\n      \"reason\": \"Issue is closed and cites PR #52 as the fix, but that PR is open and was never merged.\"\n    }\n  ]\n}\n```\n\n![Running shimguard verify with --format json against the real sybil-solutions/codex-shim repo, printing the structured JSON verdict for issue 45](./docs/usage.gif)\n\n## MCP Server\n\nShimGuard ships a Model Context Protocol server, so an MCP-compatible agent\n(Claude Desktop, Claude Code, Cursor, or any other MCP client) can call it\nas a tool instead of shelling out to the CLI and parsing text. It's part of\nthe Python distribution:\n\n```bash\npip install \"shimguard-cli[mcp]\"\n```\n\nRegister it with an MCP client such as Claude Desktop:\n\n```json\n{\n  \"mcpServers\": {\n    \"shimguard\": {\n      \"command\": \"shimguard-mcp\"\n    }\n  }\n}\n```\n\nIt exposes a single tool, `run`, that takes the exact argv you'd pass to\nthe `shimguard` CLI and returns a structured result (`{returncode, stdout,\nstderr, json?}` on success, `{error: ...}` if the command failed, timed\nout, or exited non-zero). Example call:\n\n```\nrun(args=[\"verify\", \"sybil-solutions/codex-shim\", \"--issues\", \"45,46\", \"--format\", \"json\"])\n```\n\nwhich returns the same `--format json` report shown above, as a parsed\n`json` field alongside the raw `stdout`. Full details are in the\n[Python package README](./python/README.md#mcp-server).\n\n## Library API\n\nShimGuard's verification logic is also importable directly:\n\n```typescript\nimport { TrackerVerifier, RestGitHubClient, RegexPatternMatcher } from \"shimguard-cli\";\n\nconst client = new RestGitHubClient(process.env.GITHUB_TOKEN);\nconst verifier = new TrackerVerifier(client, new RegexPatternMatcher(client));\n\nconst result = await verifier.verify({ owner: \"sybil-solutions\", repo: \"codex-shim\", number: 45 });\nconsole.log(result.verdict); // \"MISMATCH\"\n```\n\n`TrackerVerifier` takes any `GitHubClient` and an optional `PatternMatcher`\n(see `src/types.ts`, `src/pattern-matcher.ts`), both are interfaces, so a\nfuture local-config scanner or a different code host can plug in without\nchanging the verifier itself.\n\nThe Python package exposes the same shape:\n\n```python\nfrom shimguard import TrackerVerifier, RestGitHubClient, RegexPatternMatcher, IssueRef\n\nclient = RestGitHubClient()  # or RestGitHubClient(token=os.environ[\"GITHUB_TOKEN\"])\nverifier = TrackerVerifier(client, RegexPatternMatcher(client))\n\nresult = verifier.verify(IssueRef(owner=\"sybil-solutions\", repo=\"codex-shim\", number=45))\nprint(result.verdict)  # \"MISMATCH\"\n```\n\n## How it compares\n\nNo existing open-source tool checks \"this issue tracker says fixed-in-PR-#N,\nis #N actually merged.\" That claim is verified by searching for\nissue-fix-verification tools, patch-verification tools, and\nsecurity-advisory-fix tools before writing this. The closest adjacent tools\nsolve different problems:\n\n| Tool | What it actually checks | Reads issue tracker / PR merge state? |\n|---|---|---|\n| **ShimGuard** | Does a GitHub issue's cited \"fixed in PR #N\" claim match PR #N's real merge state (and, optionally, is the cited code pattern gone from `HEAD`) | Yes, this is the entire check |\n| [gitleaks](https://github.com/gitleaks/gitleaks) / [trufflehog](https://github.com/trufflesecurity/trufflehog) | Secrets committed to source (API keys, tokens) | No, scans file content, not tracker state |\n| [trivy](https://github.com/aquasecurity/trivy) / [grype](https://github.com/anchore/grype) / [osv-scanner](https://github.com/google/osv-scanner) | Known CVEs in your dependency tree | No, scans a dependency manifest/lockfile, not tracker state |\n| [Vanir](https://github.com/google/vanir) (Google) | Whether a known CVE's code signature is still present in a target source tree | No, works from CVE-to-code, doesn't touch a GitHub issue tracker |\n| [VFCFinder](https://github.com/s3c2/vfcfinder) (NC State, ASIACCS 2024) | Finds a likely fix commit for an advisory that has *no* linked fix yet | Opposite direction: finds a missing citation, doesn't verify an existing one |\n\nThe gap ShimGuard fills is real, not theoretical. [wow-actions/auto-close-fixed-issues](https://github.com/wow-actions/auto-close-fixed-issues),\na GitHub Action used by other repos, closes an issue on a PR's `closed`\nevent, not its `merged` event: a bot can mark an issue \"fixed\" the moment\na PR is closed, whether or not it actually merged. GitHub's own native\n`Closes #N` keyword linking only auto-closes on a real merge to the default\nbranch, so this specific failure mode comes from manual maintainer comments\nand third-party automation, not GitHub's own defaults, which is exactly why\nnothing catches it after the fact.\n\n## What is ShimGuard, and why does it exist\n\nShimGuard is a CLI and library, shipped as both an npm package and a PyPI\npackage, that checks whether a GitHub issue's claimed fix (\"Fixed in PR\n#N\") is actually true, by checking the real merge state of that PR (and,\noptionally, whether the vulnerable code pattern is still present at\n`HEAD`). It exists because closing an issue with a citation to an unmerged\nPR is a real, observed failure mode, not a hypothetical one:\n`sybil-solutions/codex-shim`, a 1,000+-star project, has 6 security issues\nclosed this way as of this writing, each citing the same unmerged PR #52.\nShimGuard does not scan for secrets in source code (see `gitleaks`,\n`trufflehog` for that) and does not do general vulnerability scanning\nagainst dependencies (see `osv-scanner`, `trivy`, `grype`). It verifies one\nspecific, narrow claim: does a tracker's \"fixed\" status match reality.\n\n## FAQ\n\n**Does ShimGuard modify my repo or the target repo?**\nNo. It only makes read-only GitHub API requests (issues, comments, pull\nrequests, and optionally file contents). It never writes, comments, or\nmutates anything.\n\n**Does it need a GitHub token?**\nNo for occasional use. Unauthenticated requests work, subject to GitHub's\nstandard rate limit (60 requests/hour). Set `GITHUB_TOKEN` or pass `--token`\nfor the higher authenticated limit (5,000 requests/hour), useful in CI.\n\n**What counts as a \"cited fix\"?**\nShimGuard looks for phrases like \"Fixed in PR #52\", \"fixed by #101\", or\n\"resolved in #20\" in the issue body and its comments, and extracts the\nreferenced PR number. If no such phrase is found, the result is\n`UNVERIFIED`, not `MATCH` or `MISMATCH`: ShimGuard never guesses.\n\n**Can I use this in CI?**\nYes. `shimguard verify` exits `1` when any MISMATCH is found, so a CI step\ncan gate on it directly. `--format json` gives a stable, parseable report\nfor a bot or dashboard.\n\n**Why \"ShimGuard\" if it doesn't scan BYOK shim configs?**\nAn earlier framing of this idea was a broader local-config security scanner\nfor BYOK (bring-your-own-key) model shims. That scope was narrowed to the\nsharper, more defensible wedge: verifying tracker claims against actual\nmerged code, which is what shipped in v0.1. The name reflects the\nproject's origin case (`sybil-solutions/codex-shim`, a BYOK model shim),\nnot a scope this version doesn't have.\n\n**Does ShimGuard work on Windows, macOS, and Linux?**\nThe npm CLI requires Node.js 18 or later (for the built-in `fetch` API) and\nruns anywhere Node runs, including Windows, macOS, and Linux. The PyPI\npackage requires Python 3.9 or later and lists itself as OS Independent in\nits own classifiers. Neither package has a native or compiled dependency.\n\n**How is ShimGuard different from gitleaks or trufflehog?**\nGitleaks and trufflehog scan file content for secrets accidentally\ncommitted to a repo, things like API keys and tokens. ShimGuard does not\nscan file content for secrets at all -- it checks GitHub's issue and\npull-request API for one specific mismatch: an issue closed as \"fixed in\nPR #N\" where PR #N never actually merged. The two tool categories catch\ndifferent failure modes and can run in the same pipeline without\noverlapping; see \"How it compares\" above for the full list of adjacent\ntools this project checked before writing a line of code.\n\n**Why might `shimguard --version` print a different number than the\ninstalled package?**\nIn some past releases, running `shimguard --version` reported a version\nstring that lagged behind the installed package's actual version --\nthe version string passed to `commander` in `src/cli.ts` was not always\nbumped in the same release as a `package.json` version bump. It is a\ndisplay-only mismatch and does not affect `verify` behavior. To check the\nactual installed version, read the package's own `package.json` or run\n`npm view shimguard-cli version`.\n\n**Does ShimGuard scan every closed issue in a repo automatically?**\nNo. You pass the specific issue numbers to check with `--issues\n38,41,42`; ShimGuard does not currently walk a repo's full closed-issue\nhistory looking for cited-fix claims on its own. For a large repo, pick\nthe issues you care about, for example the ones tagged `security` or tied\nto a release milestone.\n\n**Can I use ShimGuard in a commercial product or a paid CI pipeline?**\nYes. ShimGuard is MIT licensed (see [LICENSE](./LICENSE)), which permits\ncommercial use, modification, and redistribution, including inside a\npaid product or internal tool, with no royalty and no requirement to\nopen-source your own code. Keeping the MIT notice is the only condition.\n\n## Contributing\n\nSee [CONTRIBUTING.md](./CONTRIBUTING.md).\n\n## Security\n\nSee [SECURITY.md](./SECURITY.md).\n\n## License\n\nMIT, see [LICENSE](./LICENSE).\n",
  "bytes": 16267,
  "sha": "62651812ab881528a14a37f1c33acc1d253734842a2a6452914689ea14080fe5",
  "repo_slug": "rudrendupaul/shimguard",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_rudrendupaul_shimguard_4a6c973e/readme"
}