Skip to content

src2sink 2.1.0

Choose a tag to compare

@github-actions github-actions released this 06 Aug 09:46
· 61 commits to main since this release
b2ff29b

Observation and classification, separated. 2.0.0 was about findings that were
wrong; this release is mostly about making a wrong finding cheap to correct
— plus one defect reported from the field that made Maven dependency data empty
for most real repositories.

Why a minor bump. Two new node families and new record fields. Nothing is
removed and no field changes meaning, so a 2.0.0 consumer keeps working;
docs/releasing.md makes a new family a minor bump.

Before you upgrade: the first build after installing rescans every
repository
— DETECTION_VERSION moved from 1 to 6. If you have not yet run the
rescan 2.0.0 required, this replaces it rather than adding to it.

The one to read if you read nothing else: namespaced POMs — which is to say
every POM an IDE or archetype emits — parsed to zero dependencies in 2.0.0 and
earlier, silently. If you have Maven repositories, your dependency data was
empty and nothing said so.

Added

  • call-site observation nodes. The extractor now records every call
    carrying a sink-shaped name, with the inputs a classifier needs, whether or
    not any family claims it. Previously a call that failed the evidence gate was
    discarded, which is why changing what a library means required re-extracting
    the fleet. kind is reference: an observation asserts nothing about danger,
    and the absence of a corresponding sql node says the classifier declined it,
    not that the code is safe.
    DETECTION_VERSION bumps, so the first build after upgrading rescans
    every repository. Measured at ~7% more nodes on the fixture corpus, roughly
    one observation per sql node.

  • File-scoped SQL evidence no longer overrules a receiver (OI-26). The
    three evidence signals were OR'd together, so the weakest — a fact about other
    code in the same file — decided once satisfied. httpClient.execute(r) became
    a SQL execution sink because a JDBC query sat in the same class, and since
    execution sinks feed the raw-code-payload linker, it could fabricate an
    injection endpoint that never existed. Evidence is now ordered by how local it
    is: file evidence rescues an unknown receiver, never one recognised as
    another kind of boundary. ps, pstmt and cstmt joined the receiver
    vocabulary so the tightening does not withdraw real PreparedStatement sinks.

  • Namespaced POMs parsed to zero dependencies (OI-18). Reported from the
    field against 2.0.0. Namespace declarations were stripped with a regex, which
    left prefix uses behind — the standard Maven root element carries
    xsi:schemaLocation, so removing xmlns:xsi="..." made xsi: an unbound
    prefix and the document failed to parse. except ParseError: return []
    swallowed it. Every POM an IDE or archetype emits was affected, so Maven
    dependency data was empty for most real repositories and nothing said so.
    Namespaces are now matched rather than stripped.

  • Maven dependency versions are resolved offline (OI-18). ${property}
    strings and empty inherited versions were recorded as though they were
    versions. Resolution is now tiered — literal, property, parent-in-repo,
    parent-in-fleet, unresolved — with the tier recorded, and works without
    mvn, a registry, or downloading anything: an internal parent POM lives in a
    repository already cloned. <dependencyManagement> no longer emits the BOM as
    a dependency in its own right.

  • Go and Python dependencies are parsed, and lockfiles are read (OI-19).
    Dependency parsing covered Java and npm against nine ecosystems recognised for
    identity, so dependencies_internal: [] on a Go repo meant "not implemented"
    and looked identical to "no internal dependencies". go.mod states exact
    versions outright; uv.lock, poetry.lock and package-lock.json hold the
    resolved answer a manifest range does not. Dependencies now carry
    version_kind — resolved, range or unresolved — because ^1.4.2 names a
    set rather than a version. A repo whose ecosystem is recognised but unparsed
    now says so in its notes instead of reporting an empty list.

Changed

  • Findings are now derived from observations, in a separate pass. Extraction
    records what it saw; src2sink/derive.py decides what it means. The two are
    versioned apart: derivation_version governs the findings, and changing a
    classification rule now re-derives from existing records — no source, no
    parsing, no fleet rescan
    — while detection_version still governs what is
    observed. A new sql-field-marker observation carries the input the
    raw-code-payload link previously held only in memory.
  • SQL classification now reads observations, not source. The rules are
    unchanged and the sql nodes emitted are byte-identical; what changed is the
    input. classify_sql_from_observations consumes call-site records and never
    touches the source or the AST, so correcting a classification — OI-26, or any
    entry in the boundary catalogue — becomes a change to one function over stored
    data rather than a reason to re-extract the fleet.

Fixed

  • Identical but meaningless paths matched at high confidence (OI-24).
    path_templates_match returned on string equality before asking whether
    either side named a destination, so two repos both exposing a bare /v1 — or
    /api, or /service — produced a high-confidence cross-repo edge. That is the
    defect OI-1 was raised to remove, surviving through the one path that never
    reached the guard OI-1 added.
  • Path placeholders and operation verbs counted as destinations (OI-25).
    /{id} matched /{name}, and /search matched /search, both at high.
    Placeholders are now filtered like /api. Operation verbs are deliberately
    not filtered — /v1/query is a real route of a real query service — but a
    match resting entirely on verbs is capped at low.
    Behaviour change: /orders/create and /orders/delete no longer match
    each other. They are different endpoints, and conflating them was never
    intended.