Skip to content

Releases

v0.1.9

Choose a tag to compare

@Rinkia Rinkia released this 08 Sep 08:00
9f3369e

A single fix to the Java attack-surface lens.

Fixed

  • Sinks called on a try-with-resources variable are no longer missed. modscan-audit --language java resolves a call's receiver type from the file's declarations, and a variable declared in a try (ObjectInputStream in = …) header — a distinct AST node — was not read, so in.readObject() went unreported. Found by a new Semgrep cross-check against commons-lang3.

Full notes: CHANGELOG

v0.1.8

Choose a tag to compare

@Rinkia Rinkia released this 08 Sep 07:21
b6ae1df

Adds the Java security lens and makes the ranking honest about ties.

Added

  • --version on both modscan and modscan-audit.
  • Tie disclosure. detect and the benchmark now state when a score sits in a tie the alphabetical tiebreak decides, rather than presenting it as a ranking. --json carries tied_with per point.
  • modscan-audit --language java — a Java risk-sink catalog modelled on find-sec-bugs: ScriptEngine/Groovy/SpEL, ObjectInputStream/XMLDecoder/XStream/SnakeYAML, Runtime.exec/ProcessBuilder, Class.forName. Not yet cross-checked against an external authority (find-sec-bugs reads bytecode, MODScan reads source) — treat it as newer than the Python and TypeScript catalogs. Needs pip install modscan[java].

Fixed

  • @overload stubs no longer report a symbol several times. SQLAlchemy dropped 91 phantom candidates; medians improved, recall unchanged.

Full notes: CHANGELOG

v0.1.7

Choose a tag to compare

@Rinkia Rinkia released this 23 Jul 15:56
1339016

A third language, a second lens over MCP, and two TypeScript front-end defects found by pointing the benchmark at real packages.

Added

  • Java front-end — modscan detect --language java. Java maps onto the model more directly than any front-end so far: class/interface/abstract class are already its vocabulary, extends/implements are bases, annotations play the decorator role, and visibility comes from the explicit public modifier. Generic bases are reduced to their bare name, annotation arguments dropped, and nested classes deliberately not emitted as top-level seams. Optional: pip install modscan[java].
  • The security lens over MCP — modscan-mcp gains audit_attack_surface beside detect_extension_points, returning exactly the payload modscan-audit --json emits so the non-coverage disclaimer travels with the data.
  • The security lens covers TypeScript/JavaScript — modscan-audit --language typescript maps eval, Function, the vm module, child_process, dynamic require/import and string-bodied timers. Modelled on eslint-plugin-security: a computed require(name) is a sink, a literal require('fs') is not.
  • More process sinks, and shell=True raises severity — os.popen, os.startfile, the os.exec*/os.spawn* family and commands.getoutput, found by cross-checking the lens against Bandit. A subprocess.* call passing a literal shell=True now rates high.

Fixed

  • CommonJS exports are recognised. The TS/JS front-end marked symbols public only from the ESM export keyword, so every CommonJS file — most of npm — reported zero public symbols and contributed no extension points at all.
  • .d.ts declaration files are no longer scanned. A declaration file declares types for code defined elsewhere, so scanning it produced a phantom copy of every symbol and let declaration-only option bags outrank the classes a project's documentation tells you to subclass.

Note

A modscan upgrade that adds catalogue entries can make the attack-surface gate report sinks that were always in the code — new to the detector, not to the codebase. Pin the Action's modscan-version for a baseline that only moves when you choose.

Full diff: v0.1.6...v0.1.7

v0.1.6

Choose a tag to compare

@Rinkia Rinkia released this 23 Jul 13:19
f314172

The security lens grows a memory: it can now compare two snapshots and gate a pull request on the execution sinks it introduces.

Added

  • Attack-surface gate GitHub Action (Rinkia/modscan/attack-surface) — on a pull request it audits the PR and the base branch, diffs them, sticky-comments the result, and fails the check when the PR introduces new execution sinks. It gates the delta, never the standing surface: sinks already on base never fail it. fail-on defaults to high (eval/exec/pickle.loads/yaml.load/os.system) — measured across real packages, the medium tier is dominated by routine __reduce__, dynamic-import and subprocess code, so gating on it is noisy for exactly the plugin hosts MODScan serves. No LLM, no API key, no committed snapshot. MODScan runs it on itself.
  • modscan-audit --diff BASE PR — compare two --json attack-surface snapshots and report the sinks the second introduces. Identity is (id, module, call) compared as a counted multiset, so moved code never registers as a change while a third eval added to a module that already had two still shows up.
  • modscan-audit --fail-on {high,medium,low,none} — defaults to none: the CLI stays a report, and the policy lives in the gate.

Still enumeration, not taint: an introduced sink is new attack surface to review, and a passing check is not a clean bill of health.

Full diff: v0.1.5...v0.1.6

v0.1.5

Choose a tag to compare

@Rinkia Rinkia released this 22 Jul 18:20
c9c38f0

A second lens over the same static analysis: modscan-audit maps a codebase's attack surface, and moddability reasons now carry stable IDs.

Added

  • Security Lens (modscan-audit) — a sibling command that maps a Python codebase's attack surface: where untrusted code or data can enter and execute (eval/exec, pickle/marshal/yaml deserialization, __reduce__, os.system/subprocess, dynamic loaders). Bandit-style catalog: stable MS-SEC-* ids rated by severity × confidence (separate from the moddability score), ranked most-dangerous-first. Offline, no LLM. Markdown or --json. Every report states prominently it is not a vulnerability scan — no taint, CVEs, or secrets, and an empty report is not a clean bill of health. Enumeration only; it does not trace reachability.
  • Stable IDs on moddability signals — each ranking reason carries a stable MS-MOD-* id (presentation only; score and ranking unchanged).

Full diff: v0.1.4...v0.1.5

v0.1.4

Choose a tag to compare

@Rinkia Rinkia released this 22 Jul 15:28
0d5925e

Downstream polish: scaffolds you can verify, docs that point at them, and a gate comment that stops repeating itself.

