Skip to content

Corpus selection, the write server, and a probe that runs without an installer - #1

Merged
BigAchiever merged 7 commits into
mainfrom
corpus-selection
Aug 25, 2026
Merged

Corpus selection, the write server, and a probe that runs without an installer#1
BigAchiever merged 7 commits into
mainfrom
corpus-selection

Conversation

@BigAchiever

Copy link
Copy Markdown
Owner

Three pieces of the agent, and the measurements behind each.

bench/select_corpus.py — the corpus rule as code rather than prose, against an OSV snapshot pinned by sha256. Three arms: six advisories where PyPA and GitHub describe the same CVE differently, eight where they agree exactly, and eleven where no fix commit is published and the answer has to come from repository history. A corpus made only of records already known to be wrong cannot measure how often the agent wrongly corrects something, so the second arm is not optional.

mcp-introduced/ — the only process holding a token that can write. It exposes two tools and does no reasoning; rangecore reasons and holds no credentials. Both tools are annotated so the harness stops for a person before either fires, and a test asserts that, because losing an annotation does not fail loudly.

rangecore/wheel_spike.py — the sandbox has no package installer, so a behavioural probe either works by acquiring built wheels or the evidence tier does not exist. It works: certifi 2024.6.2 → 2024.7.4, probe fired at the vulnerable release and not at the fixed one. 7.4% of candidate releases can carry a probe at all; dependencies, not wheels, are the constraint.

Two mistakes worth reading the tests for: a probe that returned the same verdict at both releases was caught by its own negative control rather than reported, and an eligibility pass zipped a dict against a differently-sorted map, so every verdict was attached to the wrong package. Both now have regression tests.

One test is marked todo: the write path is not yet idempotent, and tool execution is at-least-once across a crash inside the write window.

The selection rule lives in bench/select_corpus.py rather than in prose, so the
corpus a reader gets from the same snapshot is the corpus that was measured. The
snapshot is pinned by sha256 in bench/PROVENANCE.json.

Three arms. A corpus made only of records that are already known to be wrong
cannot measure false-correction rate, because nothing in it should be left alone:

  A   6   PYSEC records a disjoint affected range where GHSA records a single
          interval. A correction is expected. ansible, scrapy, keras, airflow,
          qutebrowser, vantage6 -- all six that exist.
  B   8   Both databases agree exactly. The agent should change nothing.
  C  11   No fix commit is published; the answer has to be reconstructed from
          repository history.

Two rules were written wrong first and are covered by tests:

  * Reference type is not a reliable signal. `type: FIX` appears zero times in
    GHSA-reviewed records while 4,854 commit URLs sit under `type: WEB`, so the
    check matches the URL and ignores the declared type.

  * Ranges must be read per type. PYSEC carries a GIT range whose events are
    commit SHAs alongside an ECOSYSTEM range whose events are versions; counting
    across both makes an ordinary record look disjoint. A GIT fix event is a
    lead, never a boundary.

PYSEC is a held-out second opinion, written to bench/reference/ and never read by
an agent-facing path. Agreement is only evidence if the two were arrived at
separately.

Held-out split is round-robin within each arm, so it cannot be steered toward
advisories the implementation happens to handle. At this size the held-out set is
small enough that one error moves the figure by ten points; report raw counts.

Every advisory considered and not taken is recorded in bench/EXCLUSIONS.json with
a reason from a closed set.
This server exposes the two tools that change something outside this project, and
nothing else. It does not read git, compute ranges, or reason about evidence --
rangecore does that, in a sandbox, holding no credentials. What thinks has no key;
what holds the key does not think.

Stateless by necessity rather than preference. A stateful MCP server that restarts
while TrueForge is mid-turn leaves the session unrecoverable: -32000 "Server not
initialized", the granted approval consumed, the retry a 422. Observed on TrueForge
e9bf976. Statelessness is expressed by omitting sessionIdGenerator, since the option
is declared optional and the SDK disables session management when it is absent.

Both write tools carry readOnlyHint: false and destructiveHint: true. TrueForge
resolves its approval policy entirely from annotations -- @Write is readOnlyHint ===
false, @destructive is destructiveHint === true -- so a tool carrying neither
executes with no approval at all. Both hints are set so the tool matches either
selector however the policy is configured. A test asserts this, because losing an
annotation does not fail loudly; it starts writing without asking.

idempotentHint is deliberately absent. Tool execution is at-least-once across a
crash inside the write window, and the write path does not yet dedupe. Claiming
otherwise would be false. branchNameFor() is a pure function of the advisory id so
that a second call can find the first call's work; openOrFindPullRequest is not written yet
and is marked todo in the test suite rather than left unsaid.

The evidence schema enforces two rules in code. A boundary needs a commit and the
hunk it changed, checked at named refs. And text anyone can write may nominate a
candidate but never justify a boundary -- an issue comment pointing at a genuine old
commit passes every other check here, because this schema stops fabrication and not
misdirection.

Node is pinned to >=22.6: sources run under --experimental-strip-types with no build
step. exactOptionalPropertyTypes is off because the SDK's own Transport types are not
assignable under it.
The sandbox has Python and no way to install anything, so the behavioural evidence
tier either works by acquiring built wheels or it does not exist. Running this now
rather than on the day it was scheduled turns a late discovery into an early one.

It works. certifi 2024.6.2 -> 2024.7.4, probe written against the published fix,
executed at both releases from wheels pulled and digest-checked at run time:

    control  VULNERABLE      @ 2024.6.2
    boundary NOT_VULNERABLE  @ 2024.7.4
    calibrated true, differential true

Acquire from wheels, not from a git checkout. A git tree of a pure-Python project
often will not import -- src/ layouts, setuptools_scm generating _version.py at build
time, generated parser tables. All build-step failures, all absent from a wheel, which
is already built. Affected ranges are expressed over released versions anyway.

Two things the first runs got wrong, both now enforced:

  * The probe ran without -S, so the host's own copy of a dependency was visible and
    the probe measured the wrong tree. It read INCONCLUSIVE for the right reason by
    accident.

  * A probe written against urllib3 returned VULNERABLE at both releases, because it
    tested the wrong thing. The negative control caught it: the probe did not fire at
    the release still known to be vulnerable, so calibrated was false and the result
    was discarded rather than reported. A probe that cannot distinguish the release
    before the fix has nothing to say about the release after it.

Dependencies, not wheel availability, are the gate. 7.4% of candidate releases have
zero runtime dependencies and a portable wheel; the rest cannot be imported without
an installer this design does not have. apache-airflow has 62 dependencies,
open-webui 95.

So arm C now reserves four slots for probe-capable packages. Without the reservation
the corpus contained one by chance. The reservation biases arm C toward simpler
packages; that is a declared stratum, recorded in EXCLUSIONS.json as
PROBE_QUOTA_ONLY, and probe coverage is reported separately and never folded into the
headline. Five of twenty-five now qualify, against an exit test that needs two --
eligibility is checked at the fix version only, so the achieved number will be lower.

probe_eligibility.py zipped a dict against a differently-sorted parallel map, so every
verdict was attached to the wrong package. parso, a zero-dependency parser, came back
with 74 dependencies and calibre-web with none. Nothing failed; the corpus would just
have spent its probe slots on packages that cannot carry a probe. Eligibility is now
keyed by package@version and three tests cover it.
@BigAchiever

Copy link
Copy Markdown
Owner Author

/agentic_review

@qodo-code-review

qodo-code-review Bot commented Aug 24, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Lowercase GHSA rejected ✓ Resolved 🐞 Bug ≡ Correctness
Description
branchNameFor() claims to accept case-insensitive GHSA IDs, but GHSA_ID only matches an
uppercase GHSA- prefix, so inputs like ghsa-… will be rejected and the write tool input
validation will fail before normalization.
Code

mcp-introduced/src/github.ts[R48-49]

+export const GHSA_ID = /^GHSA-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{4}$/;
+
Evidence
The regex is anchored and requires uppercase GHSA-, while branchNameFor() asserts it is
case-insensitive and the tool input schema uses the same regex, so a lowercase-prefixed ID will
throw/refuse before .toLowerCase() normalization can happen.

mcp-introduced/src/github.ts[48-60]
mcp-introduced/src/tools.ts[29-31]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`GHSA_ID` is intended to be case-insensitive (per the comment in `branchNameFor()` and the idempotency goals), but the current regex only matches an uppercase `GHSA-` prefix. This causes valid advisory IDs in lowercase form (e.g. from feeds or user input) to be rejected, and also makes the `open_boundary_correction` tool input validation reject them.

### Issue Context
- `branchNameFor()` lowercases the advisory ID for the branch name, so rejecting purely due to casing defeats the normalization strategy.
- The tool schema uses the same `GHSA_ID`, so callers can’t work around it.

### Fix Focus Areas
- mcp-introduced/src/github.ts[48-59]
- mcp-introduced/src/tools.ts[29-31]

### Suggested change
- Change `GHSA_ID` to be case-insensitive, e.g.:
 - `export const GHSA_ID = /^GHSA-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}$/i;`
 - (or keep `[0-9a-zA-Z]` but add the `i` flag).
- Ensure `z.string().regex(GHSA_ID)` continues to work with the updated regex.
- Add/adjust tests to include a lowercase `ghsa-` prefix case.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Wheel Zip Slip write ✓ Resolved 🐞 Bug ⛨ Security
Description
fetch_and_extract() uses ZipFile.extractall() on wheels downloaded from PyPI, allowing path
traversal entries to write outside the intended directory (Zip Slip) and overwrite arbitrary files
on the host. Hash verification against PyPI metadata does not prevent a malicious-but-valid wheel
from containing such paths, so this is an untrusted-archive extraction vulnerability.
Code

rangecore/wheel_spike.py[R84-88]

+    archive = into / wheel["filename"]
+    archive.write_bytes(blob)
+    site = into / "site"
+    with zipfile.ZipFile(archive) as zf:
+        zf.extractall(site)
Evidence
The PR introduces wheel acquisition and extraction code that directly uses extractall() on an
untrusted archive without member-path validation.

rangecore/wheel_spike.py[76-89]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`rangecore/wheel_spike.py:fetch_and_extract()` downloads a wheel from PyPI and calls `zipfile.ZipFile(...).extractall(site)` without validating member paths. A wheel can contain `../` or absolute paths (or other traversal tricks) that cause extraction outside `site`, potentially overwriting arbitrary files.

### Issue Context
Wheels are fetched from the public internet (PyPI). Even when the SHA256 matches PyPI’s published digest, the wheel contents can still be malicious.

### Fix Focus Areas
- rangecore/wheel_spike.py[76-90]

### Fix approach
- Implement a `safe_extract(zip_file, dest_dir)` helper that:
 - Iterates over `zf.infolist()`.
 - Rejects any member whose resolved destination path is not under `dest_dir.resolve()`.
 - Optionally rejects absolute paths and Windows drive paths explicitly.
 - (Optional hardening) Reject symlinks if your environment/platform can create them via zip extraction.
- Replace `zf.extractall(site)` with `safe_extract(zf, site)`.
- Add a small unit test (if you have a Python test harness for `rangecore`) that builds an in-memory zip containing `../evil` and asserts extraction is refused.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. Reference test may TypeError ✓ Resolved 🐞 Bug ☼ Reliability ⭐ New
Description
tests/test_version_vs_reference.py builds the comparison pool filtering only on the custom parser
result (m), but still keeps entries where the reference parser (r) is None; if parsing ever
diverges, the test can error with a TypeError during (ra < rb) rather than failing with the intended
assertion explaining the parse disagreement.
Code

tests/test_version_vs_reference.py[R79-80]

+    parsed = [(m, r) for m, r in map(_both, _published_versions()) if m is not None]
+    assert len(parsed) > 10_000, "snapshot looks truncated"
Evidence
The ordering test filters out only entries where the custom parser returns None, but keeps entries
where the reference parser returns None. If such an entry exists, later assertions compare ra < rb
where ra can be None, causing a runtime error instead of a clear assertion failure; the separate
test that asserts parse parity is not guaranteed to run first.

tests/test_version_vs_reference.py[66-73]
tests/test_version_vs_reference.py[76-86]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`test_ordering_agrees_across_the_published_corpus()` constructs `parsed` using `if m is not None` but does not require `r is not None`. If `V(raw)` and `packaging.version.Version(raw)` ever disagree (a regression this suite is meant to catch), the ordering test can crash with a TypeError when comparing `ra < rb`, producing a less actionable failure than the explicit parse-disagreement assertion.

### Issue Context
Pytest does not guarantee test execution order, so `test_ordering_agrees_across_the_published_corpus()` should not rely on `test_the_same_strings_are_accepted_and_refused()` having run first to ensure `r` is non-None whenever `m` is non-None.

### Fix Focus Areas
- tests/test_version_vs_reference.py[66-86]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Casing test misses prefix ✓ Resolved 🐞 Bug ⚙ Maintainability
Description
The new “either case” idempotency test only changes the casing of the suffix groups but never tests
a lowercase ghsa- prefix, so it won’t catch the real casing bug the comment claims to cover.
Code

mcp-introduced/test/idempotency.test.ts[R42-45]

+test('an advisory id in either case names the same branch', () => {
+  // Two spellings of one advisory must not become two branches: the branch name is
+  // what lets a repeated write find the first write's work.
+  assert.equal(branchNameFor('GHSA-X7JH-595Q-WQ82'), branchNameFor('GHSA-x7jh-595q-wq82'));
Evidence
The assertion uses GHSA-X7JH-... vs GHSA-x7jh-..., so the prefix casing is never varied despite
the test name and comment.

mcp-introduced/test/idempotency.test.ts[42-46]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The idempotency test claims to verify that advisory IDs in either case map to the same branch, but it only varies casing in the 3 groups and keeps the `GHSA-` prefix uppercase. This leaves the actual failure mode (lowercase `ghsa-` prefix rejected) untested.

### Issue Context
This test is the guardrail for the retry/idempotency story; if it doesn’t exercise the full casing surface area, regressions can slip through.

### Fix Focus Areas
- mcp-introduced/test/idempotency.test.ts[42-46]

### Suggested change
- Update the test to include a lowercase-prefix case, e.g.:
 - `assert.equal(branchNameFor('ghsa-x7jh-595q-wq82'), branchNameFor('GHSA-x7jh-595q-wq82'))`
- Optionally add a separate assertion for the fully lowercase string to make the intention explicit.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. GHSA regex too strict ✓ Resolved 🐞 Bug ≡ Correctness
Description
GHSA ID validation only allows lowercase letters in the suffix groups ([0-9a-z]), so inputs
containing uppercase letters would be rejected even though branchNameFor() lowercases them anyway.
This can cause legitimate advisory IDs from upstream systems to be refused in both branch naming and
tool input validation.
Code

mcp-introduced/src/github.ts[R49-52]

+  if (!/^GHSA-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}$/.test(advisoryId)) {
+    throw new Error(`not a GHSA id: ${advisoryId}`);
+  }
+  return `introduced/${advisoryId.toLowerCase()}`;
Evidence
Both the branch-name helper and the tool input schema enforce lowercase-only GHSA ids, despite later
lowercasing for branch naming.

mcp-introduced/src/github.ts[48-53]
mcp-introduced/src/tools.ts[29-31]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`branchNameFor()` and `CorrectionInput.advisory_id` validate GHSA ids with `[0-9a-z]{4}` groups. This rejects uppercase letters in the groups.

### Issue Context
The code immediately lowercases the advisory id for branch naming, so accepting upper-case input is safe and improves robustness.

### Fix Focus Areas
- mcp-introduced/src/github.ts[48-53]
- mcp-introduced/src/tools.ts[29-31]

### Fix approach
- Make the regex case-insensitive (preferred): `/^GHSA-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}$/i`
- Or expand the character class: `[0-9A-Za-z]`.
- Add a regression test covering an id with uppercase letters in the groups (if that’s a realistic upstream shape for you).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View medium (3)
6. Conditional deps miscounted ✓ Resolved 🐞 Bug ≡ Correctness
Description
runtime_requirements() counts all requires_dist entries except those containing extra ==, so
dependencies guarded by environment markers (e.g., python_version < '3.10', platform markers) are
still counted even when they don't apply. This can incorrectly mark probe-capable releases as
ineligible and bias the measured probe coverage downward.
Code

rangecore/wheel_spike.py[R49-53]

+        with urllib.request.urlopen(PYPI.format(name=name, version=version), timeout=timeout) as r:
+            info = json.load(r).get("info", {})
+    except (urllib.error.HTTPError, urllib.error.URLError, TimeoutError):
+        return None
+    return [d for d in (info.get("requires_dist") or []) if "extra ==" not in d]
Evidence
The implementation only removes dependencies containing the substring extra == and returns all
other requires_dist strings unchanged, which includes conditional marker dependencies.

rangecore/wheel_spike.py[40-54]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`runtime_requirements()` filters out extras by substring but does not evaluate PEP 508 environment markers. As a result, conditional dependencies that do not apply to the current runtime still block probe eligibility.

### Issue Context
The intent (per docstring) is “runtime dependencies” for the current environment. Without evaluating markers, the eligibility gate can be overly strict.

### Fix Focus Areas
- rangecore/wheel_spike.py[40-54]

### Fix approach
- Keep stdlib-only constraint, but implement a minimal marker evaluator for the most common markers you care about (e.g., `python_version`, `python_full_version`, `sys_platform`, `platform_system`, `implementation_name`).
- Parse each `requires_dist` entry by splitting on `;` and, when a marker exists, include the dependency only if the marker evaluates true for the current interpreter.
- If you prefer conservatism, at least record/return `conditional_requires` separately so the caller can decide how to treat them and you can report how much is being excluded due to unknown marker parsing.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. Probe ignores metadata failure ✓ Resolved 🐞 Bug ☼ Reliability
Description
differential() treats runtime_requirements() returning None (release missing or metadata fetch
error) the same as “no dependencies”, so it can mark a package as eligible and proceed with probing
on incomplete/failed dependency information. This can produce flaky or incorrect
eligibility/coverage results under transient network errors or 404s.
Code

rangecore/wheel_spike.py[R137-143]

+    reqs_before = runtime_requirements(name, before)
+    if reqs_before:
+        out["eligible"] = False
+        out["reason"] = f"{len(reqs_before)} runtime dependencies; no installer to acquire them"
+        out["requires"] = reqs_before[:3]
+        return out
+    out["eligible"] = True
Evidence
runtime_requirements() returns None on network/HTTP failures, but differential() only checks
truthiness and proceeds when it is None.

rangecore/wheel_spike.py[40-53]
rangecore/wheel_spike.py[137-143]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
In `rangecore/wheel_spike.py:differential()`, `reqs_before = runtime_requirements(...)` can be `None` on HTTP error/timeout/missing release, but the code only checks `if reqs_before:`. That treats `None` as falsy and continues, effectively assuming “no deps”.

### Issue Context
`runtime_requirements()` explicitly returns `None` on `HTTPError/URLError/TimeoutError`. Eligibility decisions should not silently treat those as “zero dependencies”.

### Fix Focus Areas
- rangecore/wheel_spike.py[137-143]
- rangecore/wheel_spike.py[40-53]

### Fix approach
- In `differential()`, change to:
 - `if reqs_before is None: eligible=False; reason='metadata unavailable/release not on pypi'; return out`
 - `elif reqs_before: ...` existing dependency rejection
- Consider propagating a distinct reason for “release not on PyPI” vs “transient network error” if you want better observability.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


8. Unredacted error response ✓ Resolved 🐞 Bug ⛨ Security
Description
The MCP HTTP handler returns err.message directly in the response body, which can leak the write
token if any future error message includes it (e.g., thrown by GitHub client libraries or request
dumps). This bypasses the provided redact() helper and weakens the stated “never interpolate a
token into a log/error/tool result” guarantee.
Code

mcp-introduced/src/index.ts[R69-72]

+    void handleMcp(req, res).catch((err: unknown) => {
+      if (!res.headersSent) res.writeHead(500);
+      res.end(String(err instanceof Error ? err.message : err));
+    });
Evidence
index.ts responds with the raw error message, while github.ts defines a token redaction helper
that is not used here.

mcp-introduced/src/index.ts[62-72]
mcp-introduced/src/github.ts[27-31]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`mcp-introduced/src/index.ts` writes raw error text to the HTTP response for `/mcp` requests. If any downstream code throws an error containing the write token, it will be returned to the client.

### Issue Context
A `redact(text)` helper exists specifically to prevent token leakage, but it is not applied in this error path.

### Fix Focus Areas
- mcp-introduced/src/index.ts[68-72]
- mcp-introduced/src/github.ts[27-31]

### Fix approach
- Import `redact` into `index.ts` and wrap the returned error string:
 - `res.end(redact(String(err instanceof Error ? err.message : err)))`
- Consider returning a generic message (and logging a redacted internal error) to reduce information disclosure.
- Ensure any other logging/serialization paths also use `redact` or avoid including request headers.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

9. Local ordering comment wrong ✓ Resolved 🐞 Bug ⚙ Maintainability ⭐ New
Description
rangecore/version.py documents Version.local as “compared only for equality, never for ordering”,
but the implemented ordering key includes local segments, and the tests explicitly assert that
1.0+local sorts above 1.0, making the comment misleading for future changes.
Code

rangecore/version.py[R68-70]

+    dev: int               # -1 when absent, so 1.0.dev1 < 1.0 needs care -- see below
+    local: str             # compared only for equality, never for ordering
+
Evidence
The code includes local segments in the ordering key, and the test suite verifies local versions
sort above the plain version. This contradicts the field comment saying local is never used for
ordering.

rangecore/version.py[60-70]
rangecore/version.py[115-161]
tests/test_version.py[36-51]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The `Version` NamedTuple field comment claims local versions are only used for equality, but `_key()` includes `_local_key(v.local)` in the ordering tuple and tests assert ordering differences driven by local segments.

### Issue Context
This mismatch can lead to incorrect future refactors (e.g., someone removing local from the ordering key believing it is unused for ordering).

### Fix Focus Areas
- rangecore/version.py[60-70]
- rangecore/version.py[115-161]
- tests/test_version.py[36-51]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
Review mode: ⚖️ Balanced

Grey Divider

Tip of the day
💡 Did you know, you can hide the parts of a finding you never read, like the evidence or the agent prompt

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Previous reviews

Review updated until commit fe2e3fd

Results up to commit d09a933 ⚖️ Balanced


🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)


