Skip to content

Repository files navigation

facts

Socket Badge Coverage

Follow @SocketSecurity Follow @socket.dev on Bluesky

Generation of .socket.facts.json and the wire contracts around it.

Why this repo exists

Generation of .socket.facts.json for JVM projects has crossed the same ownership boundary twice: socket-cli delegated it to a reachability partner in June 2026 and reversed the delegation 26 days later, because the partner re-resolved the build later in the pipeline and diverged from the scan-time build on dynamically-versioned projects. Both sides now carry emitters and schemas that describe the same bytes, so this package holds one copy of each and both consumers import it instead of the code moving back and forth again.

This package never decides whether a build-tool invocation is trustworthy. socket.json lets a repository supply the build binary and its options, which makes that an attacker-controlled input and the consumer's call. So runFactsGeneration takes an invocation that is already resolved and already vetted — an absolute bin, an explicit opts array, an explicit env, an explicit cwd — and throws when any of them is missing rather than falling back to a PATH lookup, a wrapper probe, or process.env. It runs the invocation; it does not assemble one.

The property everything here protects is that resolution happens once, at scan time, against the developer's real build. A resolver that reads a cache or parses a manifest statically passes a literal-version fixture and diverges on a real project, so the conformance suite is built out of selectors — a wildcard, a bounded range, a SNAPSHOT, and a project version generated per run — whose answers appear in no committed file, and it diffs what this package emitted against what the build tool itself reports resolving. There is no static fallback: a build tool that cannot run is a loud failure, never a degraded success.

sdxgen is the other intended consumer, and the dependency runs sdxgen → facts: its JVM lane converts these facts to CycloneDX, which lets it drop its own duplicated Gradle init script and replace best-effort sbt regex parsing with real plugin-driven resolution. Component identity here is purl-shaped and the edge list is addressable so that conversion is comfortable, but the converter belongs on sdxgen's side, not this one.

Install

npm install @socketsecurity/facts

Usage

Run a build tool's emitter against an invocation you have already resolved:

import { runFactsGeneration } from '@socketsecurity/facts'

const { artifactPaths, code, facts, report } = await runFactsGeneration({
  tool: 'gradle',
  // Absolute, and chosen by your trust policy — not looked up here.
  bin: '/repo/gradlew',
  // Already filtered by your trust policy.
  opts: [],
  // Explicit. GRADLE_OPTS, JAVA_TOOL_OPTIONS, MAVEN_OPTS, SBT_OPTS, and
  // _JAVA_OPTIONS are stripped by default; pass envPolicy: 'as-given' to keep them.
  env: { JAVA_HOME: process.env.JAVA_HOME, PATH: process.env.PATH },
  cwd: '/repo',
  withFiles: true,
})

Share the wire contracts instead of re-declaring them:

import {
  assertResolvedPathsSidecar,
  validateSocketFactsSbom,
} from '@socketsecurity/facts/contract'

// Throws with what / where / saw-vs-wanted / fix, including on an additive
// field, which a strict consumer rejects wholesale.
const sidecar = assertResolvedPathsSidecar(payload, 'reachability sidecar read')

Verify emitted facts against the build's own answer:

import {
  compareFactsToGroundTruth,
  conformanceViolations,
  parseGradleDependencyTree,
} from '@socketsecurity/facts/conformance'

// `gradle dependencies --configuration runtimeClasspath` output.
const truth = parseGradleDependencyTree(report)
const divergence = conformanceViolations(
  compareFactsToGroundTruth(facts, truth),
)

Resolve the emitter assets rather than guessing where they live:

import {
  assertMavenExtensionBuilt,
  gradleInitScriptPath,
  sbtPluginSourcePath,
} from '@socketsecurity/facts/assets'

Development

Contributor commands
pnpm install
pnpm run check
pnpm test

Build the Maven core extension, which needs a JDK and is not part of pnpm run build:

pnpm run build:maven-extension

The dynamic-version conformance suite needs Gradle, Maven, and a JDK. It skips loudly when they are absent; set SOCKET_FACTS_REQUIRE_COMPAT=1 to turn that skip into a failure on a machine that should have them:

SOCKET_FACTS_REQUIRE_COMPAT=1 pnpm test test/repo/compat

License

MIT


Socket

About

Socket facts generation — build-tool emitters and the .socket.facts.json contract, shared by socket-cli and Coana

Resources

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages