Repository navigation
src2sink 2.1.0
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-siteobservation 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.kindisreference: an observation asserts nothing about danger,
and the absence of a correspondingsqlnode says the classifier declined it,
not that the code is safe.
DETECTION_VERSIONbumps, so the first build after upgrading rescans
every repository. Measured at ~7% more nodes on the fixture corpus, roughly
one observation persqlnode. -
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,pstmtandcstmtjoined the receiver
vocabulary so the tightening does not withdraw realPreparedStatementsinks. -
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 removingxmlns:xsi="..."madexsi: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, sodependencies_internal: []on a Go repo meant "not implemented"
and looked identical to "no internal dependencies".go.modstates exact
versions outright;uv.lock,poetry.lockandpackage-lock.jsonhold the
resolved answer a manifest range does not. Dependencies now carry
version_kind—resolved,rangeorunresolved— because^1.4.2names 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.pydecides what it means. The two are
versioned apart:derivation_versiongoverns the findings, and changing a
classification rule now re-derives from existing records — no source, no
parsing, no fleet rescan — whiledetection_versionstill governs what is
observed. A newsql-field-markerobservation carries the input the
raw-code-payloadlink previously held only in memory. - SQL classification now reads observations, not source. The rules are
unchanged and thesqlnodes emitted are byte-identical; what changed is the
input.classify_sql_from_observationsconsumescall-siterecords 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
highconfidence (OI-24).
path_templates_matchreturned 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
defectOI-1was raised to remove, surviving through the one path that never
reached the guardOI-1added. - Path placeholders and operation verbs counted as destinations (
OI-25).
/{id}matched/{name}, and/searchmatched/search, both athigh.
Placeholders are now filtered like/api. Operation verbs are deliberately
not filtered —/v1/queryis a real route of a real query service — but a
match resting entirely on verbs is capped atlow.
Behaviour change:/orders/createand/orders/deleteno longer match
each other. They are different endpoints, and conflating them was never
intended.