Action required
1. Wheel Zip Slip write ✓ Resolved 🐞 Bug ⛨ Security
Description
fetch_and_extract() uses ZipFile.extractall() on wheels downloaded from PyPI, allowing path
traversal entries to write outside the intended directory (Zip Slip) and overwrite arbitrary files
on the host. Hash verification against PyPI metadata does not prevent a malicious-but-valid wheel
from containing such paths, so this is an untrusted-archive extraction vulnerability.
Code

rangecore/wheel_spike.py[R84-88]

+    archive = into / wheel["filename"]
+    archive.write_bytes(blob)
+    site = into / "site"
+    with zipfile.ZipFile(archive) as zf:
+        zf.extractall(site)
Evidence
The PR introduces wheel acquisition and extraction code that directly uses extractall() on an
untrusted archive without member-path validation.

rangecore/wheel_spike.py[76-89]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`rangecore/wheel_spike.py:fetch_and_extract()` downloads a wheel from PyPI and calls `zipfile.ZipFile(...).extractall(site)` without validating member paths. A wheel can contain `../` or absolute paths (or other traversal tricks) that cause extraction outside `site`, potentially overwriting arbitrary files.

### Issue Context
Wheels are fetched from the public internet (PyPI). Even when the SHA256 matches PyPI’s published digest, the wheel contents can still be malicious.

### Fix Focus Areas
- rangecore/wheel_spike.py[76-90]

### Fix approach
- Implement a `safe_extract(zip_file, dest_dir)` helper that:
 - Iterates over `zf.infolist()`.
 - Rejects any member whose resolved destination path is not under `dest_dir.resolve()`.
 - Optionally rejects absolute paths and Windows drive paths explicitly.
 - (Optional hardening) Reject symlinks if your environment/platform can create them via zip extraction.
- Replace `zf.extractall(site)` with `safe_extract(zf, site)`.
- Add a small unit test (if you have a Python test harness for `rangecore`) that builds an in-memory zip containing `../evil` and asserts extraction is refused.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended
2. Conditional deps miscounted ✓ Resolved 🐞 Bug ≡ Correctness
Description
runtime_requirements() counts all requires_dist entries except those containing extra ==, so
dependencies guarded by environment markers (e.g., python_version < '3.10', platform markers) are
still counted even when they don't apply. This can incorrectly mark probe-capable releases as
ineligible and bias the measured probe coverage downward.
Code

rangecore/wheel_spike.py[R49-53]

+        with urllib.request.urlopen(PYPI.format(name=name, version=version), timeout=timeout) as r:
+            info = json.load(r).get("info", {})
+    except (urllib.error.HTTPError, urllib.error.URLError, TimeoutError):
+        return None
+    return [d for d in (info.get("requires_dist") or []) if "extra ==" not in d]
Evidence
The implementation only removes dependencies containing the substring extra == and returns all
other requires_dist strings unchanged, which includes conditional marker dependencies.

rangecore/wheel_spike.py[40-54]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`runtime_requirements()` filters out extras by substring but does not evaluate PEP 508 environment markers. As a result, conditional dependencies that do not apply to the current runtime still block probe eligibility.

### Issue Context
The intent (per docstring) is “runtime dependencies” for the current environment. Without evaluating markers, the eligibility gate can be overly strict.

### Fix Focus Areas
- rangecore/wheel_spike.py[40-54]

### Fix approach
- Keep stdlib-only constraint, but implement a minimal marker evaluator for the most common markers you care about (e.g., `python_version`, `python_full_version`, `sys_platform`, `platform_system`, `implementation_name`).
- Parse each `requires_dist` entry by splitting on `;` and, when a marker exists, include the dependency only if the marker evaluates true for the current interpreter.
- If you prefer conservatism, at least record/return `conditional_requires` separately so the caller can decide how to treat them and you can report how much is being excluded due to unknown marker parsing.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Unredacted error response ✓ Resolved 🐞 Bug ⛨ Security
Description
The MCP HTTP handler returns err.message directly in the response body, which can leak the write
token if any future error message includes it (e.g., thrown by GitHub client libraries or request
dumps). This bypasses the provided redact() helper and weakens the stated “never interpolate a
token into a log/error/tool result” guarantee.
Code

mcp-introduced/src/index.ts[R69-72]

+    void handleMcp(req, res).catch((err: unknown) => {
+      if (!res.headersSent) res.writeHead(500);
+      res.end(String(err instanceof Error ? err.message : err));
+    });
Evidence
index.ts responds with the raw error message, while github.ts defines a token redaction helper
that is not used here.

mcp-introduced/src/index.ts[62-72]
mcp-introduced/src/github.ts[27-31]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`mcp-introduced/src/index.ts` writes raw error text to the HTTP response for `/mcp` requests. If any downstream code throws an error containing the write token, it will be returned to the client.

### Issue Context
A `redact(text)` helper exists specifically to prevent token leakage, but it is not applied in this error path.

### Fix Focus Areas
- mcp-introduced/src/index.ts[68-72]
- mcp-introduced/src/github.ts[27-31]

### Fix approach
- Import `redact` into `index.ts` and wrap the returned error string:
 - `res.end(redact(String(err instanceof Error ? err.message : err)))`
- Consider returning a generic message (and logging a redacted internal error) to reduce information disclosure.
- Ensure any other logging/serialization paths also use `redact` or avoid including request headers.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. GHSA regex too strict ✓ Resolved 🐞 Bug ≡ Correctness
Description
GHSA ID validation only allows lowercase letters in the suffix groups ([0-9a-z]), so inputs
containing uppercase letters would be rejected even though branchNameFor() lowercases them anyway.
This can cause legitimate advisory IDs from upstream systems to be refused in both branch naming and
tool input validation.
Code

mcp-introduced/src/github.ts[R49-52]

+  if (!/^GHSA-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}$/.test(advisoryId)) {
+    throw new Error(`not a GHSA id: ${advisoryId}`);
+  }
+  return `introduced/${advisoryId.toLowerCase()}`;
Evidence
Both the branch-name helper and the tool input schema enforce lowercase-only GHSA ids, despite later
lowercasing for branch naming.

mcp-introduced/src/github.ts[48-53]
mcp-introduced/src/tools.ts[29-31]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`branchNameFor()` and `CorrectionInput.advisory_id` validate GHSA ids with `[0-9a-z]{4}` groups. This rejects uppercase letters in the groups.

### Issue Context
The code immediately lowercases the advisory id for branch naming, so accepting upper-case input is safe and improves robustness.

### Fix Focus Areas
- mcp-introduced/src/github.ts[48-53]
- mcp-introduced/src/tools.ts[29-31]

### Fix approach
- Make the regex case-insensitive (preferred): `/^GHSA-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}$/i`
- Or expand the character class: `[0-9A-Za-z]`.
- Add a regression test covering an id with uppercase letters in the groups (if that’s a realistic upstream shape for you).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View medium (1)
5. Probe ignores metadata failure ✓ Resolved 🐞 Bug ☼ Reliability
Description
differential() treats runtime_requirements() returning None (release missing or metadata fetch
error) the same as “no dependencies”, so it can mark a package as eligible and proceed with probing
on incomplete/failed dependency information. This can produce flaky or incorrect
eligibility/coverage results under transient network errors or 404s.
Code

rangecore/wheel_spike.py[R137-143]

+    reqs_before = runtime_requirements(name, before)
+    if reqs_before:
+        out["eligible"] = False
+        out["reason"] = f"{len(reqs_before)} runtime dependencies; no installer to acquire them"
+        out["requires"] = reqs_before[:3]
+        return out
+    out["eligible"] = True
Evidence
runtime_requirements() returns None on network/HTTP failures, but differential() only checks
truthiness and proceeds when it is None.

rangecore/wheel_spike.py[40-53]
rangecore/wheel_spike.py[137-143]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
In `rangecore/wheel_spike.py:differential()`, `reqs_before = runtime_requirements(...)` can be `None` on HTTP error/timeout/missing release, but the code only checks `if reqs_before:`. That treats `None` as falsy and continues, effectively assuming “no deps”.

### Issue Context
`runtime_requirements()` explicitly returns `None` on `HTTPError/URLError/TimeoutError`. Eligibility decisions should not silently treat those as “zero dependencies”.

### Fix Focus Areas
- rangecore/wheel_spike.py[137-143]
- rangecore/wheel_spike.py[40-53]

### Fix approach
- In `differential()`, change to:
 - `if reqs_before is None: eligible=False; reason='metadata unavailable/release not on pypi'; return out`
 - `elif reqs_before: ...` existing dependency rejection
- Consider propagating a distinct reason for “release not on PyPI” vs “transient network error” if you want better observability.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Results up to commit adde340 ⚖️ Balanced


🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)


Action required
1. Lowercase GHSA rejected ✓ Resolved 🐞 Bug ≡ Correctness
Description
branchNameFor() claims to accept case-insensitive GHSA IDs, but GHSA_ID only matches an
uppercase GHSA- prefix, so inputs like ghsa-… will be rejected and the write tool input
validation will fail before normalization.
Code

mcp-introduced/src/github.ts[R48-49]

+export const GHSA_ID = /^GHSA-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{4}$/;
+
Evidence
The regex is anchored and requires uppercase GHSA-, while branchNameFor() asserts it is
case-insensitive and the tool input schema uses the same regex, so a lowercase-prefixed ID will
throw/refuse before .toLowerCase() normalization can happen.

mcp-introduced/src/github.ts[48-60]
mcp-introduced/src/tools.ts[29-31]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`GHSA_ID` is intended to be case-insensitive (per the comment in `branchNameFor()` and the idempotency goals), but the current regex only matches an uppercase `GHSA-` prefix. This causes valid advisory IDs in lowercase form (e.g. from feeds or user input) to be rejected, and also makes the `open_boundary_correction` tool input validation reject them.

### Issue Context
- `branchNameFor()` lowercases the advisory ID for the branch name, so rejecting purely due to casing defeats the normalization strategy.
- The tool schema uses the same `GHSA_ID`, so callers can’t work around it.

### Fix Focus Areas
- mcp-introduced/src/github.ts[48-59]
- mcp-introduced/src/tools.ts[29-31]

### Suggested change
- Change `GHSA_ID` to be case-insensitive, e.g.:
 - `export const GHSA_ID = /^GHSA-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}$/i;`
 - (or keep `[0-9a-zA-Z]` but add the `i` flag).
- Ensure `z.string().regex(GHSA_ID)` continues to work with the updated regex.
- Add/adjust tests to include a lowercase `ghsa-` prefix case.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended
2. Casing test misses prefix ✓ Resolved 🐞 Bug ⚙ Maintainability
Description
The new “either case” idempotency test only changes the casing of the suffix groups but never tests
a lowercase ghsa- prefix, so it won’t catch the real casing bug the comment claims to cover.
Code

mcp-introduced/test/idempotency.test.ts[R42-45]

