TL;DR: PR #652 began as reproducible-delivery work. Its first commit addedcyclonedx-bomto generate SBOM evidence, and Socket classified that dependency as malicious. It was removed from the delivery path. A second Socket alert then identifiedpycparser==3.0for obfuscation. The lasting change was not a one-off package swap: an alerted release can no longer be approved by a review record. It is blocked locally and in CI before dependency installation.
Scope and language: this post records the controls and evidence from PR #652. It calls cyclonedx-bom malicious-classified and pycparser==3.0 obfuscation-alerted exactly as Socket classified them. This was the first real CVE/security issue acted on through the established Socket checks; the post does not attach an unverified CVE identifier to either package or infer a broader compromise.
Reproducible Delivery Was the Starting Point
The initial commit in PR #652 had a straightforward engineering goal: make a release repeatable. It committed a uv.lock, produced a hash-protected CI export, introduced a frozen Python setup action, and added proof that independently built environments produce the same package graph and SBOM.
That kind of work is usually treated as build hygiene. It is also supply-chain work: every new tool and every frozen artifact becomes part of the release trust boundary.
To generate SBOM evidence, the initial change added cyclonedx-bom. Socket classified that library as malicious. The response was deliberately simple: remove it from the delivery path. A dependency that has crossed that threshold is not a candidate for an exception, a suppression, or a later tidy-up.
This was the first pivot: reproducibility is useful, but reproducing an unsafe input accurately is not a security control.
A Second Alert Exposed a Policy Problem
The frozen graph also contained pycparser==3.0, reached through specfact-cli → cryptography → cffi → pycparser. Socket raised a separate obfuscation alert for that release.
The early response had a weakness. A review register could document why an artifact had been inspected, but it could also make an already-alerted release look acceptable. That confuses two different decisions:
- Review evidence: a maintainer has documented a specific, time-limited reason to retain a non-alerted artifact.
- Release policy: a known alerted release must not be normalised into routine delivery input.
Those are not interchangeable. A record may explain a residual risk. It must never waive the rule that stops a known alert from shipping.
The alert-blocking change made that distinction executable. pycparser==3.0 is now an explicit deny entry: the native dependency-trust checker rejects it even if someone adds it to the review register.
From a Warning to a Non-Bypassable Gate
The remediation replaced pycparser==3.0 with 2.22 in the reviewed lock and constrained package resolution with pycparser>=2.22,!=3.0. That keeps the alerted release out of fresh resolver results without publishing an unnecessarily exact transitive runtime pin to downstream users.
More importantly, the policy now binds review evidence to the artifact that will actually be installed. A record must include the immutable PyPI URL, artifact SHA-256, classification, review date, expiry, and transitive path. The checker parses uv.lock and verifies the matching package record rather than accepting a matching string somewhere else in the lock.
| Control | What it prevents |
|---|---|
| Explicit blocked release list | A review entry cannot approve pycparser==3.0. |
| Lock-bound URL and digest | A copied review record cannot approve a different artifact. |
| Expiry dates | Residual-risk decisions fail closed unless they are reviewed again. |
| Pre-commit and required CI gate | Dependency inputs are checked before they become a merge or release candidate. |
The ordering matters. The trust checker now runs before the shared frozen-environment action calls uv sync. A policy that runs only after installation can report a problem, but it cannot honestly claim to have stopped the risky install path.
Hardening the Whole Path, Not Just One Package
The follow-up audit hardening addressed the control gaps exposed by the incident:
- Package names are canonicalised so case, underscore, or dot variants cannot evade a denylist.
- Artifact URLs and hashes are checked against the package’s own lock entry, not the lock file as a whole.
- The type runner resolves its location from its script path, and the refresh workflow rejects unsafe symlinked output paths.
pip-audit --strictfails on new or changed advisories; documented exceptions are exact and expire.- Socket Project Report and Pull Request Alerts remain required branch checks, while Dependabot updates still pass frozen-export, audit, and compatibility gates before review.
That combination is intentional. Socket contributes independent supply-chain and obfuscation analysis. The repository’s own policy makes the known decision enforceable in every developer and CI workflow. Neither layer is sufficient on its own.
License Gates Complete the Dependency Decision
PR #652 did not introduce dependency governance from zero. Earlier dependency-hygiene work introduced hatch run license-check as a local and CI gate. A package cannot be treated as production-ready merely because it installs cleanly and has no known CVE; its license obligations also need an explicit decision.
The gate enumerates the installed environment, checks module-manifest pip_dependencies against a maintained license map, and fails on GPL or AGPL packages that are absent from the documented exception allowlist. Each accepted exception must identify the package and license and record a human-readable reason; missing or malformed policy data fails the check rather than silently weakening it.
This is a compliance control, not a malware detector or a promise that an upstream package is benign. Unknown license data still needs review. Its value is narrower and important: license decisions become visible, reproducible release evidence instead of an assumption made after a dependency is already embedded in a production candidate.
Together, license validation, vulnerability auditing, Socket analysis, lock-bound artifact review, and the blocked-release policy form the production-readiness boundary for SpecFact CLI. Each catches a different class of risk; none should be used as an excuse to skip the others.
Security Review Is Now Part of AI Development
This PR changed the working practice as well as the repository. Before it, Codex Security had been explored a few times but was not installed and run locally inside the Codex app as part of day-to-day development. Socket Security had already been onboarded as a required PR signal, but this was the first real CVE/security issue where its finding had to drive a concrete delivery decision.
That changes the review model for AI-assisted development. Tests, types, linting, and AI code review remain necessary—but they do not establish whether a newly introduced dependency, lock update, generated artifact, or release helper is acceptable to ship. Security review is now a key review element, not a late compliance activity.
The local workflow is explicit rather than automatic: when a change warrants security review, run the relevant Codex Security slash command in the Codex app and review the resulting evidence before treating the work as ready. The trigger points are practical:
- dependency manifests, lock files, SBOM tooling, or package-manager configuration change;
- a workflow installs, publishes, signs, or otherwise executes a new artifact;
- an AI-assisted change introduces a new library, network boundary, deserialisation path, or privileged automation; and
- a PR is ready for review but the change has a meaningful supply-chain or security boundary.
This does not turn every edit into a ritual. It gives high-risk changes a local security pass while the implementation context is still fresh, then keeps Socket as an independent PR-level check. The two views are complementary: local security review follows the code and control flow; Socket follows dependency and supply-chain signals.
For larger organisations, the consequence is operational as well as technical. One malicious dependency, unsafe downstream code path, or unresolved CVE can cause an application or release to be quarantined during inventory and security classification. If the risk cannot be explained and remediated, a project can become too costly to approve or maintain. That is why a fast AI development loop must include a security-review loop with equally concrete evidence.
Evidence, Not Decorative Green Checks
The change was developed with a test for the policy failure: a record for the Socket-alerted pycparser==3.0 must still be rejected. It also added regression coverage for cross-record URL and digest matching, package-name normalisation, pre-install ordering, runner shadowing, and unsafe refresh outputs.
The retained dependency-trust security review and PR evidence are the source material for this account. Socket’s GitHub checks did not preserve a useful annotation list through the GitHub check-run API, so this post does not manufacture one. It links the PR, the commits, the review record, and the resulting tested policy instead.
What Still Remains True
No lock file, checksum, or review register can prove that an upstream package is harmless. Source-provenance review remains maintainer evidence, not automated proof of an upstream source repository. The documented MCP advisory exception is intentionally time-bounded and must be renewed, removed, or replaced when it expires.
That is not a reason to avoid automation. It is the reason to give automation a narrow, honest job: preserve immutable evidence, stop known-bad releases, make exceptions visible and temporary, and leave a review trail when the available evidence is incomplete.
PR #652 began with a goal of making delivery reproducible. It ended by making one principle explicit: when a security signal changes the trust decision, the release process must change with it.