Added

  • modscan scaffold --verify — after writing plugin skeletons, imports the target and confirms each base actually subclasses/instantiates (or, for hook/registration points, imports and is callable), reusing the layer-5 validator. Offline and free; exits non-zero if any point fails. Opt-in (executes the target's module code); the default scaffold path never imports the target.
  • Docs link to scaffolding, and explain the status badges. The plugin guide prints the exact modscan scaffold "<id>" command per point; the index carries a legend for the verified/executed/compiled/generated/unverified badges. Deterministic — no LLM.

Changed

  • The breaking-change gate posts a sticky PR comment — it finds its prior comment by a hidden marker and updates it in place instead of stacking a new one on every re-push.

Full diff: v0.1.3...v0.1.4

v0.1.3

Choose a tag to compare

@Rinkia Rinkia released this 22 Jul 09:53
b8b5c72

A free CI gate that fails a pull request removing or changing a library's extension points, plus detect polish.

Highlights

  • Breaking-change gate GitHub Action (Rinkia/modscan/breaking-change) — on a PR, diffs modscan detect against the base branch, comments the diff, and fails when an extension point is removed or its category/kind changed. Protects a library's plugin/mod authors from silent breakage. No committed manifest, no LLM, no API key. One uses: line.
  • modscan diff accepts detect --json (a flat list) as well as a manifest, so the gate runs off free detect output. score is never compared — re-ranking alone is not breaking.
  • modscan detect --label — a clean header label instead of the scan path.
  • Fix: no absolute scan path leaks into detect ids (root-package symbols); JSON gains an explicit module field.
  • Fix: the output directory is excluded from the scan (no self-scan on re-runs).
  • Chore: GitHub Actions bumped off deprecated Node 20.

MODScan runs the gate on its own API — see .github/workflows/extension-api-gate.yml.

Full detail in CHANGELOG.md.

Install: `pip install modscan`

v0.1.2

Choose a tag to compare

@Rinkia Rinkia released this 21 Jul 14:26
bcc1376

Robustness for runs against real, imperfect codebases, plus cleaner output and a pipeline diagram in the README.

Highlights

  • Pre-flight import probe — a run stops before any LLM call with a classified cause and a pip install remediation when the target or its dependencies can't be imported, instead of grinding through to empty docs.
  • Classified drop reporting — extension points that fail validation are counted and labelled (missing dependency vs. other), in the CLI summary and an index.md "Not documented" section, instead of vanishing silently.
  • modscan detect: separate "Plugin registration points" section — entry_points-style loader sites are reported apart from the implement-this ranking and de-duplicated.
  • Run isolation — modules imported during validation are cleaned up, so consecutive runs in one process don't contaminate each other.
  • Fix: the re-export ranking signal now fires when scanning a parent directory (a real checkout), not only when pointed straight at the package.
  • Self-scan guard — warns when --out is inside the scanned tree.

Full detail in CHANGELOG.md. See the new pipeline diagram in the README.

Install: `pip install modscan`

v0.1.1

Choose a tag to compare

@Rinkia Rinkia released this 21 Jul 10:30
dd00f41

Ships the offline, no-API-key surface and the measured ranking work.

Highlights

  • `modscan detect` — rank a codebase's extension points with static analysis only: no LLM, no API key, no code execution. Markdown or `--json`.
  • GitHub Action (`action.yml`) — ranked extension points in every PR's job summary, safe on untrusted PRs.
  • MCP server (`pip install modscan[mcp]`, `modscan-mcp`) — the detector as a tool for AI clients (Claude Desktop, Cursor).
  • Ranking benchmark (`benchmarks/`) — labelled ground truth over five real packages, `score.py` reporting recall@10, plus a CI guard against quietly bending the labels.
  • Re-export + override-point signals, each judged on the benchmark (aggregate recall@10 4/12 → 8/12 on the original three targets), with honest generalisation gaps recorded.
  • Fix: builtin `import` is no longer misranked as a plugin loader.

Full detail in CHANGELOG.md. See ROADMAP.md for what's next.

Install: `pip install modscan`

v0.1.0 — TypeScript support, breaking-change diffs, sandboxed validation

Choose a tag to compare

@Rinkia Rinkia released this 20 Jul 09:27

MODScan scans a codebase and generates everything you need to write plugins and mods for it — it finds the seams a modder can actually hook into, writes grounded documentation for them, and proves each generated example by loading it into the target.

Now on PyPI: pypi.org/project/modscan

pip install modscan[anthropic]      # or [openai], [gemini], [typescript]
export ANTHROPIC_API_KEY=sk-ant-...

modscan ./your-project              # -> modding-docs/ + extension-points.json
modscan scaffold "pkg.mod:Symbol"   # -> a ready-to-edit plugin skeleton

Want to look before spending anything? The deterministic layers need no API key
at all:

from modscan import parse_codebase, build_graph, detect_extension_points
cb = parse_codebase("./your-project")
for p in detect_extension_points(build_graph(cb), min_score=0.5)[:10]:
    print(f"{p.score:.2f} {p.category} {p.seam.module}:{p.seam.name}")

Marked a pre-release: this is a 0.x, the API can still move, and two known
limitations are documented below rather than hidden.


Everything since the MVP: more languages, more surfaces, safer execution, and a
substantial internal cleanup.

Added

  • TypeScript/JavaScript front-end (experimental, via tree-sitter). Parses
    .ts/.tsx/.js/.jsx into the shared model, so the graph, detector and docs work
    on JS/TS too. --language typescript|javascript; optional dependency
    modscan[typescript]. Example execution-validation remains Python-only.
  • Pluggable language front-ends — LanguageParser protocol plus a registry,
    so a new language plugs in without touching the graph, detector or generator.
  • modscan diff — compare two extension-points.json manifests and report
    removed / changed / added extension points. Exits non-zero on breaking
    changes, so it can gate CI. A copy-paste workflow that comments the diff on a
    pull request ships in examples/ci/breaking-change.yml.
  • modscan config — detect config/data-driven modding surfaces (manifest
    files, drop-in directories) that the AST detector cannot see.
  • modscan scaffold --all — generate plugin skeletons for every point in a
    manifest in one command.
  • Subprocess sandbox (--sandbox) — validate generated examples in an
    isolated child process with a timeout, containing hangs and crashes when the
    target is less trusted.
  • On-disk LLM response cache (--cache-dir) — memoises responses by
    provider, endpoint, model and prompt, making re-runs free and offline.
  • Native Gemini provider (--provider gemini), alongside Anthropic and the
    OpenAI-compatible adapter.
  • Concurrency for the LLM fan-out (--concurrency, default 1). A run is
    dominated by 1 + points x (1..retries) sequential network round-trips; this
    is where the wall-clock time is. Output is unaffected — results are collected
    in input order and in-process validation is serialised.
  • executed example status — hook/registration examples are now executed,
    not merely compile-checked, so bad imports and runtime errors are caught.
  • Continuous integration across Python 3.10–3.13, and an opt-in live smoke test
    (MODSCAN_LIVE=1) that is the only test allowed to make a real API call.
  • Contributor documentation (CONTRIBUTING.md) and README badges.

Changed

  • Single source of truth for code execution. Loading a target, executing an
    example, finding a concrete subclass and instantiating it previously existed
    three times — in the validator, in the generator, and again as a Python string
    inside the sandbox runner. It now lives once in modscan/execution.py, and the
    sandbox child imports that module instead of carrying a copy.
  • docgen split into a package (pipeline, examples, render, types).
    The public API is unchanged.
  • Domain types moved to models.py (Seam, ExtensionPoint), so layers no
    longer import each other merely to name a type. Old import paths still resolve.
  • Example statuses are an enum (models.ExampleStatus) instead of strings
    re-spelled across four modules. It subclasses str, so the JSON manifest is
    byte-for-byte unchanged.
  • One filesystem convention — SKIP_DIRS, source walking and slug
    generation live in modscan/fsutil.py.
  • Library-safe logging: a NullHandler on the package root, plus debug logs
    where failures were previously swallowed without a trace.

Fixed

  • Crash on typing.Protocol seams. Scanning a codebase that defines a
    runtime_checkable Protocol with a data member aborted the run, because
    issubclass raises for those and the call sat outside the guard. Found by
    running MODScan on itself.
  • Divergent directory skipping. SKIP_DIRS was defined three times with
    three different values, so the same tree produced different results depending
    on which scanner asked — dist/ was walked by the Python parser but skipped
    elsewhere, .venv/ was walked by the TypeScript one. Now unified. This is a
    deliberate behavior change: build output and virtualenvs are consistently
    skipped.
  • More dynamic-import calls detected — import_string, load_entry_point,
    iter_entry_points, load_module, find_spec and get_loader now register
    as plugin-loader seams. Thanks to @AjibadeHassan
    for the first external contribution (#18).
  • Silent point loss. Point selection relied on zip() truncating against a
    shorter validation list; the limit is now applied explicitly and the zip is
    strict, so a mismatch fails loudly.
  • javascript examples are written as .js with a javascript code fence
    (previously .ts/typescript).
  • Cache keys include the provider and endpoint, so the same model and prompt
    against two different backends can no longer collide.
  • Removed a dead import left behind by the language-front-end integration.

Known limitations

  • Imported target modules are never unloaded, so a long-lived process
    accumulates them and a second scan of a different tree using the same package
    name reuses the first. Restoring sys.modules safely needs its own change.
  • Generating docs into the tree being scanned makes the next run pick up its
    own generated examples. Write the output elsewhere, or ignore the output
    directory.

Full changelog: CHANGELOG.md · Compare: v0.0.1...v0.1.0

Contributions welcome — there are good first issues open, and CONTRIBUTING.md explains the layered architecture. Tests are framework-free and offline: no API key needed to hack on it.