+test('an advisory id in either case names the same branch', () => {
+  // Two spellings of one advisory must not become two branches: the branch name is
+  // what lets a repeated write find the first write's work.
+  assert.equal(branchNameFor('GHSA-X7JH-595Q-WQ82'), branchNameFor('GHSA-x7jh-595q-wq82'));
Evidence
The assertion uses GHSA-X7JH-... vs GHSA-x7jh-..., so the prefix casing is never varied despite
the test name and comment.

mcp-introduced/test/idempotency.test.ts[42-46]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The idempotency test claims to verify that advisory IDs in either case map to the same branch, but it only varies casing in the 3 groups and keeps the `GHSA-` prefix uppercase. This leaves the actual failure mode (lowercase `ghsa-` prefix rejected) untested.

### Issue Context
This test is the guardrail for the retry/idempotency story; if it doesn’t exercise the full casing surface area, regressions can slip through.

### Fix Focus Areas
- mcp-introduced/test/idempotency.test.ts[42-46]

### Suggested change
- Update the test to include a lowercase-prefix case, e.g.:
 - `assert.equal(branchNameFor('ghsa-x7jh-595q-wq82'), branchNameFor('GHSA-x7jh-595q-wq82'))`
- Optionally add a separate assertion for the fully lowercase string to make the intention explicit.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread rangecore/wheel_spike.py Outdated
Comment thread rangecore/wheel_spike.py
Comment thread rangecore/wheel_spike.py Outdated
Comment thread mcp-introduced/src/index.ts
Comment thread mcp-introduced/src/github.ts Outdated
Four of the five stand. The first does not, and the reason is worth recording.

ZIP SLIP -- not reproducible, guard added anyway. The report says extractall() on a
downloaded wheel allows path traversal and lets an archive overwrite files on the
host. I built an archive containing `../../escaped.txt`, an absolute path, and a
symlink entry pointing at /etc/passwd, and extracted it: CPython strips `..`
segments, strips leading separators, and writes symlink entries as ordinary files.
Nothing escaped. So the vulnerability as described is not exploitable here.

safe_extract() is added regardless, because the safety of that function was an
undocumented property of the standard library rather than anything this code stated
or checked -- and this is the one place in the project where an archive from an
unreviewed third party is unpacked. A matching digest proves PyPI served that wheel.
It proves nothing about what is inside it.

METADATA FAILURE READ AS "NO DEPENDENCIES" -- correct, and the worst of the five.
runtime_requirements() returns None on HTTP error or timeout, and differential()
tested it for truthiness, so a transient network failure marked a release eligible.
That inflates a reported coverage number on a flaky connection. Now checked
explicitly.

MARKER-GUARDED DEPENDENCIES MISCOUNTED -- correct, and it moved the number.
Dependencies behind an environment marker were counted as if they always applied.
Evaluating PEP 508 markers needs a parser this module deliberately does not have, so
they are now excluded from the gate and a probe that turns out to need one fails as
INCONCLUSIVE, which is refused rather than reported. Being wrong here costs a probe,
never a wrong answer. Probe eligibility across the snapshot goes from 195 releases to
235 -- the estimate was biased downward, as reported.

ERROR RESPONSE BYPASSED redact() -- correct. github.ts states that a token is never
interpolated into a log, an error, or a tool result, and the HTTP error path returned
err.message raw. An error raised deep in a request path is exactly where one would
arrive. It goes through redact() now.

GHSA IDS REJECTED ON CASE -- correct, with a second reason the report did not give.
The pattern is now case-insensitive and shared between the branch helper and the tool
schema. Normalising also means two spellings of one advisory cannot produce two
branches, which matters because that name is what lets a repeated write find the
first write's work. Covered by a test.
Comment thread mcp-introduced/src/github.ts Outdated
Comment thread mcp-introduced/test/idempotency.test.ts Outdated
@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit adde340

Both findings from the second review are correct, and both are mine.

The pattern was spelled with character classes -- [0-9a-zA-Z] in each group -- which
left the `GHSA-` prefix literal. So `GHSA-x7jh-595q-wq82` was rejected while the
comment directly above it promised case-insensitivity. A comment asserting behaviour
the code does not have is worse than no comment: it stops the next reader checking.

The test did not catch it because it only varied the suffix. It compared
`GHSA-X7JH-...` against `GHSA-x7jh-...`, never touched the prefix, and passed. A test
written from the same assumption as the code cannot find the assumption.

The pattern now carries the `i` flag, which covers the whole string, and the test
walks three spellings including a lower-case prefix and a mixed-case one.
@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 951ef54

Every boundary this project files rests on one comparison, and a wrong answer here
does not fail anywhere downstream. It produces a well-formed correction that is wrong,
which is the single outcome the rest of the design exists to prevent. So this is
hand-written, and then checked against the reference implementation.

The trap it exists for: there are two comparisons in Python packaging and they
disagree. PEP 440 ORDERING places a pre-release below its own release, so a range of
"< 1.4.0" contains 1.4.0rc1. pip's SPECIFIER MATCHING excludes pre-releases unless
asked for, so the same range would not match it. OSV ECOSYSTEM ranges are ordering --
the question is whether a published version was affected, not whether pip would
install it. Built on the other one, this project would disagree with the published
record on every advisory whose boundary is a pre-release, and the disagreement would
look like a finding rather than a bug.

Dependency-free because rangecore runs in a sandbox with no installer. That is a
deployment constraint and not a licence to be approximately right, so
tests/test_version_vs_reference.py imports `packaging` -- never a runtime dependency --
and compares the two over every version string OSV publishes for PyPI in the pinned
snapshot: 43,265 strings, 41,214 parseable, zero disagreements about which strings are
versions and zero disagreements across 200,000 sampled ordering comparisons. The
committed test samples 20,000 with a fixed seed and skips cleanly when `packaging` or
the snapshot is absent.

Three cases a naive tuple comparison gets wrong, each covered: trailing zeros carry no
meaning (1.0 == 1.0.0); a dev release precedes its own version's pre-releases
(1.0.dev1 < 1.0a1) while an absent dev sorts above any dev number; an absent post
sorts below any post number.

An unparseable version raises rather than sorting. Ordering a version wrongly produces
a boundary; refusing produces an abstention, and abstention is the design.
Comment thread tests/test_version_vs_reference.py Outdated
Comment thread rangecore/version.py
@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 54b354c

Both findings stand.

The Version.local comment said "compared only for equality, never for ordering" while
_key() includes local segments and the suite asserts 1.0 < 1.0+local. PEP 440 does
order local versions above their plain form; the comment described an earlier intent
and was never updated. That is the third comment in two days claiming behaviour the
code does not have -- the same shape as the idempotency row in AI_USAGE.md and the
case-insensitivity note on the GHSA pattern. Worth naming rather than quietly fixing:
the comment gets written from the intent, the implementation moves, and nothing
reconciles them.

The reference test filtered on the local parser only, so an entry the reference parser
rejected would reach `ra < rb` and raise TypeError rather than failing through the
parity assertion that explains the divergence. Test execution order is not guaranteed,
so it could not rely on the parity test running first. It now requires both.
@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit fe2e3fd

@BigAchiever
BigAchiever merged commit 7cb054f into main Aug 25, 2026
1 check passed
@BigAchiever
BigAchiever deleted the corpus-selection branch August 25, 2026 07:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant