border-customs 0.3.2 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +161 -1
- package/dist/assets/release-src/releaseCoherence.ts +423 -0
- package/dist/assets/residue-src/crates.ts +13 -0
- package/dist/assets/residue-src/npm.ts +14 -0
- package/dist/assets/residue-src/pypi.ts +20 -0
- package/dist/assets/residue-src/rubygems.ts +12 -0
- package/dist/index.js +1629 -499
- package/package.json +1 -1
|
@@ -28,6 +28,7 @@ import { EngineRunError, type EngineOptions } from "../engines/support.ts";
|
|
|
28
28
|
import { LIFECYCLE_SCRIPT_KEYS, parseNpmManifest, unexpectedEntries } from "./manifestDiff.ts";
|
|
29
29
|
import { residueFindings, scanNpmLifecycleHook, sweepResidueNpmArtifact } from "./residue.ts";
|
|
30
30
|
import { RESIDUE_T1_RULE } from "../rules/residueMatchers.ts";
|
|
31
|
+
import { npmPackCoherenceFindings } from "../rules/releaseCoherence.ts";
|
|
31
32
|
import {
|
|
32
33
|
NPM_LIFECYCLE_RULE,
|
|
33
34
|
NPM_PUBLINT_RULE,
|
|
@@ -197,6 +198,19 @@ export async function runNpmArtifactStage(o: NpmStageOptions): Promise<NpmStageR
|
|
|
197
198
|
// not the working tree — npm may rewrite package.json inside the tarball.
|
|
198
199
|
const packedManifest = parseNpmManifest(readFileSync(join(extractDir, packedManifestRel), "utf8"));
|
|
199
200
|
|
|
201
|
+
// W4.1 release coherence: packed package.json ↔ a packed package-lock.json (root +
|
|
202
|
+
// packages[""]). ABSENT-SOURCE BOUNDARY (load-bearing): locks are usually NOT in the
|
|
203
|
+
// tarball (files whitelist) — an unpacked lock is not drift and never a finding;
|
|
204
|
+
// false-positive gates just train users to pass --force. See rules/releaseCoherence.ts.
|
|
205
|
+
const lockRel = `${root}/package-lock.json`;
|
|
206
|
+
findings.push(...npmPackCoherenceFindings({
|
|
207
|
+
packedVersion: packedManifest.version,
|
|
208
|
+
lockText: relPaths.includes(lockRel) ? readFileSync(join(extractDir, lockRel), "utf8") : null,
|
|
209
|
+
lockRelPath: lockRel,
|
|
210
|
+
identity,
|
|
211
|
+
...(o.sanitizer !== undefined ? { sanitizer: o.sanitizer } : {}),
|
|
212
|
+
}));
|
|
213
|
+
|
|
200
214
|
// RESIDUE-CONTRACT §1.6/§3 (R2): a hook is downgraded to MEDIUM ONLY when the
|
|
201
215
|
// classifier matches closed signature #1 in full; every other outcome — unknown
|
|
202
216
|
// grammar, unreadable target, out-of-signature literal — keeps the 0.2.0
|
|
@@ -40,6 +40,7 @@ import { basename, dirname, join, relative, resolve } from "node:path";
|
|
|
40
40
|
import { extractArchive, removeSandbox } from "./extract.ts";
|
|
41
41
|
import { residueFindings } from "./residue.ts";
|
|
42
42
|
import { residuePypiHits } from "./residuePy.ts";
|
|
43
|
+
import { pypiArtifactCoherenceFindings } from "../rules/releaseCoherence.ts";
|
|
43
44
|
import type { Finding } from "../findings.ts";
|
|
44
45
|
import { globToRegExp } from "../rules/artifactMatchers.ts";
|
|
45
46
|
import { scanTree } from "../engines/gitleaks.ts";
|
|
@@ -316,6 +317,25 @@ export async function scanPyPiArtifacts(o: PypiInput): Promise<PypiScanResult> {
|
|
|
316
317
|
identity: basename(artifact.path),
|
|
317
318
|
...(o.sanitizer !== undefined ? { sanitizer: o.sanitizer } : {}),
|
|
318
319
|
})) absorb(f, f.path ?? "");
|
|
320
|
+
// W4.1 release coherence: every version source inside THIS artifact — wheel:
|
|
321
|
+
// filename ↔ dist-info dir ↔ METADATA Version ↔ __version__ literals (the aihr
|
|
322
|
+
// replay is caught from the wheel bytes alone); sdist: pyproject/setup.* ↔
|
|
323
|
+
// PKG-INFO ↔ __version__. Regex-text probes only, nothing executed; a source
|
|
324
|
+
// absent from the tree is NOT drift (releaseCoherence.ts header boundary).
|
|
325
|
+
for (const f of pypiArtifactCoherenceFindings({
|
|
326
|
+
kind: artifact.kind,
|
|
327
|
+
archiveName: basename(artifact.path),
|
|
328
|
+
files: rest,
|
|
329
|
+
read: (rel: string) => {
|
|
330
|
+
try {
|
|
331
|
+
return readFileSync(join(scanRoot, rel), "utf8");
|
|
332
|
+
} catch {
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
},
|
|
336
|
+
identity: basename(artifact.path),
|
|
337
|
+
...(o.sanitizer !== undefined ? { sanitizer: o.sanitizer } : {}),
|
|
338
|
+
})) absorb(f, f.path ?? "");
|
|
319
339
|
if (artifact.kind === "sdist") for (const f of manifestFindings(o, rest, target)) absorb(f, f.path ?? "");
|
|
320
340
|
} finally {
|
|
321
341
|
removeSandbox(sandbox);
|
|
@@ -32,6 +32,7 @@ import { BORDER_STATE_DIR, ensureStateDir } from "../check/lock.ts";
|
|
|
32
32
|
import { extractArchive, removeSandbox } from "./extract.ts";
|
|
33
33
|
import { type ResidueHit, residueFindings } from "./residue.ts";
|
|
34
34
|
import { residueGemHits } from "./residueGem.ts";
|
|
35
|
+
import { gemCoherenceFindings } from "../rules/releaseCoherence.ts";
|
|
35
36
|
import { scanTree } from "../engines/gitleaks.ts";
|
|
36
37
|
import { scanPaths, type SecretlintMode } from "../engines/secretlint.ts";
|
|
37
38
|
import { EngineMissingError, EngineRunError, type EngineOptions } from "../engines/support.ts";
|
|
@@ -265,6 +266,17 @@ export async function runRubygemsArtifactStage(o: GemStageOptions): Promise<GemS
|
|
|
265
266
|
})) {
|
|
266
267
|
findings.push(f);
|
|
267
268
|
}
|
|
269
|
+
// W4.1 release coherence: metadata.gz is the shipped bytes' own Gem::Specification —
|
|
270
|
+
// reconcile it against the built filename. NOT vacuous: gemspec.ts's GEMSPEC_VERSION
|
|
271
|
+
// regex takes the FIRST literal s.version= while Ruby assignment order makes the LAST
|
|
272
|
+
// win, so a twice-assigned literal drifts border's filename from the real spec.
|
|
273
|
+
findings.push(...gemCoherenceFindings({
|
|
274
|
+
specName: identity.name,
|
|
275
|
+
specVersion: identity.version,
|
|
276
|
+
metadataText: metadata,
|
|
277
|
+
identity: gemName,
|
|
278
|
+
...(o.sanitizer !== undefined ? { sanitizer: o.sanitizer } : {}),
|
|
279
|
+
}));
|
|
268
280
|
}
|
|
269
281
|
} finally {
|
|
270
282
|
removeSandbox(extractDir);
|