Skip to content

feat: add vulnerability class reference and deterministic entry-point inventory - #200

Open
Haiduongcable wants to merge 4 commits into
openai:mainfrom
Haiduongcable:feat/vulnerability-class-reference
Open

feat: add vulnerability class reference and deterministic entry-point inventory#200
Haiduongcable wants to merge 4 commits into
openai:mainfrom
Haiduongcable:feat/vulnerability-class-reference

Conversation

@Haiduongcable

Copy link
Copy Markdown
Contributor

Summary

Two related detection-surface additions to the bundled plugin. They are separable and I am happy to
split them into one PR per concern on request.

  1. A vulnerability class reference for classes the concise scan procedures never name.
  2. A deterministic remote entry-point inventory, the plugin's first reachability prior.

Impact

The standard repository and scoped-path scan routes away from the discovery checklist
(skills/finding-discovery/SKILL.md) and relies on a single sentence for its entire vulnerability
taxonomy (repository-wide-scan.md): "unsafe command execution, unsafe parsing, XSS,
attacker-controlled network requests, unsafe file access, and missing permission checks."

Ten classes had zero mentions anywhere in skills/ or references/, verified by grep: race
conditions and TOCTOU, CI/CD workflow injection, prototype pollution, ReDoS, mass assignment,
business-logic and authorization gaps, CORS, token validation, infrastructure and container
configuration, and GraphQL.

Separately, nothing in the plugin knew where untrusted input first reaches a target. No script
mentions any web framework, and generate_rank_input.py only validates a model-supplied score,
so a standard scan reads every file with no reachability signal at all.

Before After
6-category taxonomy for the default scan mode plus 10 classes, each naming the control that defeats it
no entry-point concept http_route, graphql, serverless_handler, message_consumer, server_bind, ci_trigger across 9 languages
reviewed source not marked as untrusted marked in the procedure that reads every byte of the target

Changes

references/vulnerability-classes.md (new). Per class: the code shapes that make it visible in
source, and the specific control that defeats it. Suppression requires naming that control — "the
framework probably handles it"
is explicitly not one. Wired into repository-wide-scan.md and into
finding-discovery/SKILL.md before the workflow split, so diff scans reach it too.

scripts/inventory_entry_points.py (new, model-free). Emits sorted, deduplicated JSONL plus a
summary. Resolves the handler symbol where the syntax carries one. Every read is bounded
(MAX_FILES, MAX_FILE_BYTES, MAX_TOTAL_BYTES, MAX_ROWS, line truncation). Wired into the
standard scan procedure to order review by reachability, stated explicitly as a prior and not a
scope filter — every file in in_scope_files.txt is still reviewed.

tests-ts/entry-point-inventory.test.ts (new). 12 cases shelling the real script via
spawnSync(python, ["-I", "-B", ...]), following the existing scan-recovery.test.ts pattern.

plugin-files.json. Registers both new files; check-package.mjs rejects unregistered ones.

Two shared constants deliberately not reused

Both hide real remote surface, and the tests assert the deviation as a regression guard:

  • generate_rank_input.EXCLUDED_DIRS contains .github, so workflow files are invisible to
    worklist-driven scans. Verified: path_is_excluded(Path(".github/workflows/x.yml")) is True. A
    workflow trigger is among the most directly attacker-reachable entry points a repository has.
  • rank_preview.TEXT_CODE_EXTENSIONS omits .tf, so Terraform is invisible.

Verification

Local, in CI order:

  • run types — clean
  • run test722 pass / 5 skip / 0 fail (33 files)
  • run format — clean
  • run build — clean
  • pnpm pack + run check:package — validates 97 bundled plugin files, 181 tarball entries

A/B comparison of the class reference

Measured directly rather than asserted. Two whole-repo scans of the same fixture commit, same model
(gpt-5.6-sol), same effort (xhigh), same concurrency, with the plugin tree differing in exactly
the three changed files and nothing else. The fixture is a 9-file, 385-line service carrying 19
planted instances across the ten newly covered classes, with negative controls alongside them.

baseline with the reference
discovery candidates 30 42
distinct CWEs raised 22 25
planted instances covered 17 of 19 19 of 19

The reference closed both remaining gaps and raised classes the baseline never surfaced:

  • GraphQL introspection (CWE-200) — enabled unconditionally in the fixture and unreported by
    the baseline.
  • Container image defects — the root default, an unpinned base image, and a secret baked into an
    ENV layer, each filed as its own candidate rather than folded into the compose finding.
  • Writable host bind mount (CWE-732).

It also decomposed authorization far more precisely: six per-resolver candidates for GraphQL
field-level authorization where the baseline filed one, each naming the specific resolver
(User.apiKeys, User.invoices, Organization.members, Organization.billingContact,
User.organization, and the top-level organization query) that resolves without a tenant check.
Both arms independently flagged an Origin-header authorization gap that the fixture author had
mislabelled as safe — the scanner was right and the answer key was wrong.

Scope: both runs were terminated by content-safety filtering (#56) after discovery and before
validation, so these are discovery candidates rather than validated findings, and the richer
treatment output carried a 66% higher estimated spend. One run per arm.

Notes

CI. All eight matrix legs pass on a fork PR at 6fa68d1, so the full matrix is already green
without needing a workflow approval here: ubuntu-latest x node 22 / 24 / 24.0.0 / 26 / 26.0.0,
macos-latest x node 22, windows-latest x node 22 (6m28s), and the linux-amd64 container job.
Zero failures. Exact run links are in the follow-up comment.

Known overlap with in-flight work. #88 and #71 both modify
skills/security-scan/references/repository-wide-scan.md, which this branch also touches to wire in
the two new artifacts. The wiring is a 10-line addition and a one-line pointer; I am glad to rebase
onto whichever of those lands first, or to drop the wiring entirely and ship the script and reference
unwired if you would rather keep that file untouched while it is being reworked.

Projection. references/ and skills/ look like a projection of an internal source rather than
the editable original. Both wiring edits are deliberately minimal for that reason, and they would
need mirroring internally to survive a sync. If the reference file itself is better raised as an
issue than carried as a PR, say so and I will move it.

Scope of the A/B evidence. Both scans were cut short by content-safety filtering (#56) after
discovery and before validation, so the comparison above reports discovery candidates rather than
validated findings, and it is one run per arm on a fixture I wrote. The entry-point inventory's
correctness, determinism, and bounds are verified independently of any model spend.

Not changed deliberately. No schema changes, no TypeScript source changes, _sarif_result and
SARIF_LEVELS untouched, and generate_rank_input.py / rank_preview.py left alone even though the
inventory documents two of their constants as blind spots -- changing shared worklist constants is a
separate decision that belongs with you, not bundled into a new-file addition.

The standard repository and scoped-path scan routes away from the discovery
checklist and relies on a single six-category sentence for its entire
vulnerability taxonomy. Concurrency and TOCTOU, workflow injection, prototype
pollution, ReDoS, mass assignment, business-logic and authorization gaps, CORS,
token validation, infrastructure configuration, and GraphQL had no coverage
anywhere in the skills or references.

Add a shared class reference and wire it into both the standard scan procedure
and discovery so diff scans reach it as well. Each entry names the code shapes
that make the class visible in source and the specific control that defeats it,
so an instance can only be suppressed by naming that control. Also mark reviewed
source as untrusted data in the standard scan procedure, which previously read
every byte of the target without saying so.
Nothing in the plugin knows where untrusted input first reaches a target. No
script mentions any web framework, and the ranking worklist only validates a
model-supplied score, so a standard scan reads every file with no reachability
prior at all.

Add a model-free inventory that reports HTTP routes, GraphQL servers,
serverless handlers, message consumers, server binds, and CI workflow triggers
across Python, JavaScript, TypeScript, Go, Java, Kotlin, Ruby, PHP, and C#,
with the handler symbol resolved where the syntax carries one. Wire it into the
standard scan procedure to order review by reachability. Output is sorted and
deduplicated so repeated runs are byte-identical, and every read is bounded.

The inventory deliberately does not reuse two shared worklist constants,
because both hide remote surface: generate_rank_input.EXCLUDED_DIRS contains
.github, so workflow triggers are invisible to worklist-driven scans, and
rank_preview.TEXT_CODE_EXTENSIONS omits .tf.

State it as a prior, not a verdict: the procedure keeps reviewing every file in
scope, since a file with no entry point can still hold a reachable bug through a
helper.
@chatgpt-codex-connector

Copy link
Copy Markdown

Note

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@Haiduongcable

Copy link
Copy Markdown
Contributor Author

Full matrix results from the fork PR at 6fa68d1 (Haiduongcable#4), so the matrix can be confirmed without approving a workflow run here.

Leg Result Duration
categorize release notes skipped --
ubuntu-latest / node-26 success --
linux-amd64 success --
macos-latest / node-22 success --
ubuntu-latest / node-24 success --
ubuntu-latest / node-24.0.0 success --
windows-latest / node-22 success --
ubuntu-latest / node-22 success --
ubuntu-latest / node-26.0.0 success --
publish-linux-${{ matrix.architecture }} skipped --
publish-and-verify-multiarchitecture-candidate skipped --
attest-verified-multiarchitecture-candidate skipped --
promote-verified-and-attested-release skipped --
validate-linux-${{ matrix.architecture }} skipped --
authorize-container-publication skipped --

Zero failures across all eight legs. Local gate in CI order, on the same commit:

  • pnpm --dir sdk/typescript run types — clean
  • pnpm --dir sdk/typescript run test — 722 pass / 5 skip / 0 fail across 33 files
  • pnpm --dir sdk/typescript run format — clean
  • pnpm --dir sdk/typescript run build — clean
  • pnpm pack + pnpm run check:package — 97 bundled plugin files, 181 tarball entries

The branch merges cleanly into main at 3bf2621 with no rebase required.

@github-actions github-actions Bot added the enhancement New feature or request label Aug 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant