up
Some checks failed
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Policy Simulation / policy-simulate (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-12-13 09:37:15 +02:00
parent e00f6365da
commit 6e45066e37
349 changed files with 17160 additions and 1867 deletions

View File

@@ -196,6 +196,25 @@ public sealed class BunLanguageAnalyzerTests
cancellationToken);
}
[Fact]
public async Task PatchedMultiVersionIsParsedAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var fixturePath = TestPaths.ResolveFixture("lang", "bun", "patched-multi-version");
var goldenPath = Path.Combine(fixturePath, "expected.json");
var analyzers = new ILanguageAnalyzer[]
{
new BunLanguageAnalyzer()
};
await LanguageAnalyzerTestHarness.AssertDeterministicAsync(
fixturePath,
goldenPath,
analyzers,
cancellationToken);
}
[Fact]
public async Task DeepDependencyTreeIsParsedAsync()
{
@@ -252,4 +271,80 @@ public sealed class BunLanguageAnalyzerTests
analyzers,
cancellationToken);
}
[Fact]
public async Task ContainerLayersAreDiscoveredAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var fixturePath = TestPaths.ResolveFixture("lang", "bun", "container-layers");
var goldenPath = Path.Combine(fixturePath, "expected.json");
var analyzers = new ILanguageAnalyzer[]
{
new BunLanguageAnalyzer()
};
await LanguageAnalyzerTestHarness.AssertDeterministicAsync(
fixturePath,
goldenPath,
analyzers,
cancellationToken);
}
[Fact]
public async Task BunfigOnlyEmitsDeclaredOnlyAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var fixturePath = TestPaths.ResolveFixture("lang", "bun", "bunfig-only");
var goldenPath = Path.Combine(fixturePath, "expected.json");
var analyzers = new ILanguageAnalyzer[]
{
new BunLanguageAnalyzer()
};
await LanguageAnalyzerTestHarness.AssertDeterministicAsync(
fixturePath,
goldenPath,
analyzers,
cancellationToken);
}
[Fact]
public async Task LockfileDevClassificationIsDeterministicAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var fixturePath = TestPaths.ResolveFixture("lang", "bun", "lockfile-dev-classification");
var goldenPath = Path.Combine(fixturePath, "expected.json");
var analyzers = new ILanguageAnalyzer[]
{
new BunLanguageAnalyzer()
};
await LanguageAnalyzerTestHarness.AssertDeterministicAsync(
fixturePath,
goldenPath,
analyzers,
cancellationToken);
}
[Fact]
public async Task NonConcreteVersionsUseExplicitKeyAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var fixturePath = TestPaths.ResolveFixture("lang", "bun", "non-concrete-versions");
var goldenPath = Path.Combine(fixturePath, "expected.json");
var analyzers = new ILanguageAnalyzer[]
{
new BunLanguageAnalyzer()
};
await LanguageAnalyzerTestHarness.AssertDeterministicAsync(
fixturePath,
goldenPath,
analyzers,
cancellationToken);
}
}

View File

@@ -0,0 +1,74 @@
[
{
"analyzerId": "bun",
"componentKey": "explicit::bun::npm::left-pad::sha256:8ad9c18ee1a619ce3a224346fe984c4ced211ac443ebf7d709a93f1343ef8ba2",
"name": "left-pad",
"type": "npm",
"usedByEntrypoint": false,
"metadata": {
"declared.locator": "package.json#dependencies",
"declared.scope": "prod",
"declared.source": "package.json",
"declared.sourceType": "range",
"declared.versionSpec": "^1.3.0",
"declaredOnly": "true",
"packageManager": "bun"
},
"evidence": [
{
"kind": "file",
"source": "package.json",
"locator": "package.json",
"sha256": "465919e1195aa0b066f473c55341df77abff6a6b7d62e25d63ccfb7c13e3287b"
}
]
},
{
"analyzerId": "bun",
"componentKey": "explicit::bun::npm::local-file::sha256:61b6ef7b8e24fe3a1e1080296c61f2ca4ad8839f453e24cb8adf874678521caa",
"name": "local-file",
"type": "npm",
"usedByEntrypoint": false,
"metadata": {
"declared.locator": "package.json#dependencies",
"declared.scope": "prod",
"declared.source": "package.json",
"declared.sourceType": "file",
"declared.versionSpec": "file:../local-file",
"declaredOnly": "true",
"packageManager": "bun"
},
"evidence": [
{
"kind": "file",
"source": "package.json",
"locator": "package.json",
"sha256": "465919e1195aa0b066f473c55341df77abff6a6b7d62e25d63ccfb7c13e3287b"
}
]
},
{
"analyzerId": "bun",
"componentKey": "explicit::bun::npm::typescript::sha256:5a0a88f051ea20b8875334dadc5bce3c0861d146b151ab7bab95654541b7a168",
"name": "typescript",
"type": "npm",
"usedByEntrypoint": false,
"metadata": {
"declared.locator": "package.json#devDependencies",
"declared.scope": "dev",
"declared.source": "package.json",
"declared.sourceType": "range",
"declared.versionSpec": "~5.3.0",
"declaredOnly": "true",
"packageManager": "bun"
},
"evidence": [
{
"kind": "file",
"source": "package.json",
"locator": "package.json",
"sha256": "465919e1195aa0b066f473c55341df77abff6a6b7d62e25d63ccfb7c13e3287b"
}
]
}
]

View File

@@ -0,0 +1,11 @@
{
"name": "bunfig-only-fixture",
"private": true,
"dependencies": {
"left-pad": "^1.3.0",
"local-file": "file:../local-file"
},
"devDependencies": {
"typescript": "~5.3.0"
}
}

View File

@@ -0,0 +1,6 @@
{
"lockfileVersion": 1,
"packages": {
"ms@2.1.3": ["https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="]
}
}

View File

@@ -0,0 +1,7 @@
{
"name": "bun-container-layers-fixture",
"version": "1.0.0",
"dependencies": {
"ms": "^2.1.3"
}
}

View File

@@ -0,0 +1,34 @@
[
{
"analyzerId": "bun",
"componentKey": "purl::pkg:npm/ms@2.1.3",
"purl": "pkg:npm/ms@2.1.3",
"name": "ms",
"version": "2.1.3",
"type": "npm",
"usedByEntrypoint": false,
"metadata": {
"direct": "true",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"packageManager": "bun",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"source": "bun.lock"
},
"evidence": [
{
"kind": "metadata",
"source": "integrity",
"locator": ".layers/layer0/app/bun.lock:packages[ms@2.1.3]",
"value": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"sha256": "4a384b14aba7740bd500cdf0da7329a41a2940662e9b1fcab1fbc71c6c8389e7"
},
{
"kind": "metadata",
"source": "resolved",
"locator": ".layers/layer0/app/bun.lock:packages[ms@2.1.3]",
"value": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"sha256": "4a384b14aba7740bd500cdf0da7329a41a2940662e9b1fcab1fbc71c6c8389e7"
}
]
}
]

View File

@@ -22,20 +22,23 @@
{
"kind": "file",
"source": "node_modules",
"locator": "node_modules/@company/internal-pkg/package.json"
"locator": "node_modules/@company/internal-pkg/package.json",
"sha256": "f5311f43a95bd76e1912dbd7d0a5b3611baa9e82bcf72d5dc7f34c5f71f0ddf4"
},
{
"kind": "metadata",
"source": "integrity",
"locator": "bun.lock",
"value": "sha512-customhash123=="
"locator": "bun.lock:packages[@company/internal-pkg@1.0.0]",
"value": "sha512-customhash123==",
"sha256": "eb3bacf736d4a1b3cf9e02357afc1add9f20323916ce62cf8748c9ad9a80f195"
},
{
"kind": "metadata",
"source": "resolved",
"locator": "bun.lock",
"value": "https://npm.company.com/@company/internal-pkg/-/internal-pkg-1.0.0.tgz"
"locator": "bun.lock:packages[@company/internal-pkg@1.0.0]",
"value": "https://npm.company.com/@company/internal-pkg/-/internal-pkg-1.0.0.tgz",
"sha256": "eb3bacf736d4a1b3cf9e02357afc1add9f20323916ce62cf8748c9ad9a80f195"
}
]
}
]
]

View File

@@ -19,19 +19,22 @@
{
"kind": "file",
"source": "node_modules",
"locator": "node_modules/debug/package.json"
"locator": "node_modules/debug/package.json",
"sha256": "2258b5b4d7e5ed711aeef1a86d5d9e5abf2a04410e05bd89ea806e423417e493"
},
{
"kind": "metadata",
"source": "integrity",
"locator": "bun.lock",
"value": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX\u002B7G/vCNNhehwxfkQ=="
"locator": "bun.lock:packages[debug@4.3.4]",
"value": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX\u002B7G/vCNNhehwxfkQ==",
"sha256": "33d4886c0591242ffb78b5e739c5248c81559312586d59d543d48387e4bb6a2b"
},
{
"kind": "metadata",
"source": "resolved",
"locator": "bun.lock",
"value": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"
"locator": "bun.lock:packages[debug@4.3.4]",
"value": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"sha256": "33d4886c0591242ffb78b5e739c5248c81559312586d59d543d48387e4bb6a2b"
}
]
},
@@ -54,20 +57,23 @@
{
"kind": "file",
"source": "node_modules",
"locator": "node_modules/ms/package.json"
"locator": "node_modules/ms/package.json",
"sha256": "ae11c4ce44027a95893e8c890aed0c582f04e8cf1b8022931eddcb613cd9d3f7"
},
{
"kind": "metadata",
"source": "integrity",
"locator": "bun.lock",
"value": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
"locator": "bun.lock:packages[ms@2.1.3]",
"value": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"sha256": "33d4886c0591242ffb78b5e739c5248c81559312586d59d543d48387e4bb6a2b"
},
{
"kind": "metadata",
"source": "resolved",
"locator": "bun.lock",
"value": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"
"locator": "bun.lock:packages[ms@2.1.3]",
"value": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"sha256": "33d4886c0591242ffb78b5e739c5248c81559312586d59d543d48387e4bb6a2b"
}
]
}
]
]

View File

@@ -21,14 +21,16 @@
{
"kind": "file",
"source": "node_modules",
"locator": "node_modules/my-git-pkg/package.json"
"locator": "node_modules/my-git-pkg/package.json",
"sha256": "45687abed9d301c361987ca877da135e830c80dc3ce37f9ea1c74c7df96b8bf2"
},
{
"kind": "metadata",
"source": "resolved",
"locator": "bun.lock",
"value": "git\u002Bhttps://github.com/user/my-git-pkg.git#abc123def456"
"locator": "bun.lock:packages[my-git-pkg@1.0.0]",
"value": "git\u002Bhttps://github.com/user/my-git-pkg.git#abc123def456",
"sha256": "819a7efc185bd1314d21aa7fdc0e5b2134a0c9b758ecd9daa62cb6cba2feddd0"
}
]
}
]
]

View File

@@ -18,19 +18,22 @@
{
"kind": "file",
"source": "node_modules",
"locator": "node_modules/.bun/is-number@6.0.0/package.json"
"locator": "node_modules/.bun/is-number@6.0.0/package.json",
"sha256": "0324c895ec4aa4049c77371f08e937eed97a58e442595a8834ba21afd8e100b3"
},
{
"kind": "metadata",
"source": "integrity",
"locator": "bun.lock",
"value": "sha512-Wu1VZAVuL1snqOnHLxJ0l2p3pjlzLnMcJ8gJhaTZVfP7VFKN7fSJ8X/gR0qFCLwfFJ0Rqd3IxfS\u002BTY/Lc1Q7Pw=="
"locator": "bun.lock:packages[is-number@6.0.0]",
"value": "sha512-Wu1VZAVuL1snqOnHLxJ0l2p3pjlzLnMcJ8gJhaTZVfP7VFKN7fSJ8X/gR0qFCLwfFJ0Rqd3IxfS\u002BTY/Lc1Q7Pw==",
"sha256": "746b6c809e50ee2d7bdb27a0ee43046d48fa5f21d7597bbadd3bd44269798812"
},
{
"kind": "metadata",
"source": "resolved",
"locator": "bun.lock",
"value": "https://registry.npmjs.org/is-number/-/is-number-6.0.0.tgz"
"locator": "bun.lock:packages[is-number@6.0.0]",
"value": "https://registry.npmjs.org/is-number/-/is-number-6.0.0.tgz",
"sha256": "746b6c809e50ee2d7bdb27a0ee43046d48fa5f21d7597bbadd3bd44269798812"
}
]
},
@@ -54,20 +57,23 @@
{
"kind": "file",
"source": "node_modules",
"locator": "node_modules/.bun/is-odd@3.0.1/package.json"
"locator": "node_modules/.bun/is-odd@3.0.1/package.json",
"sha256": "beb18158821ecb86f3bb2a6be3ef817c0b8dcdc3e05a53e0b9a1c62d74a595ac"
},
{
"kind": "metadata",
"source": "integrity",
"locator": "bun.lock",
"value": "sha512-CQpnWPrDwmP1\u002BSMHXvTXAoSEu2mCPgMU0VKt1WcA7D8VXCo4HfVNlUbD1k8Tg0BVDX/LhyRaZqKqiS4vI6tTHg=="
"locator": "bun.lock:packages[is-odd@3.0.1]",
"value": "sha512-CQpnWPrDwmP1\u002BSMHXvTXAoSEu2mCPgMU0VKt1WcA7D8VXCo4HfVNlUbD1k8Tg0BVDX/LhyRaZqKqiS4vI6tTHg==",
"sha256": "746b6c809e50ee2d7bdb27a0ee43046d48fa5f21d7597bbadd3bd44269798812"
},
{
"kind": "metadata",
"source": "resolved",
"locator": "bun.lock",
"value": "https://registry.npmjs.org/is-odd/-/is-odd-3.0.1.tgz"
"locator": "bun.lock:packages[is-odd@3.0.1]",
"value": "https://registry.npmjs.org/is-odd/-/is-odd-3.0.1.tgz",
"sha256": "746b6c809e50ee2d7bdb27a0ee43046d48fa5f21d7597bbadd3bd44269798812"
}
]
}
]
]

View File

@@ -19,20 +19,23 @@
{
"kind": "file",
"source": "node_modules",
"locator": "node_modules/lodash/package.json"
"locator": "node_modules/lodash/package.json",
"sha256": "82145cd4bdc9a690c14843b405179c60aeda1a958029f6ae62776c1b26e42169"
},
{
"kind": "metadata",
"source": "integrity",
"locator": "bun.lock",
"value": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vz1kAmtILi\u002B8fm9nJMg7b0GN8sMEJz2mxG/S7mNxhWQ7\u002BD9bF8Q=="
"locator": "bun.lock:packages[lodash@4.17.21]",
"value": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vz1kAmtILi\u002B8fm9nJMg7b0GN8sMEJz2mxG/S7mNxhWQ7\u002BD9bF8Q==",
"sha256": "7b34fdbdf0cb3e0d07e25f7d7f452491dcfad421138449217a1c20b2f66a6475"
},
{
"kind": "metadata",
"source": "resolved",
"locator": "bun.lock",
"value": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz"
"locator": "bun.lock:packages[lodash@4.17.21]",
"value": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"sha256": "7b34fdbdf0cb3e0d07e25f7d7f452491dcfad421138449217a1c20b2f66a6475"
}
]
}
]
]

View File

@@ -0,0 +1,10 @@
{
"lockfileVersion": 1,
"packages": {
"prod-pkg@1.0.0": ["https://registry.npmjs.org/prod-pkg/-/prod-pkg-1.0.0.tgz", null, {"shared": "^1.0.0"}],
"dev-pkg@1.0.0": ["https://registry.npmjs.org/dev-pkg/-/dev-pkg-1.0.0.tgz", null, {"dev-only": "^1.0.0"}],
"shared@1.0.0": ["https://registry.npmjs.org/shared/-/shared-1.0.0.tgz", null],
"dev-only@1.0.0": ["https://registry.npmjs.org/dev-only/-/dev-only-1.0.0.tgz", null]
}
}

View File

@@ -0,0 +1,98 @@
[
{
"analyzerId": "bun",
"componentKey": "purl::pkg:npm/dev-only@1.0.0",
"purl": "pkg:npm/dev-only@1.0.0",
"name": "dev-only",
"version": "1.0.0",
"type": "npm",
"usedByEntrypoint": false,
"metadata": {
"dev": "true",
"packageManager": "bun",
"resolved": "https://registry.npmjs.org/dev-only/-/dev-only-1.0.0.tgz",
"source": "bun.lock"
},
"evidence": [
{
"kind": "metadata",
"source": "resolved",
"locator": "bun.lock:packages[dev-only@1.0.0]",
"value": "https://registry.npmjs.org/dev-only/-/dev-only-1.0.0.tgz",
"sha256": "4d40cc185e492e4544a6dc3b17cdfd77096e4d4260569a243eb694befbada6ac"
}
]
},
{
"analyzerId": "bun",
"componentKey": "purl::pkg:npm/dev-pkg@1.0.0",
"purl": "pkg:npm/dev-pkg@1.0.0",
"name": "dev-pkg",
"version": "1.0.0",
"type": "npm",
"usedByEntrypoint": false,
"metadata": {
"dev": "true",
"direct": "true",
"packageManager": "bun",
"resolved": "https://registry.npmjs.org/dev-pkg/-/dev-pkg-1.0.0.tgz",
"source": "bun.lock"
},
"evidence": [
{
"kind": "metadata",
"source": "resolved",
"locator": "bun.lock:packages[dev-pkg@1.0.0]",
"value": "https://registry.npmjs.org/dev-pkg/-/dev-pkg-1.0.0.tgz",
"sha256": "4d40cc185e492e4544a6dc3b17cdfd77096e4d4260569a243eb694befbada6ac"
}
]
},
{
"analyzerId": "bun",
"componentKey": "purl::pkg:npm/prod-pkg@1.0.0",
"purl": "pkg:npm/prod-pkg@1.0.0",
"name": "prod-pkg",
"version": "1.0.0",
"type": "npm",
"usedByEntrypoint": false,
"metadata": {
"direct": "true",
"packageManager": "bun",
"resolved": "https://registry.npmjs.org/prod-pkg/-/prod-pkg-1.0.0.tgz",
"source": "bun.lock"
},
"evidence": [
{
"kind": "metadata",
"source": "resolved",
"locator": "bun.lock:packages[prod-pkg@1.0.0]",
"value": "https://registry.npmjs.org/prod-pkg/-/prod-pkg-1.0.0.tgz",
"sha256": "4d40cc185e492e4544a6dc3b17cdfd77096e4d4260569a243eb694befbada6ac"
}
]
},
{
"analyzerId": "bun",
"componentKey": "purl::pkg:npm/shared@1.0.0",
"purl": "pkg:npm/shared@1.0.0",
"name": "shared",
"version": "1.0.0",
"type": "npm",
"usedByEntrypoint": false,
"metadata": {
"packageManager": "bun",
"resolved": "https://registry.npmjs.org/shared/-/shared-1.0.0.tgz",
"source": "bun.lock"
},
"evidence": [
{
"kind": "metadata",
"source": "resolved",
"locator": "bun.lock:packages[shared@1.0.0]",
"value": "https://registry.npmjs.org/shared/-/shared-1.0.0.tgz",
"sha256": "4d40cc185e492e4544a6dc3b17cdfd77096e4d4260569a243eb694befbada6ac"
}
]
}
]

View File

@@ -0,0 +1,11 @@
{
"name": "bun-lockfile-dev-classification-fixture",
"version": "1.0.0",
"dependencies": {
"prod-pkg": "^1.0.0"
},
"devDependencies": {
"dev-pkg": "^1.0.0"
}
}

View File

@@ -18,15 +18,17 @@
{
"kind": "metadata",
"source": "integrity",
"locator": "bun.lock",
"value": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
"locator": "bun.lock:packages[ms@2.1.3]",
"value": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"sha256": "4a384b14aba7740bd500cdf0da7329a41a2940662e9b1fcab1fbc71c6c8389e7"
},
{
"kind": "metadata",
"source": "resolved",
"locator": "bun.lock",
"value": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"
"locator": "bun.lock:packages[ms@2.1.3]",
"value": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"sha256": "4a384b14aba7740bd500cdf0da7329a41a2940662e9b1fcab1fbc71c6c8389e7"
}
]
}
]
]

View File

@@ -19,19 +19,22 @@
{
"kind": "file",
"source": "node_modules",
"locator": "node_modules/lodash/package.json"
"locator": "node_modules/lodash/package.json",
"sha256": "82145cd4bdc9a690c14843b405179c60aeda1a958029f6ae62776c1b26e42169"
},
{
"kind": "metadata",
"source": "integrity",
"locator": "bun.lock",
"value": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vz1kAmtILi\u002B8fm9nJMg7b0GN8sMEJz2mxG/S7mNxhWQ7\u002BD9bF8Q=="
"locator": "bun.lock:packages[lodash@4.17.21]",
"value": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vz1kAmtILi\u002B8fm9nJMg7b0GN8sMEJz2mxG/S7mNxhWQ7\u002BD9bF8Q==",
"sha256": "8a0d37c3761b81514ee397c3836ccff48167ce6aa1afdfd484ca7679e586df4a"
},
{
"kind": "metadata",
"source": "resolved",
"locator": "bun.lock",
"value": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz"
"locator": "bun.lock:packages[lodash@4.17.21]",
"value": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"sha256": "8a0d37c3761b81514ee397c3836ccff48167ce6aa1afdfd484ca7679e586df4a"
}
]
},
@@ -55,20 +58,23 @@
{
"kind": "file",
"source": "node_modules",
"locator": "node_modules/ms/package.json"
"locator": "node_modules/ms/package.json",
"sha256": "ae11c4ce44027a95893e8c890aed0c582f04e8cf1b8022931eddcb613cd9d3f7"
},
{
"kind": "metadata",
"source": "integrity",
"locator": "bun.lock",
"value": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
"locator": "bun.lock:packages[ms@2.1.3]",
"value": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"sha256": "8a0d37c3761b81514ee397c3836ccff48167ce6aa1afdfd484ca7679e586df4a"
},
{
"kind": "metadata",
"source": "resolved",
"locator": "bun.lock",
"value": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"
"locator": "bun.lock:packages[ms@2.1.3]",
"value": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"sha256": "8a0d37c3761b81514ee397c3836ccff48167ce6aa1afdfd484ca7679e586df4a"
}
]
}
]
]

View File

@@ -0,0 +1,11 @@
{
"name": "bun-multi-workspace-fixture",
"version": "1.0.0",
"workspaces": [
"packages/*"
],
"dependencies": {
"lodash": "^4.17.21",
"ms": "^2.1.3"
}
}

View File

@@ -0,0 +1,21 @@
{
"lockfileVersion": 1,
"packages": {
"file-pkg@file:../file-pkg.tgz": [
"file:../file-pkg.tgz",
null,
{}
],
"link-pkg@link:../link-pkg": [
"link:../link-pkg",
null,
{}
],
"local-pkg@workspace:*": [
"workspace:packages/local-pkg",
null,
{}
]
}
}

View File

@@ -0,0 +1,80 @@
[
{
"analyzerId": "bun",
"componentKey": "explicit::bun::npm::file-pkg::sha256:c541f5764a7e2fdea9fc5789b13e404f8e15ffc8db0110a81346552c607c89ff",
"name": "file-pkg",
"type": "npm",
"usedByEntrypoint": false,
"metadata": {
"direct": "true",
"nonConcreteVersion": "true",
"packageManager": "bun",
"resolved": "file:../file-pkg.tgz",
"source": "bun.lock",
"sourceType": "file",
"specifier": "file:../file-pkg.tgz",
"versionSpec": "file:../file-pkg.tgz"
},
"evidence": [
{
"kind": "metadata",
"source": "resolved",
"locator": "bun.lock:packages[file-pkg@file:../file-pkg.tgz]",
"value": "file:../file-pkg.tgz",
"sha256": "d7ae02476b6737ea3056226ea69e36bacb664feacd7a5223bc66ea287757656b"
}
]
},
{
"analyzerId": "bun",
"componentKey": "explicit::bun::npm::link-pkg::sha256:ebb0c3119ab319e05c02e2448f1d6b4a23dc69076bf8dbfbe95657a3405d1b11",
"name": "link-pkg",
"type": "npm",
"usedByEntrypoint": false,
"metadata": {
"direct": "true",
"nonConcreteVersion": "true",
"packageManager": "bun",
"resolved": "link:../link-pkg",
"source": "bun.lock",
"sourceType": "link",
"specifier": "link:../link-pkg",
"versionSpec": "link:../link-pkg"
},
"evidence": [
{
"kind": "metadata",
"source": "resolved",
"locator": "bun.lock:packages[link-pkg@link:../link-pkg]",
"value": "link:../link-pkg",
"sha256": "d7ae02476b6737ea3056226ea69e36bacb664feacd7a5223bc66ea287757656b"
}
]
},
{
"analyzerId": "bun",
"componentKey": "explicit::bun::npm::local-pkg::sha256:cafb6902358bb6a2503a67d71abe50446cdcb3c8359dfb6f3ab00ee1672a5c07",
"name": "local-pkg",
"type": "npm",
"usedByEntrypoint": false,
"metadata": {
"direct": "true",
"nonConcreteVersion": "true",
"packageManager": "bun",
"resolved": "workspace:packages/local-pkg",
"source": "bun.lock",
"sourceType": "workspace",
"specifier": "workspace:packages/local-pkg",
"versionSpec": "workspace:*"
},
"evidence": [
{
"kind": "metadata",
"source": "resolved",
"locator": "bun.lock:packages[local-pkg@workspace:*]",
"value": "workspace:packages/local-pkg",
"sha256": "d7ae02476b6737ea3056226ea69e36bacb664feacd7a5223bc66ea287757656b"
}
]
}
]

View File

@@ -0,0 +1,10 @@
{
"name": "non-concrete-versions",
"version": "1.0.0",
"dependencies": {
"file-pkg": "file:../file-pkg.tgz",
"link-pkg": "link:../link-pkg",
"local-pkg": "workspace:*"
}
}

View File

@@ -0,0 +1,8 @@
{
"lockfileVersion": 1,
"packages": {
"lodash@4.17.21": ["https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "sha512-lodash-421"],
"lodash@4.17.20": ["https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", "sha512-lodash-420"]
}
}

View File

@@ -0,0 +1,86 @@
[
{
"analyzerId": "bun",
"componentKey": "purl::pkg:npm/lodash@4.17.20",
"purl": "pkg:npm/lodash@4.17.20",
"name": "lodash",
"version": "4.17.20",
"type": "npm",
"usedByEntrypoint": false,
"metadata": {
"direct": "true",
"integrity": "sha512-lodash-420",
"packageManager": "bun",
"patchFile": "patches/lodash@4.17.20.patch",
"patched": "true",
"path": "node_modules/a/node_modules/lodash",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
"scopeUnknown": "true",
"source": "node_modules"
},
"evidence": [
{
"kind": "file",
"source": "node_modules",
"locator": "node_modules/a/node_modules/lodash/package.json",
"sha256": "a883443850ed2188979ee56e2cf8200fa34935a65aae606d85d1aaa60d8ff32e"
},
{
"kind": "metadata",
"source": "integrity",
"locator": "bun.lock:packages[lodash@4.17.20]",
"value": "sha512-lodash-420",
"sha256": "e83cd6aa810c1a8af47d6ae0eb621a8a5dc13b23ec08925ad9b5ff4d035cfc7c"
},
{
"kind": "metadata",
"source": "resolved",
"locator": "bun.lock:packages[lodash@4.17.20]",
"value": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
"sha256": "e83cd6aa810c1a8af47d6ae0eb621a8a5dc13b23ec08925ad9b5ff4d035cfc7c"
}
]
},
{
"analyzerId": "bun",
"componentKey": "purl::pkg:npm/lodash@4.17.21",
"purl": "pkg:npm/lodash@4.17.21",
"name": "lodash",
"version": "4.17.21",
"type": "npm",
"usedByEntrypoint": false,
"metadata": {
"direct": "true",
"integrity": "sha512-lodash-421",
"packageManager": "bun",
"patchFile": "patches/lodash@4.17.21.patch",
"patched": "true",
"path": "node_modules/lodash",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"scopeUnknown": "true",
"source": "node_modules"
},
"evidence": [
{
"kind": "file",
"source": "node_modules",
"locator": "node_modules/lodash/package.json",
"sha256": "1bb77ea984b96ef61781adcc6299a2a1c5f9e42dcf594264cdbb96aa06f5c813"
},
{
"kind": "metadata",
"source": "integrity",
"locator": "bun.lock:packages[lodash@4.17.21]",
"value": "sha512-lodash-421",
"sha256": "e83cd6aa810c1a8af47d6ae0eb621a8a5dc13b23ec08925ad9b5ff4d035cfc7c"
},
{
"kind": "metadata",
"source": "resolved",
"locator": "bun.lock:packages[lodash@4.17.21]",
"value": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"sha256": "e83cd6aa810c1a8af47d6ae0eb621a8a5dc13b23ec08925ad9b5ff4d035cfc7c"
}
]
}
]

View File

@@ -0,0 +1,12 @@
{
"name": "patched-multi-version-fixture",
"version": "1.0.0",
"dependencies": {
"lodash": "^4.17.21"
},
"patchedDependencies": {
"lodash@4.17.21": "patches/lodash@4.17.21.patch",
"lodash@4.17.20": "patches/lodash@4.17.20.patch"
}
}

View File

@@ -0,0 +1,8 @@
diff --git a/index.js b/index.js
index 0000000..2222222 100644
--- a/index.js
+++ b/index.js
@@
-// placeholder
+// patched

View File

@@ -0,0 +1,8 @@
diff --git a/index.js b/index.js
index 0000000..1111111 100644
--- a/index.js
+++ b/index.js
@@
-// placeholder
+// patched

View File

@@ -21,20 +21,23 @@
{
"kind": "file",
"source": "node_modules",
"locator": "node_modules/lodash/package.json"
"locator": "node_modules/lodash/package.json",
"sha256": "82145cd4bdc9a690c14843b405179c60aeda1a958029f6ae62776c1b26e42169"
},
{
"kind": "metadata",
"source": "integrity",
"locator": "bun.lock",
"value": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vz1kAmtILi\u002B8fm9nJMg7b0GN8sMEJz2mxG/S7mNxhWQ7\u002BD9bF8Q=="
"locator": "bun.lock:packages[lodash@4.17.21]",
"value": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vz1kAmtILi\u002B8fm9nJMg7b0GN8sMEJz2mxG/S7mNxhWQ7\u002BD9bF8Q==",
"sha256": "61ff5c565c08f6564bd16153c10feba4a171986510aaf40f84fe710eabd180c2"
},
{
"kind": "metadata",
"source": "resolved",
"locator": "bun.lock",
"value": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz"
"locator": "bun.lock:packages[lodash@4.17.21]",
"value": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"sha256": "61ff5c565c08f6564bd16153c10feba4a171986510aaf40f84fe710eabd180c2"
}
]
}
]
]

View File

@@ -19,19 +19,22 @@
{
"kind": "file",
"source": "node_modules",
"locator": "node_modules/@babel/core/package.json"
"locator": "node_modules/@babel/core/package.json",
"sha256": "c4d995bed6c0ec71ccf6ecb74ee8f20b1431798bd93e54182afcb6870b6cfa23"
},
{
"kind": "metadata",
"source": "integrity",
"locator": "bun.lock",
"value": "sha512-fQfkg0Gjkza3nf0c7/w6Xf34BW4YvzNfACRLmmb7XRLa6XHdR\u002BK9AlJlxneFfWYf6uhOzuzZVTjF/8KfndZANw=="
"locator": "bun.lock:packages[@babel/core@7.24.0]",
"value": "sha512-fQfkg0Gjkza3nf0c7/w6Xf34BW4YvzNfACRLmmb7XRLa6XHdR\u002BK9AlJlxneFfWYf6uhOzuzZVTjF/8KfndZANw==",
"sha256": "6ffde82e85e550d36bdb577210cd80c56cbd36c02dbfb4d8ec6ada27643bcd2d"
},
{
"kind": "metadata",
"source": "resolved",
"locator": "bun.lock",
"value": "https://registry.npmjs.org/@babel/core/-/core-7.24.0.tgz"
"locator": "bun.lock:packages[@babel/core@7.24.0]",
"value": "https://registry.npmjs.org/@babel/core/-/core-7.24.0.tgz",
"sha256": "6ffde82e85e550d36bdb577210cd80c56cbd36c02dbfb4d8ec6ada27643bcd2d"
}
]
},
@@ -55,20 +58,23 @@
{
"kind": "file",
"source": "node_modules",
"locator": "node_modules/@types/node/package.json"
"locator": "node_modules/@types/node/package.json",
"sha256": "db7446931abf3479f92734485e30ee7631923d056bcfa5b210159008524f40e2"
},
{
"kind": "metadata",
"source": "integrity",
"locator": "bun.lock",
"value": "sha512-o9bjXmDNcF7GbM4CNQpmi\u002BTutCgap/K3w1JyKgxXjVJa7b8XWCF/wPH2E/0Vz9e\u002BV1B3eXX0WCw\u002BINcAobvUag=="
"locator": "bun.lock:packages[@types/node@20.11.0]",
"value": "sha512-o9bjXmDNcF7GbM4CNQpmi\u002BTutCgap/K3w1JyKgxXjVJa7b8XWCF/wPH2E/0Vz9e\u002BV1B3eXX0WCw\u002BINcAobvUag==",
"sha256": "6ffde82e85e550d36bdb577210cd80c56cbd36c02dbfb4d8ec6ada27643bcd2d"
},
{
"kind": "metadata",
"source": "resolved",
"locator": "bun.lock",
"value": "https://registry.npmjs.org/@types/node/-/node-20.11.0.tgz"
"locator": "bun.lock:packages[@types/node@20.11.0]",
"value": "https://registry.npmjs.org/@types/node/-/node-20.11.0.tgz",
"sha256": "6ffde82e85e550d36bdb577210cd80c56cbd36c02dbfb4d8ec6ada27643bcd2d"
}
]
}
]
]

View File

@@ -19,20 +19,23 @@
{
"kind": "file",
"source": "node_modules",
"locator": "node_modules/lodash/package.json"
"locator": "node_modules/lodash/package.json",
"sha256": "bfe21067561ba47f62c290400e6208b95ac875f0c41e00c4dddce889e8a8ad4e"
},
{
"kind": "metadata",
"source": "integrity",
"locator": "bun.lock",
"value": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vz1kAmtILi\u002B8fm9nJMg7b0GN8sMEJz2mxG/S7mNxhWQ7\u002BD9bF8Q=="
"locator": "bun.lock:packages[lodash@4.17.21]",
"value": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vz1kAmtILi\u002B8fm9nJMg7b0GN8sMEJz2mxG/S7mNxhWQ7\u002BD9bF8Q==",
"sha256": "61ff5c565c08f6564bd16153c10feba4a171986510aaf40f84fe710eabd180c2"
},
{
"kind": "metadata",
"source": "resolved",
"locator": "bun.lock",
"value": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz"
"locator": "bun.lock:packages[lodash@4.17.21]",
"value": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"sha256": "61ff5c565c08f6564bd16153c10feba4a171986510aaf40f84fe710eabd180c2"
}
]
}
]
]

View File

@@ -19,20 +19,23 @@
{
"kind": "file",
"source": "node_modules",
"locator": "node_modules/safe-pkg/package.json"
"locator": "node_modules/safe-pkg/package.json",
"sha256": "1ade6129984f59a954ec2c175075e74cb2759ba97b9b04acf76537262b0f35af"
},
{
"kind": "metadata",
"source": "integrity",
"locator": "bun.lock",
"value": "sha512-abc123"
"locator": "bun.lock:packages[safe-pkg@1.0.0]",
"value": "sha512-abc123",
"sha256": "54dd0b2c2f30e59b29970d34350d083b295789e056e849361da5be932d1ef747"
},
{
"kind": "metadata",
"source": "resolved",
"locator": "bun.lock",
"value": "https://registry.npmjs.org/safe-pkg/-/safe-pkg-1.0.0.tgz"
"locator": "bun.lock:packages[safe-pkg@1.0.0]",
"value": "https://registry.npmjs.org/safe-pkg/-/safe-pkg-1.0.0.tgz",
"sha256": "54dd0b2c2f30e59b29970d34350d083b295789e056e849361da5be932d1ef747"
}
]
}
]
]

View File

@@ -19,20 +19,23 @@
{
"kind": "file",
"source": "node_modules",
"locator": "node_modules/chalk/package.json"
"locator": "node_modules/chalk/package.json",
"sha256": "7d6ff4f365c8d42bae13a48bb4bc84e4cef4e7a7bd7b211e0662ef62fb675736"
},
{
"kind": "metadata",
"source": "integrity",
"locator": "bun.lock",
"value": "sha512-dLitG79d\u002BGV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos\u002Buw7WmWF4wUwBd9jxjocFC2w=="
"locator": "bun.lock:packages[chalk@5.3.0]",
"value": "sha512-dLitG79d\u002BGV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos\u002Buw7WmWF4wUwBd9jxjocFC2w==",
"sha256": "8706c5aecdc68ae4f06c6a2f1bfa9e431e473a961c2f32063911febaba0c65cc"
},
{
"kind": "metadata",
"source": "resolved",
"locator": "bun.lock",
"value": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz"
"locator": "bun.lock:packages[chalk@5.3.0]",
"value": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
"sha256": "8706c5aecdc68ae4f06c6a2f1bfa9e431e473a961c2f32063911febaba0c65cc"
}
]
}
]
]

View File

@@ -322,7 +322,31 @@ public sealed class BunLockParserTests
Assert.Single(result.AllEntries);
var entry = result.AllEntries[0];
Assert.Single(entry.Dependencies);
Assert.Contains("ms", entry.Dependencies);
Assert.Equal("ms", entry.Dependencies[0].Name);
Assert.Equal("^2.1.3", entry.Dependencies[0].Specifier);
Assert.False(entry.Dependencies[0].IsOptionalPeer);
}
[Fact]
public void Parse_ArrayFormat_ExtractsOptionalPeerDependencies()
{
var content = """
{
"lockfileVersion": 1,
"packages": {
"pkg@1.0.0": ["https://registry.npmjs.org/pkg/-/pkg-1.0.0.tgz", null, {}, {"react": "^18.0.0"}]
}
}
""";
var result = BunLockParser.Parse(content);
Assert.Single(result.AllEntries);
var entry = result.AllEntries[0];
Assert.Single(entry.Dependencies);
Assert.Equal("react", entry.Dependencies[0].Name);
Assert.Equal("^18.0.0", entry.Dependencies[0].Specifier);
Assert.True(entry.Dependencies[0].IsOptionalPeer);
}
[Fact]

View File

@@ -0,0 +1,30 @@
using StellaOps.Scanner.Analyzers.Lang.Bun.Internal;
using StellaOps.Scanner.Analyzers.Lang.Tests.TestUtilities;
namespace StellaOps.Scanner.Analyzers.Lang.Bun.Tests.Parsers;
public sealed class BunLockScopeClassifierTests
{
[Fact]
public void IncludeDevFalse_FiltersDevOnlyPackages()
{
var fixturePath = TestPaths.ResolveFixture("lang", "bun", "lockfile-dev-classification");
var lockPath = Path.Combine(fixturePath, "bun.lock");
var lockData = BunLockParser.Parse(File.ReadAllText(lockPath));
var declared = BunDeclaredDependencyCollector.Collect(fixturePath);
var classified = BunLockScopeClassifier.Classify(lockData, declared);
Assert.True(classified.FindEntry("dev-pkg", "1.0.0")?.IsDev ?? false);
Assert.True(classified.FindEntry("dev-only", "1.0.0")?.IsDev ?? false);
Assert.False(classified.FindEntry("prod-pkg", "1.0.0")?.IsDev ?? true);
Assert.False(classified.FindEntry("shared", "1.0.0")?.IsDev ?? true);
var filtered = BunLockInventory.ExtractPackages(classified, includeDev: false);
Assert.DoesNotContain(filtered, package => package.Name == "dev-pkg");
Assert.DoesNotContain(filtered, package => package.Name == "dev-only");
Assert.Contains(filtered, package => package.Name == "prod-pkg");
Assert.Contains(filtered, package => package.Name == "shared");
}
}

View File

@@ -222,7 +222,7 @@ public sealed class BunPackageTests
var resolvedEvidence = evidence.FirstOrDefault(e => e.Source == "resolved");
Assert.NotNull(resolvedEvidence);
Assert.Equal(LanguageEvidenceKind.Metadata, resolvedEvidence.Kind);
Assert.Equal("bun.lock", resolvedEvidence.Locator);
Assert.Equal("bun.lock:packages[lodash@4.17.21]", resolvedEvidence.Locator);
Assert.Equal("https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", resolvedEvidence.Value);
// Integrity evidence
@@ -265,7 +265,7 @@ public sealed class BunPackageTests
IsOptional = false,
IsPeer = false,
SourceType = "npm",
Dependencies = new List<string> { "debug" }
Dependencies = new List<BunLockDependency> { new("debug", "^4.3.4", IsOptionalPeer: false) }
};
var package = BunPackage.FromLockEntry(lockEntry, "bun.lock");

View File

@@ -192,8 +192,8 @@ public sealed class BunWorkspaceHelperTests : IDisposable
var result = BunWorkspaceHelper.ParseWorkspaceInfo(_tempDir);
Assert.Single(result.PatchedDependencies);
Assert.True(result.PatchedDependencies.ContainsKey("lodash"));
Assert.Equal("patches/lodash@4.17.21.patch", result.PatchedDependencies["lodash"]);
Assert.True(result.PatchedDependencies.ContainsKey("lodash@4.17.21"));
Assert.Equal("patches/lodash@4.17.21.patch", result.PatchedDependencies["lodash@4.17.21"]);
}
[Fact]
@@ -215,8 +215,10 @@ public sealed class BunWorkspaceHelperTests : IDisposable
var result = BunWorkspaceHelper.ParseWorkspaceInfo(_tempDir);
Assert.Equal(2, result.PatchedDependencies.Count);
Assert.True(result.PatchedDependencies.ContainsKey("lodash"));
Assert.True(result.PatchedDependencies.ContainsKey("@babel+core"));
Assert.True(result.PatchedDependencies.ContainsKey("lodash@4.17.21"));
Assert.Equal("patches/lodash@4.17.21.patch", result.PatchedDependencies["lodash@4.17.21"]);
Assert.True(result.PatchedDependencies.ContainsKey("@babel/core@7.24.0"));
Assert.Equal("patches/@babel+core@7.24.0.patch", result.PatchedDependencies["@babel/core@7.24.0"]);
}
[Fact]
@@ -237,7 +239,8 @@ public sealed class BunWorkspaceHelperTests : IDisposable
var result = BunWorkspaceHelper.ParseWorkspaceInfo(_tempDir);
Assert.Single(result.PatchedDependencies);
Assert.True(result.PatchedDependencies.ContainsKey("ms"));
Assert.True(result.PatchedDependencies.ContainsKey("ms@2.1.3"));
Assert.Equal(".patches/ms@2.1.3.patch", result.PatchedDependencies["ms@2.1.3"]);
}
[Fact]

View File

@@ -17,12 +17,10 @@
<PackageReference Remove="xunit" />
<PackageReference Remove="xunit.runner.visualstudio" />
<PackageReference Remove="Microsoft.AspNetCore.Mvc.Testing" />
<PackageReference Remove="Mongo2Go" />
<PackageReference Remove="coverlet.collector" />
<PackageReference Remove="Microsoft.Extensions.TimeProvider.Testing" />
<ProjectReference Remove="..\StellaOps.Concelier.Testing\StellaOps.Concelier.Testing.csproj" />
<Compile Remove="$(MSBuildThisFileDirectory)..\StellaOps.Concelier.Tests.Shared\AssemblyInfo.cs" />
<Compile Remove="$(MSBuildThisFileDirectory)..\StellaOps.Concelier.Tests.Shared\MongoFixtureCollection.cs" />
<Using Remove="StellaOps.Concelier.Testing" />
</ItemGroup>

View File

@@ -17,7 +17,7 @@ public sealed class DenoRuntimePathHasherTests
var identity = DenoRuntimePathHasher.Create(root, absolute);
Assert.Equal("subdir/main.ts", identity.Normalized);
Assert.Equal("2d0ef79c25b433a216f41853e89d8e1e1e1ef0b0e77d12b37a7f4f7c2a25f635", identity.PathSha256);
Assert.Equal("c3b59fd8169cee9cc111b4737e733f8c0227403717e04f37cba870c49c7ff2c3", identity.PathSha256);
}
finally
{
@@ -33,7 +33,7 @@ public sealed class DenoRuntimePathHasherTests
{
var identity = DenoRuntimePathHasher.Create(root, root);
Assert.Equal(".", identity.Normalized);
Assert.Equal("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", identity.PathSha256);
Assert.Equal("cdb4ee2aea69cc6a83331bbe96dc2caa9a299d21329efb0336fc02a82e1839a8", identity.PathSha256);
}
finally
{

View File

@@ -27,6 +27,6 @@ public sealed class DenoRuntimeTraceProbeTests
Assert.Equal(new[] { "https://deno.land" }, metadata.RemoteOrigins);
Assert.Equal(new[] { "fs", "net" }, metadata.UniquePermissions);
Assert.Equal("8f67e4b77f2ea4155d9101c5e6a45922e4ac1e19006955c3e6c2afe1938f0a8d", hash);
Assert.Equal("97f26acf896f0c2da77079885f6462cc7b589597b505532b09ae4bc6d1c0f314", hash);
}
}

View File

@@ -45,13 +45,13 @@ public sealed class DenoRuntimeTraceRecorderTests
Assert.Equal(2, snapshot.Metadata.ModuleLoads);
Assert.Equal(1, snapshot.Metadata.PermissionUses);
Assert.Equal(new[] { "https://deno.land/x/std" }, snapshot.Metadata.RemoteOrigins);
Assert.Equal(new[] { "net" }, snapshot.Metadata.UniquePermissions);
Assert.Equal(new[] { "fs", "net" }, snapshot.Metadata.UniquePermissions);
Assert.Equal(0, snapshot.Metadata.NpmResolutions);
Assert.Equal(0, snapshot.Metadata.WasmLoads);
Assert.Equal(1, snapshot.Metadata.DynamicImports);
// Stable hash check
Assert.Equal("198c6e038f1c39a78a52b844f051bfa6eaa5312faa66f1bc73d2f6d1048d8a7a", snapshot.Sha256);
Assert.Equal("61584731fc2870d972c78a86cb307d6f6dc5e110473d61b5bf61208f8be55e7a", snapshot.Sha256);
}
finally
{

View File

@@ -46,6 +46,6 @@ public sealed class DenoRuntimeTraceSerializerTests
";
Assert.Equal(expectedNdjson.Replace("\r\n", "\n"), text.Replace("\r\n", "\n"));
Assert.Equal("fdc6f07fe6b18b4cdd228c44b83e61d63063b7bd3422a2d3ab8000ac8420ceb0", hash);
Assert.Equal("9e74e46f576beafcfe76cd33b2f2f207bd2f1ba3cc86e045383c1afd52134961", hash);
}
}

View File

@@ -32,6 +32,9 @@ public sealed class DenoAnalyzerGoldenTests
normalized = normalized.TrimEnd();
expected = expected.TrimEnd();
normalized = normalized.Replace("\r\n", "\n", StringComparison.Ordinal).Replace("\r", "\n", StringComparison.Ordinal);
expected = expected.Replace("\r\n", "\n", StringComparison.Ordinal).Replace("\r", "\n", StringComparison.Ordinal);
if (!string.Equals(expected, normalized, StringComparison.Ordinal))
{
var actualPath = golden + ".actual";
@@ -201,9 +204,21 @@ public sealed class DenoAnalyzerGoldenTests
var altRoot = workspaceRoot.Replace("/", "\\", StringComparison.Ordinal);
var altRootLower = altRoot.ToLowerInvariant();
var altRootEscaped = altRoot.Replace("\\", "\\\\", StringComparison.Ordinal);
var altRootLowerEscaped = altRootLower.Replace("\\", "\\\\", StringComparison.Ordinal);
var altRootDoubleEscaped = altRootEscaped.Replace("\\", "\\\\", StringComparison.Ordinal);
var altRootLowerDoubleEscaped = altRootLowerEscaped.Replace("\\", "\\\\", StringComparison.Ordinal);
result = result
.Replace(altRoot, "<workspace>", StringComparison.Ordinal)
.Replace(altRootLower, "<workspace>", StringComparison.Ordinal);
.Replace(altRootLower, "<workspace>", StringComparison.Ordinal)
.Replace(altRootEscaped, "<workspace>", StringComparison.Ordinal)
.Replace(altRootLowerEscaped, "<workspace>", StringComparison.Ordinal)
.Replace(altRootDoubleEscaped, "<workspace>", StringComparison.Ordinal)
.Replace(altRootLowerDoubleEscaped, "<workspace>", StringComparison.Ordinal);
result = result
.Replace("\\\\\\\\", "/", StringComparison.Ordinal)
.Replace("\\\\", "/", StringComparison.Ordinal);
return result;
}

View File

@@ -14,12 +14,10 @@
<PackageReference Remove="xunit" />
<PackageReference Remove="xunit.runner.visualstudio" />
<PackageReference Remove="Microsoft.AspNetCore.Mvc.Testing" />
<PackageReference Remove="Mongo2Go" />
<PackageReference Remove="coverlet.collector" />
<PackageReference Remove="Microsoft.Extensions.TimeProvider.Testing" />
<ProjectReference Remove="..\\StellaOps.Concelier.Testing\\StellaOps.Concelier.Testing.csproj" />
<Compile Remove="$(MSBuildThisFileDirectory)..\\StellaOps.Concelier.Tests.Shared\\AssemblyInfo.cs" />
<Compile Remove="$(MSBuildThisFileDirectory)..\\StellaOps.Concelier.Tests.Shared\\MongoFixtureCollection.cs" />
<Using Remove="StellaOps.Concelier.Testing" />
</ItemGroup>

View File

@@ -6,7 +6,7 @@
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<!-- Disable Concelier test infrastructure - this project doesn't need MongoDB -->
<!-- Disable Concelier test infrastructure - not needed for scanner tests -->
<UseConcelierTestInfra>false</UseConcelierTestInfra>
</PropertyGroup>
@@ -16,7 +16,6 @@
<PackageReference Remove="xunit" />
<PackageReference Remove="xunit.runner.visualstudio" />
<PackageReference Remove="Microsoft.AspNetCore.Mvc.Testing" />
<PackageReference Remove="Mongo2Go" />
<PackageReference Remove="coverlet.collector" />
<PackageReference Remove="Microsoft.Extensions.TimeProvider.Testing" />
<PackageReference Remove="SharpCompress" />

View File

@@ -1,24 +1,24 @@
[
{
"analyzerId": "golang",
"componentKey": "golang::bin::sha256:7125d65230b913faa744a33acd884899c81a1dbc6d88cbf251a74b19621cde99",
"name": "app",
"type": "bin",
"usedByEntrypoint": false,
"metadata": {
"binary.sha256": "7125d65230b913faa744a33acd884899c81a1dbc6d88cbf251a74b19621cde99",
"binaryPath": "app",
"go.version.hint": "go1.22.8",
"languageHint": "golang",
"provenance": "binary"
{
"analyzerId": "golang",
"componentKey": "golang::bin::sha256:80f528c90b72a4c4cc3fa078501154e4f2a3f49faea3ec380112d61740bde4c3",
"name": "app",
"type": "bin",
"usedByEntrypoint": false,
"metadata": {
"binary.sha256": "80f528c90b72a4c4cc3fa078501154e4f2a3f49faea3ec380112d61740bde4c3",
"binaryPath": "app",
"go.version.hint": "go1.22.8",
"languageHint": "golang",
"provenance": "binary"
},
"evidence": [
{
"kind": "file",
"source": "binary",
"locator": "app",
"sha256": "7125d65230b913faa744a33acd884899c81a1dbc6d88cbf251a74b19621cde99"
},
"kind": "file",
"source": "binary",
"locator": "app",
"sha256": "80f528c90b72a4c4cc3fa078501154e4f2a3f49faea3ec380112d61740bde4c3"
},
{
"kind": "metadata",
"source": "go.heuristic",

View File

@@ -14,12 +14,10 @@
<PackageReference Remove="xunit" />
<PackageReference Remove="xunit.runner.visualstudio" />
<PackageReference Remove="Microsoft.AspNetCore.Mvc.Testing" />
<PackageReference Remove="Mongo2Go" />
<PackageReference Remove="coverlet.collector" />
<PackageReference Remove="Microsoft.Extensions.TimeProvider.Testing" />
<ProjectReference Remove="..\StellaOps.Concelier.Testing\StellaOps.Concelier.Testing.csproj" />
<Compile Remove="$(MSBuildThisFileDirectory)..\StellaOps.Concelier.Tests.Shared\AssemblyInfo.cs" />
<Compile Remove="$(MSBuildThisFileDirectory)..\StellaOps.Concelier.Tests.Shared\MongoFixtureCollection.cs" />
<Using Remove="StellaOps.Concelier.Testing" />
</ItemGroup>

View File

@@ -0,0 +1,35 @@
[
{
"analyzerId": "java",
"componentKey": "purl::pkg:maven/com/example/pomxml-only@1.2.3",
"purl": "pkg:maven/com/example/pomxml-only@1.2.3",
"name": "pomxml-only",
"version": "1.2.3",
"type": "maven",
"usedByEntrypoint": true,
"metadata": {
"artifactId": "pomxml-only",
"displayName": "PomXml Only",
"groupId": "com.example",
"jarPath": "libs/pomxml-only.jar",
"manifestTitle": "PomXml Only",
"manifestVendor": "Example Corp",
"manifestVersion": "1.2.3",
"packaging": "jar"
},
"evidence": [
{
"kind": "file",
"source": "MANIFEST.MF",
"locator": "libs/pomxml-only.jar!META-INF/MANIFEST.MF",
"value": "title=PomXml Only;version=1.2.3;vendor=Example Corp"
},
{
"kind": "file",
"source": "pom.xml",
"locator": "libs/pomxml-only.jar!META-INF/maven/com.example/pomxml-only/pom.xml",
"sha256": "9a315451470e76bb25c2a77ecdf03982aed210f1cbccab480c79eb1d4a5a79a5"
}
]
}
]

View File

@@ -0,0 +1,65 @@
[
{
"analyzerId": "java",
"componentKey": "purl::pkg:maven/com/example/app-fat@1.0.0",
"purl": "pkg:maven/com/example/app-fat@1.0.0",
"name": "app-fat",
"version": "1.0.0",
"type": "maven",
"usedByEntrypoint": true,
"metadata": {
"artifactId": "app-fat",
"displayName": "App Fat",
"embeddedScan.candidateJars": "1",
"embeddedScan.emittedComponents": "1",
"embeddedScan.scannedJars": "1",
"groupId": "com.example",
"jarPath": "apps/app-fat.jar",
"manifestTitle": "App Fat",
"manifestVendor": "Example Corp",
"manifestVersion": "1.0.0",
"packaging": "jar"
},
"evidence": [
{
"kind": "file",
"source": "MANIFEST.MF",
"locator": "apps/app-fat.jar!META-INF/MANIFEST.MF",
"value": "title=App Fat;version=1.0.0;vendor=Example Corp"
},
{
"kind": "file",
"source": "pom.properties",
"locator": "apps/app-fat.jar!META-INF/maven/com.example/app-fat/pom.properties",
"sha256": "bba5da43d59efe9726f4195a86581d53b01bd449603fd2536fab29d720dcb806"
}
]
},
{
"analyzerId": "java",
"componentKey": "purl::pkg:maven/com/example/embedded-lib@2.1.0",
"purl": "pkg:maven/com/example/embedded-lib@2.1.0",
"name": "embedded-lib",
"version": "2.1.0",
"type": "maven",
"usedByEntrypoint": true,
"metadata": {
"artifactId": "embedded-lib",
"displayName": "Embedded Lib",
"embedded": "true",
"embedded.containerJarPath": "apps/app-fat.jar",
"embedded.entryPath": "BOOT-INF/lib/embedded-lib.jar",
"groupId": "com.example",
"jarPath": "apps/app-fat.jar!BOOT-INF/lib/embedded-lib.jar",
"packaging": "jar"
},
"evidence": [
{
"kind": "file",
"source": "pom.properties",
"locator": "apps/app-fat.jar!BOOT-INF/lib/embedded-lib.jar!META-INF/maven/com.example/embedded-lib/pom.properties",
"sha256": "45cbc64bcc2dcf25ee71a698cd35a676d79b0ed09cc77b61fead907c6345081f"
}
]
}
]

View File

@@ -0,0 +1,65 @@
[
{
"analyzerId": "java",
"componentKey": "purl::pkg:maven/com/example/demo-war@1.0.0?type=war",
"purl": "pkg:maven/com/example/demo-war@1.0.0?type=war",
"name": "demo-war",
"version": "1.0.0",
"type": "maven",
"usedByEntrypoint": true,
"metadata": {
"artifactId": "demo-war",
"displayName": "Demo War",
"embeddedScan.candidateJars": "1",
"embeddedScan.emittedComponents": "1",
"embeddedScan.scannedJars": "1",
"groupId": "com.example",
"jarPath": "apps/demo-war.war",
"manifestTitle": "Demo War",
"manifestVendor": "Example Corp",
"manifestVersion": "1.0.0",
"packaging": "war"
},
"evidence": [
{
"kind": "file",
"source": "MANIFEST.MF",
"locator": "apps/demo-war.war!META-INF/MANIFEST.MF",
"value": "title=Demo War;version=1.0.0;vendor=Example Corp"
},
{
"kind": "file",
"source": "pom.properties",
"locator": "apps/demo-war.war!META-INF/maven/com.example/demo-war/pom.properties",
"sha256": "cb57c79ca5007119bfb0fafd6ae24a6702e508116d5e799835392df742a49460"
}
]
},
{
"analyzerId": "java",
"componentKey": "purl::pkg:maven/com/example/web-lib@3.0.0",
"purl": "pkg:maven/com/example/web-lib@3.0.0",
"name": "web-lib",
"version": "3.0.0",
"type": "maven",
"usedByEntrypoint": true,
"metadata": {
"artifactId": "web-lib",
"displayName": "Web Lib",
"embedded": "true",
"embedded.containerJarPath": "apps/demo-war.war",
"embedded.entryPath": "WEB-INF/lib/web-lib.jar",
"groupId": "com.example",
"jarPath": "apps/demo-war.war!WEB-INF/lib/web-lib.jar",
"packaging": "jar"
},
"evidence": [
{
"kind": "file",
"source": "pom.properties",
"locator": "apps/demo-war.war!WEB-INF/lib/web-lib.jar!META-INF/maven/com.example/web-lib/pom.properties",
"sha256": "ab7151e977ef21d48c459395dbb0f88395a3a33b2f5903a28b7d78b53cb8880d"
}
]
}
]

View File

@@ -36,6 +36,81 @@ public sealed class JavaLanguageAnalyzerTests
}
}
[Fact]
public async Task ExtractsMavenArtifactsFromSpringBootFatJarEmbeddedLibrariesAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var root = TestPaths.CreateTemporaryDirectory();
try
{
var jarPath = JavaFixtureBuilder.CreateSpringBootFatJarWithEmbeddedMavenLibrary(root);
var usageHints = new LanguageUsageHints(new[] { jarPath });
var analyzers = new ILanguageAnalyzer[] { new JavaLanguageAnalyzer() };
var goldenPath = TestPaths.ResolveFixture("java", "spring-boot-fat-embedded-maven", "expected.json");
await LanguageAnalyzerTestHarness.AssertDeterministicAsync(
fixturePath: root,
goldenPath: goldenPath,
analyzers: analyzers,
cancellationToken: cancellationToken,
usageHints: usageHints);
}
finally
{
TestPaths.SafeDelete(root);
}
}
[Fact]
public async Task ExtractsMavenArtifactsFromWarEmbeddedLibrariesAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var root = TestPaths.CreateTemporaryDirectory();
try
{
var warPath = JavaFixtureBuilder.CreateWarArchiveWithEmbeddedMavenLibrary(root);
var usageHints = new LanguageUsageHints(new[] { warPath });
var analyzers = new ILanguageAnalyzer[] { new JavaLanguageAnalyzer() };
var goldenPath = TestPaths.ResolveFixture("java", "war-embedded-maven", "expected.json");
await LanguageAnalyzerTestHarness.AssertDeterministicAsync(
fixturePath: root,
goldenPath: goldenPath,
analyzers: analyzers,
cancellationToken: cancellationToken,
usageHints: usageHints);
}
finally
{
TestPaths.SafeDelete(root);
}
}
[Fact]
public async Task ExtractsMavenArtifactsFromPomXmlOnlyJarAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var root = TestPaths.CreateTemporaryDirectory();
try
{
var jarPath = JavaFixtureBuilder.CreatePomXmlOnlyJar(root);
var usageHints = new LanguageUsageHints(new[] { jarPath });
var analyzers = new ILanguageAnalyzer[] { new JavaLanguageAnalyzer() };
var goldenPath = TestPaths.ResolveFixture("java", "pomxml-only-jar", "expected.json");
await LanguageAnalyzerTestHarness.AssertDeterministicAsync(
fixturePath: root,
goldenPath: goldenPath,
analyzers: analyzers,
cancellationToken: cancellationToken,
usageHints: usageHints);
}
finally
{
TestPaths.SafeDelete(root);
}
}
[Fact]
public async Task LockfilesProduceDeclaredOnlyComponentsAsync()
{
@@ -157,7 +232,12 @@ public sealed class JavaLanguageAnalyzerTests
WritePomProperties(archive, "com.example", "demo-jni", "1.0.0");
WriteManifest(archive, "demo-jni", "1.0.0", "com.example");
CreateBinaryEntry(archive, "com/example/App.class", "System.loadLibrary(\"foo\")");
var classEntry = archive.CreateEntry("com/example/App.class");
var classBytes = JavaClassFileFactory.CreateSystemLoadLibraryInvoker("com/example/App", "foo");
using (var classStream = classEntry.Open())
{
classStream.Write(classBytes);
}
CreateTextEntry(archive, "lib/native/libfoo.so");
CreateTextEntry(archive, "META-INF/native-image/demo/jni-config.json");
}
@@ -177,7 +257,197 @@ public sealed class JavaLanguageAnalyzerTests
var metadata = component.GetProperty("metadata");
Assert.Equal("libfoo.so", metadata.GetProperty("jni.nativeLibs").GetString());
Assert.Equal("demo-jni.jar!META-INF/native-image/demo/jni-config.json", metadata.GetProperty("jni.graalConfig").GetString());
Assert.Equal("demo-jni.jar!com/example/App.class", metadata.GetProperty("jni.loadCalls").GetString());
Assert.Equal("1", metadata.GetProperty("jni.edgeCount").GetString());
Assert.Equal("0", metadata.GetProperty("jni.nativeMethodCount").GetString());
Assert.Equal("1", metadata.GetProperty("jni.loadCallCount").GetString());
Assert.Equal("SystemLoadLibrary", metadata.GetProperty("jni.reasons").GetString());
Assert.Equal("foo", metadata.GetProperty("jni.targetLibraries").GetString());
}
finally
{
TestPaths.SafeDelete(root);
}
}
[Fact]
public async Task ExtractsMavenArtifactFromEmbeddedJarAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var root = TestPaths.CreateTemporaryDirectory();
try
{
var jarPath = Path.Combine(root, "demo-fat.jar");
Directory.CreateDirectory(Path.GetDirectoryName(jarPath)!);
using (var archive = ZipFile.Open(jarPath, ZipArchiveMode.Create))
{
WritePomProperties(archive, "com.example", "demo-fat", "1.0.0");
WriteManifest(archive, "demo-fat", "1.0.0", "com.example");
using var embeddedBuffer = new MemoryStream();
using (var embeddedJar = new ZipArchive(embeddedBuffer, ZipArchiveMode.Create, leaveOpen: true))
{
WritePomProperties(embeddedJar, "com.example", "embedded-lib", "2.0.0");
}
embeddedBuffer.Position = 0;
var embeddedEntry = archive.CreateEntry("BOOT-INF/lib/embedded-lib.jar");
using var embeddedStream = embeddedEntry.Open();
embeddedBuffer.CopyTo(embeddedStream);
}
var analyzers = new ILanguageAnalyzer[] { new JavaLanguageAnalyzer() };
var json = await LanguageAnalyzerTestHarness.RunToJsonAsync(
root,
analyzers,
cancellationToken,
new LanguageUsageHints(new[] { jarPath }));
using var document = JsonDocument.Parse(json);
var components = document.RootElement.EnumerateArray().ToArray();
var outer = components.First(c => c.GetProperty("name").GetString() == "demo-fat");
var outerMetadata = outer.GetProperty("metadata");
Assert.Equal("1", outerMetadata.GetProperty("embeddedScan.candidateJars").GetString());
Assert.Equal("1", outerMetadata.GetProperty("embeddedScan.scannedJars").GetString());
Assert.Equal("1", outerMetadata.GetProperty("embeddedScan.emittedComponents").GetString());
var embedded = components.First(c => c.GetProperty("name").GetString() == "embedded-lib");
var embeddedMetadata = embedded.GetProperty("metadata");
Assert.Equal("true", embeddedMetadata.GetProperty("embedded").GetString());
Assert.Equal("demo-fat.jar!BOOT-INF/lib/embedded-lib.jar", embeddedMetadata.GetProperty("jarPath").GetString());
var embeddedEvidence = embedded.GetProperty("evidence").EnumerateArray().ToArray();
Assert.Contains(embeddedEvidence, e =>
string.Equals(e.GetProperty("source").GetString(), "pom.properties", StringComparison.OrdinalIgnoreCase) &&
string.Equals(e.GetProperty("locator").GetString(), "demo-fat.jar!BOOT-INF/lib/embedded-lib.jar!META-INF/maven/com.example/embedded-lib/pom.properties", StringComparison.OrdinalIgnoreCase) &&
e.TryGetProperty("sha256", out var sha) &&
!string.IsNullOrWhiteSpace(sha.GetString()));
}
finally
{
TestPaths.SafeDelete(root);
}
}
[Fact]
public async Task ExtractsMavenArtifactFromPomXmlWhenPomPropertiesMissingAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var root = TestPaths.CreateTemporaryDirectory();
try
{
var jarPath = Path.Combine(root, "demo-pomxml.jar");
Directory.CreateDirectory(Path.GetDirectoryName(jarPath)!);
using (var archive = ZipFile.Open(jarPath, ZipArchiveMode.Create))
{
WriteManifest(archive, "demo-pomxml", "1.2.3", "com.example");
var pomXmlPath = "META-INF/maven/com.example/demo-pomxml/pom.xml";
var pomXml = """
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>demo-pomxml</artifactId>
<version>1.2.3</version>
<name>Demo Pom XML</name>
</project>
""";
CreateTextEntry(archive, pomXmlPath, pomXml);
}
var analyzers = new ILanguageAnalyzer[] { new JavaLanguageAnalyzer() };
var json = await LanguageAnalyzerTestHarness.RunToJsonAsync(
root,
analyzers,
cancellationToken,
new LanguageUsageHints(new[] { jarPath }));
using var document = JsonDocument.Parse(json);
var component = document.RootElement
.EnumerateArray()
.First(element => string.Equals(element.GetProperty("name").GetString(), "demo-pomxml", StringComparison.Ordinal));
Assert.Equal("pkg:maven/com/example/demo-pomxml@1.2.3", component.GetProperty("purl").GetString());
var evidence = component.GetProperty("evidence").EnumerateArray().ToArray();
Assert.Contains(evidence, e =>
string.Equals(e.GetProperty("source").GetString(), "pom.xml", StringComparison.OrdinalIgnoreCase) &&
string.Equals(e.GetProperty("locator").GetString(), "demo-pomxml.jar!META-INF/maven/com.example/demo-pomxml/pom.xml", StringComparison.OrdinalIgnoreCase) &&
e.TryGetProperty("sha256", out var sha) &&
!string.IsNullOrWhiteSpace(sha.GetString()));
}
finally
{
TestPaths.SafeDelete(root);
}
}
[Fact]
public async Task PomXmlWithIncompleteCoordinatesEmitsUnresolvedComponentAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var root = TestPaths.CreateTemporaryDirectory();
try
{
var jarPath = Path.Combine(root, "demo-pomxml-unresolved.jar");
Directory.CreateDirectory(Path.GetDirectoryName(jarPath)!);
using (var archive = ZipFile.Open(jarPath, ZipArchiveMode.Create))
{
WriteManifest(archive, "demo-pomxml-unresolved", "9.9.9", "com.example");
var pomXmlPath = "META-INF/maven/com.example/demo-pomxml-unresolved/pom.xml";
var pomXml = """
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>demo-pomxml-unresolved</artifactId>
</project>
""";
CreateTextEntry(archive, pomXmlPath, pomXml);
}
var analyzers = new ILanguageAnalyzer[] { new JavaLanguageAnalyzer() };
var json = await LanguageAnalyzerTestHarness.RunToJsonAsync(
root,
analyzers,
cancellationToken,
new LanguageUsageHints(new[] { jarPath }));
using var document = JsonDocument.Parse(json);
var component = document.RootElement
.EnumerateArray()
.First(element =>
{
if (!element.TryGetProperty("metadata", out var metadata) || metadata.ValueKind != JsonValueKind.Object)
{
return false;
}
return metadata.TryGetProperty("unresolvedCoordinates", out var unresolved)
&& string.Equals(unresolved.GetString(), "true", StringComparison.Ordinal);
});
if (component.TryGetProperty("purl", out var purl))
{
Assert.Equal(JsonValueKind.Null, purl.ValueKind);
}
var metadata = component.GetProperty("metadata");
Assert.Equal("demo-pomxml-unresolved", metadata.GetProperty("manifestTitle").GetString());
Assert.Equal("9.9.9", metadata.GetProperty("manifestVersion").GetString());
var evidence = component.GetProperty("evidence").EnumerateArray().ToArray();
Assert.Contains(evidence, e =>
string.Equals(e.GetProperty("source").GetString(), "pom.xml", StringComparison.OrdinalIgnoreCase) &&
string.Equals(e.GetProperty("locator").GetString(), "demo-pomxml-unresolved.jar!META-INF/maven/com.example/demo-pomxml-unresolved/pom.xml", StringComparison.OrdinalIgnoreCase) &&
e.TryGetProperty("sha256", out var sha) &&
!string.IsNullOrWhiteSpace(sha.GetString()));
}
finally
{
@@ -436,14 +706,6 @@ public sealed class JavaLanguageAnalyzerTests
}
}
private static void CreateBinaryEntry(ZipArchive archive, string path, string content)
{
var entry = archive.CreateEntry(path);
using var stream = entry.Open();
var bytes = Encoding.UTF8.GetBytes(content);
stream.Write(bytes, 0, bytes.Length);
}
private static string CreateSampleJar(string root, string groupId, string artifactId, string version)
{
var jarPath = Path.Combine(root, $"{artifactId}-{version}.jar");

View File

@@ -14,12 +14,10 @@
<PackageReference Remove="xunit" />
<PackageReference Remove="xunit.runner.visualstudio" />
<PackageReference Remove="Microsoft.AspNetCore.Mvc.Testing" />
<PackageReference Remove="Mongo2Go" />
<PackageReference Remove="coverlet.collector" />
<PackageReference Remove="Microsoft.Extensions.TimeProvider.Testing" />
<ProjectReference Remove="..\StellaOps.Concelier.Testing\StellaOps.Concelier.Testing.csproj" />
<Compile Remove="$(MSBuildThisFileDirectory)..\StellaOps.Concelier.Tests.Shared\AssemblyInfo.cs" />
<Compile Remove="$(MSBuildThisFileDirectory)..\StellaOps.Concelier.Tests.Shared\MongoFixtureCollection.cs" />
<!-- Exclude shared OpenSSL files - they come from referenced Lang.Tests project -->
<Compile Remove="$(MSBuildThisFileDirectory)..\..\..\..\tests\shared\OpenSslLegacyShim.cs" />
<Compile Remove="$(MSBuildThisFileDirectory)..\..\..\..\tests\shared\OpenSslAutoInit.cs" />

View File

@@ -0,0 +1,80 @@
[
{
"analyzerId": "node-phase22",
"componentKey": "/app/native/addon.node",
"name": "addon.node",
"type": "node:native",
"usedByEntrypoint": false,
"metadata": {
"confidence": "0.82",
"reason": "native-addon-file"
},
"evidence": []
},
{
"analyzerId": "node-phase22",
"componentKey": "/app/pkg/pkg.wasm",
"name": "pkg.wasm",
"type": "node:wasm",
"usedByEntrypoint": false,
"metadata": {
"confidence": "0.80",
"reason": "wasm-file"
},
"evidence": []
},
{
"analyzerId": "node-phase22",
"componentKey": "/src/app.js",
"name": "app.js",
"type": "node:bundle",
"usedByEntrypoint": false,
"metadata": {
"confidence": "0.87",
"format": "esm",
"reason": "source-map"
},
"evidence": []
},
{
"analyzerId": "node",
"componentKey": "purl::pkg:npm/cached-lib@1.0.0",
"purl": "pkg:npm/cached-lib@1.0.0",
"name": "cached-lib",
"version": "1.0.0",
"type": "npm",
"usedByEntrypoint": false,
"metadata": {
"path": ".yarn/cache",
"yarnPnp": "true"
},
"evidence": [
{
"kind": "file",
"source": "package.json",
"locator": ".yarn/cache/cached-lib-1.0.0.zip!package/package.json",
"sha256": "b13d2a5d313d5929280c14af2086e23ca8f0d60761085c0ad44982ec307c92e3"
}
]
},
{
"analyzerId": "node",
"componentKey": "purl::pkg:npm/yarn-pnp-demo@1.0.0",
"purl": "pkg:npm/yarn-pnp-demo@1.0.0",
"name": "yarn-pnp-demo",
"version": "1.0.0",
"type": "npm",
"usedByEntrypoint": false,
"metadata": {
"path": ".",
"yarnPnp": "true"
},
"evidence": [
{
"kind": "file",
"source": "package.json",
"locator": "package.json"
}
]
}
]

View File

@@ -23,7 +23,8 @@
{
"kind": "file",
"source": "package.json",
"locator": "package.json"
"locator": "package.json",
"sha256": "4cd71adf540fff675b46ecd4d88d0b186534e97f9ca57ee86588d1386deb9274"
}
]
},
@@ -48,4 +49,4 @@
}
]
}
]
]

View File

@@ -1,4 +1,51 @@
[
{
"analyzerId": "node",
"componentKey": "purl::pkg:npm/hidden-lib@0.2.0",
"purl": "pkg:npm/hidden-lib@0.2.0",
"name": "hidden-lib",
"version": "0.2.0",
"type": "npm",
"usedByEntrypoint": false,
"metadata": {
"entrypoint": ".layers/layer0/node_modules/hidden-lib/index.js",
"path": ".layers/layer0/node_modules/hidden-lib"
},
"evidence": [
{
"kind": "file",
"source": "package.json",
"locator": ".layers/layer0/node_modules/hidden-lib/package.json",
"sha256": "d014f340282aa989e5887ddf10a1d2165ba556f89428d0f2812eb8ce8e63c1c8"
},
{
"kind": "metadata",
"source": "package.json:entrypoint",
"locator": ".layers/layer0/node_modules/hidden-lib/package.json#entrypoint",
"value": ".layers/layer0/node_modules/hidden-lib/index.js;index.js"
}
]
},
{
"analyzerId": "node",
"componentKey": "purl::pkg:npm/layer-app@0.1.0",
"purl": "pkg:npm/layer-app@0.1.0",
"name": "layer-app",
"version": "0.1.0",
"type": "npm",
"usedByEntrypoint": false,
"metadata": {
"path": "layers/layer1/app"
},
"evidence": [
{
"kind": "file",
"source": "package.json",
"locator": "layers/layer1/app/package.json",
"sha256": "23abb943f062b3ccdc18966eb36dfc48dd7ec4b5a6105851484fe2911946ecdd"
}
]
},
{
"analyzerId": "node",
"componentKey": "purl::pkg:npm/layer-lib@0.1.0",
@@ -15,7 +62,8 @@
{
"kind": "file",
"source": "package.json",
"locator": "layers/layer1/node_modules/layer-lib/package.json"
"locator": "layers/layer1/node_modules/layer-lib/package.json",
"sha256": "4d4ee909c5fa810d7e9a1bb74f4e6e2da59c3bb4182f62f8bb8f6074218f19d8"
},
{
"kind": "metadata",
@@ -24,5 +72,32 @@
"value": "layers/layer1/node_modules/layer-lib/index.js;index.js"
}
]
},
{
"analyzerId": "node",
"componentKey": "purl::pkg:npm/top-layer-lib@0.3.0",
"purl": "pkg:npm/top-layer-lib@0.3.0",
"name": "top-layer-lib",
"version": "0.3.0",
"type": "npm",
"usedByEntrypoint": false,
"metadata": {
"entrypoint": "layer2/node_modules/top-layer-lib/index.js",
"path": "layer2/node_modules/top-layer-lib"
},
"evidence": [
{
"kind": "file",
"source": "package.json",
"locator": "layer2/node_modules/top-layer-lib/package.json",
"sha256": "9de01a780c07e3d34ef74dfdbd14c5173f419609e77f9cc1fb973c30400e30e9"
},
{
"kind": "metadata",
"source": "package.json:entrypoint",
"locator": "layer2/node_modules/top-layer-lib/package.json#entrypoint",
"value": "layer2/node_modules/top-layer-lib/index.js;index.js"
}
]
}
]

View File

@@ -0,0 +1,7 @@
{
"name": "layer-app",
"version": "0.1.0",
"dependencies": {
"layer-lib": "1.0.0"
}
}

View File

@@ -0,0 +1,200 @@
[
{
"analyzerId": "node",
"componentKey": "explicit::node::npm::dev-range::sha256:681581098c1c40eb1faebe65e1916e56011369708306debd2568bfaa8173a6fc",
"name": "dev-range",
"type": "npm",
"usedByEntrypoint": false,
"metadata": {
"declared.locator": "package.json#devDependencies",
"declared.scope": "development",
"declared.source": "package.json",
"declared.sourceType": "range",
"declared.versionSpec": "~2.0.0",
"declaredOnly": "true"
},
"evidence": [
{
"kind": "metadata",
"source": "node.declared",
"locator": "package.json#devDependencies"
}
]
},
{
"analyzerId": "node",
"componentKey": "explicit::node::npm::file-lib::sha256:2222fa2f2ec7523cabd6c80a0ffd89c3b306fad0ff6b89856f8b896e0b2fe70e",
"name": "file-lib",
"type": "npm",
"usedByEntrypoint": false,
"metadata": {
"declared.locator": "package.json#dependencies",
"declared.scope": "production",
"declared.source": "package.json",
"declared.sourceType": "file",
"declared.versionSpec": "file:../local/file-lib",
"declaredOnly": "true"
},
"evidence": [
{
"kind": "metadata",
"source": "node.declared",
"locator": "package.json#dependencies"
}
]
},
{
"analyzerId": "node",
"componentKey": "explicit::node::npm::git-lib::sha256:02362d8e5c76a43f5f657721c46db1370be1c5504c9b30be8f667d5d85f369c6",
"name": "git-lib",
"type": "npm",
"usedByEntrypoint": false,
"metadata": {
"declared.locator": "package.json#dependencies",
"declared.scope": "production",
"declared.source": "package.json",
"declared.sourceType": "git",
"declared.versionSpec": "git\u002Bhttps://example.com/repo.git#v1.0.0",
"declaredOnly": "true"
},
"evidence": [
{
"kind": "metadata",
"source": "node.declared",
"locator": "package.json#dependencies"
}
]
},
{
"analyzerId": "node",
"componentKey": "explicit::node::npm::opt-lib::sha256:79ae0f26f0d9e4a55710b56ae3f216251cedb886ce9dd45891fd70a17d2c273b",
"name": "opt-lib",
"type": "npm",
"usedByEntrypoint": false,
"metadata": {
"declared.locator": "package.json#optionalDependencies",
"declared.scope": "optional",
"declared.source": "package.json",
"declared.sourceType": "workspace",
"declared.versionSpec": "workspace:*",
"declaredOnly": "true"
},
"evidence": [
{
"kind": "metadata",
"source": "node.declared",
"locator": "package.json#optionalDependencies"
}
]
},
{
"analyzerId": "node",
"componentKey": "explicit::node::npm::path-lib::sha256:5dfa2181ae287045798e7f7d1b3d8f452f071f19be8366d0a218da64fe815589",
"name": "path-lib",
"type": "npm",
"usedByEntrypoint": false,
"metadata": {
"declared.locator": "package.json#dependencies",
"declared.scope": "production",
"declared.source": "package.json",
"declared.sourceType": "path",
"declared.versionSpec": "./local/path-lib",
"declaredOnly": "true"
},
"evidence": [
{
"kind": "metadata",
"source": "node.declared",
"locator": "package.json#dependencies"
}
]
},
{
"analyzerId": "node",
"componentKey": "explicit::node::npm::peer-lib::sha256:518814536b4014ed1645c41ccf2de9349336f33be755dfe30d60b14f85f5b302",
"name": "peer-lib",
"type": "npm",
"usedByEntrypoint": false,
"metadata": {
"declared.locator": "package.json#peerDependencies",
"declared.scope": "peer",
"declared.source": "package.json",
"declared.sourceType": "range",
"declared.versionSpec": "\u003E=3.0.0",
"declaredOnly": "true"
},
"evidence": [
{
"kind": "metadata",
"source": "node.declared",
"locator": "package.json#peerDependencies"
}
]
},
{
"analyzerId": "node",
"componentKey": "explicit::node::npm::range-lib::sha256:d90425e51e919a0e90bd2cae825d86919c79799ed49ce2ecdeb956de86344145",
"name": "range-lib",
"type": "npm",
"usedByEntrypoint": false,
"metadata": {
"declared.locator": "package.json#dependencies",
"declared.scope": "production",
"declared.source": "package.json",
"declared.sourceType": "range",
"declared.versionSpec": "^1.2.3",
"declaredOnly": "true"
},
"evidence": [
{
"kind": "metadata",
"source": "node.declared",
"locator": "package.json#dependencies"
}
]
},
{
"analyzerId": "node",
"componentKey": "explicit::node::npm::tag-lib::sha256:8337331b8a282de0437b980185f47c0d4f592b79d1b114a67b5dad11b8815afd",
"name": "tag-lib",
"type": "npm",
"usedByEntrypoint": false,
"metadata": {
"declared.locator": "package.json#dependencies",
"declared.scope": "production",
"declared.source": "package.json",
"declared.sourceType": "tag",
"declared.versionSpec": "latest",
"declaredOnly": "true"
},
"evidence": [
{
"kind": "metadata",
"source": "node.declared",
"locator": "package.json#dependencies"
}
]
},
{
"analyzerId": "node",
"componentKey": "explicit::node::npm::tarball-lib::sha256:4fcba99e507cd649bf51963a3ed4f16fbf6431a4170e08580495999ad1ea1fd6",
"name": "tarball-lib",
"type": "npm",
"usedByEntrypoint": false,
"metadata": {
"declared.locator": "package.json#dependencies",
"declared.scope": "production",
"declared.source": "package.json",
"declared.sourceType": "tarball",
"declared.versionSpec": "https://example.com/pkg.tgz",
"declaredOnly": "true"
},
"evidence": [
{
"kind": "metadata",
"source": "node.declared",
"locator": "package.json#dependencies"
}
]
}
]

View File

@@ -0,0 +1,20 @@
{
"private": true,
"dependencies": {
"range-lib": "^1.2.3",
"tag-lib": "latest",
"git-lib": "git+https://example.com/repo.git#v1.0.0",
"tarball-lib": "https://example.com/pkg.tgz",
"file-lib": "file:../local/file-lib",
"path-lib": "./local/path-lib"
},
"devDependencies": {
"dev-range": "~2.0.0"
},
"peerDependencies": {
"peer-lib": ">=3.0.0"
},
"optionalDependencies": {
"opt-lib": "workspace:*"
}
}

View File

@@ -16,7 +16,8 @@
{
"kind": "file",
"source": "package.json",
"locator": "package.json"
"locator": "package.json",
"sha256": "e20b4c9ec9073b572c368b5ea40465eb59586487fa9469ae784cc23f618f3457"
},
{
"kind": "metadata",

View File

@@ -30,13 +30,63 @@
"usedByEntrypoint": false,
"metadata": {
"entrypoint": "src/index.js",
"imports": "4",
"path": "."
},
"evidence": [
{
"kind": "file",
"source": "node.import",
"locator": "src/index.js",
"value": "./lib//entry.js${*} (conf:medium;template-dynamic)"
},
{
"kind": "file",
"source": "node.import",
"locator": "src/index.js",
"value": "./lib/concat.js (conf:high;literal;literal)"
},
{
"kind": "file",
"source": "node.import",
"locator": "src/index.js",
"value": "./lib/static.js (conf:high;literal)"
},
{
"kind": "file",
"source": "node.import",
"locator": "src/original.ts",
"value": "./lib/sourcemap.js (conf:high;literal)"
},
{
"kind": "file",
"source": "package.json",
"locator": "package.json"
"locator": "package.json",
"sha256": "07f1225926d9d07b0a024994036d10a689f3d98cc51324e2b21a06a7bddb8d0e"
},
{
"kind": "metadata",
"source": "node.resolve",
"locator": "package.json",
"value": "src/index.js:./lib//entry.js${*}-\u003Eunresolved (unresolved;low)"
},
{
"kind": "metadata",
"source": "node.resolve",
"locator": "package.json",
"value": "src/index.js:./lib/concat.js-\u003Eunresolved (unresolved;low)"
},
{
"kind": "metadata",
"source": "node.resolve",
"locator": "package.json",
"value": "src/index.js:./lib/static.js-\u003Eunresolved (unresolved;low)"
},
{
"kind": "metadata",
"source": "node.resolve",
"locator": "package.json",
"value": "src/original.ts:./lib/sourcemap.js-\u003Eunresolved (unresolved;low)"
},
{
"kind": "metadata",

View File

@@ -0,0 +1,98 @@
[
{
"analyzerId": "node",
"componentKey": "purl::pkg:npm/%40scope/scoped-child@4.0.0",
"purl": "pkg:npm/%40scope/scoped-child@4.0.0",
"name": "@scope/scoped-child",
"version": "4.0.0",
"type": "npm",
"usedByEntrypoint": false,
"metadata": {
"declared.locator": "package-lock.json:node_modules/parent/node_modules/@scope/scoped-child",
"declared.resolvedVersion": "4.0.0",
"declared.source": "package-lock.json",
"declared.sourceType": "range",
"declared.versionSpec": "4.0.0",
"declaredOnly": "true"
},
"evidence": [
{
"kind": "metadata",
"source": "node.declared",
"locator": "package-lock.json:node_modules/parent/node_modules/@scope/scoped-child"
}
]
},
{
"analyzerId": "node",
"componentKey": "purl::pkg:npm/%40scope/scoped@3.0.0",
"purl": "pkg:npm/%40scope/scoped@3.0.0",
"name": "@scope/scoped",
"version": "3.0.0",
"type": "npm",
"usedByEntrypoint": false,
"metadata": {
"declared.locator": "package-lock.json:node_modules/@scope/scoped",
"declared.resolvedVersion": "3.0.0",
"declared.source": "package-lock.json",
"declared.sourceType": "range",
"declared.versionSpec": "3.0.0",
"declaredOnly": "true"
},
"evidence": [
{
"kind": "metadata",
"source": "node.declared",
"locator": "package-lock.json:node_modules/@scope/scoped"
}
]
},
{
"analyzerId": "node",
"componentKey": "purl::pkg:npm/child@2.0.0",
"purl": "pkg:npm/child@2.0.0",
"name": "child",
"version": "2.0.0",
"type": "npm",
"usedByEntrypoint": false,
"metadata": {
"declared.locator": "package-lock.json:node_modules/parent/node_modules/child",
"declared.resolvedVersion": "2.0.0",
"declared.source": "package-lock.json",
"declared.sourceType": "range",
"declared.versionSpec": "2.0.0",
"declaredOnly": "true"
},
"evidence": [
{
"kind": "metadata",
"source": "node.declared",
"locator": "package-lock.json:node_modules/parent/node_modules/child"
}
]
},
{
"analyzerId": "node",
"componentKey": "purl::pkg:npm/parent@1.0.0",
"purl": "pkg:npm/parent@1.0.0",
"name": "parent",
"version": "1.0.0",
"type": "npm",
"usedByEntrypoint": false,
"metadata": {
"declared.locator": "package-lock.json:node_modules/parent",
"declared.resolvedVersion": "1.0.0",
"declared.source": "package-lock.json",
"declared.sourceType": "range",
"declared.versionSpec": "1.0.0",
"declaredOnly": "true"
},
"evidence": [
{
"kind": "metadata",
"source": "node.declared",
"locator": "package-lock.json:node_modules/parent"
}
]
}
]

View File

@@ -0,0 +1,18 @@
{
"name": "lock-only-package-lock",
"lockfileVersion": 3,
"packages": {
"node_modules/parent": {
"version": "1.0.0"
},
"node_modules/parent/node_modules/child": {
"version": "2.0.0"
},
"node_modules/@scope/scoped": {
"version": "3.0.0"
},
"node_modules/parent/node_modules/@scope/scoped-child": {
"version": "4.0.0"
}
}
}

View File

@@ -0,0 +1,75 @@
[
{
"analyzerId": "node",
"componentKey": "explicit::node::npm::local-link::sha256:ea69a4c271be378e84e910ddee0a83c3b6da4b4526146ce646e3ce411ae0aa07",
"name": "local-link",
"type": "npm",
"usedByEntrypoint": false,
"metadata": {
"declared.locator": "pnpm-lock.yaml:local-link/0.0.0",
"declared.source": "pnpm-lock.yaml",
"declared.sourceType": "link",
"declared.versionSpec": "link:../packages/local-link",
"declaredOnly": "true",
"lockIntegrityMissing": "true",
"lockIntegrityMissingReason": "link"
},
"evidence": [
{
"kind": "metadata",
"source": "node.declared",
"locator": "pnpm-lock.yaml:local-link/0.0.0"
}
]
},
{
"analyzerId": "node",
"componentKey": "purl::pkg:npm/%40scope/scoped@2.0.0",
"purl": "pkg:npm/%40scope/scoped@2.0.0",
"name": "@scope/scoped",
"version": "2.0.0",
"type": "npm",
"usedByEntrypoint": false,
"metadata": {
"declared.locator": "pnpm-lock.yaml:@scope/scoped/2.0.0",
"declared.resolvedVersion": "2.0.0",
"declared.source": "pnpm-lock.yaml",
"declared.sourceType": "range",
"declared.versionSpec": "2.0.0",
"declaredOnly": "true",
"lockIntegrityMissing": "true",
"lockIntegrityMissingReason": "missing"
},
"evidence": [
{
"kind": "metadata",
"source": "node.declared",
"locator": "pnpm-lock.yaml:@scope/scoped/2.0.0"
}
]
},
{
"analyzerId": "node",
"componentKey": "purl::pkg:npm/left-pad@1.3.0",
"purl": "pkg:npm/left-pad@1.3.0",
"name": "left-pad",
"version": "1.3.0",
"type": "npm",
"usedByEntrypoint": false,
"metadata": {
"declared.locator": "pnpm-lock.yaml:left-pad/1.3.0",
"declared.resolvedVersion": "1.3.0",
"declared.source": "pnpm-lock.yaml",
"declared.sourceType": "range",
"declared.versionSpec": "1.3.0",
"declaredOnly": "true"
},
"evidence": [
{
"kind": "metadata",
"source": "node.declared",
"locator": "pnpm-lock.yaml:left-pad/1.3.0"
}
]
}
]

View File

@@ -0,0 +1,10 @@
lockfileVersion: 6.0
packages:
/left-pad/1.3.0:
resolution: {integrity: sha512-leftpad, tarball: https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz}
/local-link/0.0.0:
version: link:../packages/local-link
resolution: {tarball: link:../packages/local-link}
/@scope/scoped/2.0.0:
resolution: {tarball: https://registry.npmjs.org/@scope/scoped/-/scoped-2.0.0.tgz}

View File

@@ -0,0 +1,98 @@
[
{
"analyzerId": "node",
"componentKey": "purl::pkg:npm/%40scope/scoped@3.0.1",
"purl": "pkg:npm/%40scope/scoped@3.0.1",
"name": "@scope/scoped",
"version": "3.0.1",
"type": "npm",
"usedByEntrypoint": false,
"metadata": {
"declared.locator": "yarn.lock:@scope/scoped@npm:^3.0.0",
"declared.resolvedVersion": "3.0.1",
"declared.source": "yarn.lock",
"declared.sourceType": "range",
"declared.versionSpec": "3.0.1",
"declaredOnly": "true"
},
"evidence": [
{
"kind": "metadata",
"source": "node.declared",
"locator": "yarn.lock:@scope/scoped@npm:^3.0.0"
}
]
},
{
"analyzerId": "node",
"componentKey": "purl::pkg:npm/legacy@1.0.0",
"purl": "pkg:npm/legacy@1.0.0",
"name": "legacy",
"version": "1.0.0",
"type": "npm",
"usedByEntrypoint": false,
"metadata": {
"declared.locator": "yarn.lock:legacy@^1.0.0",
"declared.resolvedVersion": "1.0.0",
"declared.source": "yarn.lock",
"declared.sourceType": "range",
"declared.versionSpec": "1.0.0",
"declaredOnly": "true"
},
"evidence": [
{
"kind": "metadata",
"source": "node.declared",
"locator": "yarn.lock:legacy@^1.0.0"
}
]
},
{
"analyzerId": "node",
"componentKey": "purl::pkg:npm/multi@1.0.0",
"purl": "pkg:npm/multi@1.0.0",
"name": "multi",
"version": "1.0.0",
"type": "npm",
"usedByEntrypoint": false,
"metadata": {
"declared.locator": "yarn.lock:multi@npm:^1.0.0",
"declared.resolvedVersion": "1.0.0",
"declared.source": "yarn.lock",
"declared.sourceType": "range",
"declared.versionSpec": "1.0.0",
"declaredOnly": "true"
},
"evidence": [
{
"kind": "metadata",
"source": "node.declared",
"locator": "yarn.lock:multi@npm:^1.0.0"
}
]
},
{
"analyzerId": "node",
"componentKey": "purl::pkg:npm/multi@2.0.0",
"purl": "pkg:npm/multi@2.0.0",
"name": "multi",
"version": "2.0.0",
"type": "npm",
"usedByEntrypoint": false,
"metadata": {
"declared.locator": "yarn.lock:multi@npm:^2.0.0",
"declared.resolvedVersion": "2.0.0",
"declared.source": "yarn.lock",
"declared.sourceType": "range",
"declared.versionSpec": "2.0.0",
"declaredOnly": "true"
},
"evidence": [
{
"kind": "metadata",
"source": "node.declared",
"locator": "yarn.lock:multi@npm:^2.0.0"
}
]
}
]

View File

@@ -0,0 +1,23 @@
__metadata:
version: 8
cacheKey: 10
"multi@npm:^1.0.0":
version: "1.0.0"
resolution: "multi@npm:1.0.0"
checksum: "abcd1234"
"multi@npm:^2.0.0":
version: "2.0.0"
resolution: "multi@npm:2.0.0"
integrity: "sha512-xyz987"
"@scope/scoped@npm:^3.0.0":
version: "3.0.1"
resolution: "@scope/scoped@npm:3.0.1"
checksum: "deadbeef"
legacy@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/legacy/-/legacy-1.0.0.tgz#abc123"
integrity sha512-legacy

View File

@@ -17,7 +17,8 @@
{
"kind": "file",
"source": "package.json",
"locator": "node_modules/.pnpm/pkg@1.2.3/node_modules/pkg/package.json"
"locator": "node_modules/.pnpm/pkg@1.2.3/node_modules/pkg/package.json",
"sha256": "23fc3dc23387b21780c9b0b3f8cf3e07e1619a0603325a2744e2a6d2873fceac"
},
{
"kind": "metadata",
@@ -42,7 +43,8 @@
{
"kind": "file",
"source": "package.json",
"locator": "package.json"
"locator": "package.json",
"sha256": "1ee30a64ac1806fececa1a00c36555930029c81c92de4b376c2610499c1cb435"
}
]
}

View File

@@ -34,7 +34,8 @@
{
"kind": "file",
"source": "package.json",
"locator": "package.json"
"locator": "package.json",
"sha256": "e7bea1ac14004d809d1b649c1329c9e09ce69d458c3794ee98b4f37c6fa591b5"
},
{
"kind": "metadata",

View File

@@ -16,7 +16,8 @@
{
"kind": "file",
"source": "package.json",
"locator": "package.json"
"locator": "package.json",
"sha256": "452a5c537c19282754f6c32eebf8aea46e9604c76b8d3b16527cdab932701ff7"
},
{
"kind": "metadata",

View File

@@ -31,8 +31,9 @@
{
"kind": "file",
"source": "package.json",
"locator": "package.json"
"locator": "package.json",
"sha256": "3c16561eea74166e8add2e59d2bf93d55c035a7cdac640410332ee2b9e7a1a35"
}
]
}
]
]

View File

@@ -1,4 +1,53 @@
[
{
"analyzerId": "node",
"componentKey": "purl::pkg:npm/declared-only@9.9.9",
"purl": "pkg:npm/declared-only@9.9.9",
"name": "declared-only",
"version": "9.9.9",
"type": "npm",
"usedByEntrypoint": false,
"metadata": {
"declared.locator": "package-lock.json:packages/app/node_modules/declared-only",
"declared.resolvedVersion": "9.9.9",
"declared.source": "package-lock.json",
"declared.sourceType": "range",
"declared.versionSpec": "9.9.9",
"declaredOnly": "true"
},
"evidence": [
{
"kind": "metadata",
"source": "node.declared",
"locator": "package-lock.json:packages/app/node_modules/declared-only"
}
]
},
{
"analyzerId": "node",
"componentKey": "purl::pkg:npm/left-pad@1.3.0",
"purl": "pkg:npm/left-pad@1.3.0",
"name": "left-pad",
"version": "1.3.0",
"type": "npm",
"usedByEntrypoint": false,
"metadata": {
"declared.locator": "package-lock.json:packages/app/node_modules/left-pad",
"declared.resolvedVersion": "1.3.0",
"declared.scope": "production",
"declared.source": "package-lock.json",
"declared.sourceType": "range",
"declared.versionSpec": "1.3.0",
"declaredOnly": "true"
},
"evidence": [
{
"kind": "metadata",
"source": "node.declared",
"locator": "package-lock.json:packages/app/node_modules/left-pad"
}
]
},
{
"analyzerId": "node",
"componentKey": "purl::pkg:npm/lib@2.0.1",
@@ -13,6 +62,8 @@
"lockSource": "package-lock.json",
"path": "packages/lib",
"resolved": "https://registry.example/lib-2.0.1.tgz",
"riskLevel": "production",
"scope": "production",
"workspaceMember": "true",
"workspaceRoot": "packages/lib"
},
@@ -20,7 +71,30 @@
{
"kind": "file",
"source": "package.json",
"locator": "packages/lib/package.json"
"locator": "packages/lib/package.json",
"sha256": "5198fbaf659fb4f8d9845a7ffa51067ac7e727631f2503615c962540fe8c2298"
}
]
},
{
"analyzerId": "node",
"componentKey": "purl::pkg:npm/nested-tool@0.0.5",
"purl": "pkg:npm/nested-tool@0.0.5",
"name": "nested-tool",
"version": "0.0.5",
"type": "npm",
"usedByEntrypoint": false,
"metadata": {
"path": "packages/nested/tool",
"workspaceMember": "true",
"workspaceRoot": "packages/nested/tool"
},
"evidence": [
{
"kind": "file",
"source": "package.json",
"locator": "packages/nested/tool/package.json",
"sha256": "3011f57f07fab11b4ecb61788319bc9768d2577cafd9f53f37a7cac721fc77cf"
}
]
},
@@ -42,7 +116,8 @@
{
"kind": "file",
"source": "package.json",
"locator": "package.json"
"locator": "package.json",
"sha256": "aa060a0c2a8a6c41f68783d0f7366491e5560bb9af3ea043d6d2dc664de20a7f"
}
]
},
@@ -60,6 +135,8 @@
"lockSource": "package-lock.json",
"path": "packages/shared",
"resolved": "https://registry.example/shared-3.1.4.tgz",
"riskLevel": "production",
"scope": "production",
"workspaceMember": "true",
"workspaceRoot": "packages/shared",
"workspaceTargets": "packages/lib"
@@ -68,7 +145,8 @@
{
"kind": "file",
"source": "package.json",
"locator": "packages/shared/package.json"
"locator": "packages/shared/package.json",
"sha256": "4440d351c91132499bedb21859b2a1813b25563b93a54eb1ad1ded79d18839d1"
}
]
},
@@ -95,7 +173,8 @@
{
"kind": "file",
"source": "package.json",
"locator": "packages/app/package.json"
"locator": "packages/app/package.json",
"sha256": "e734fc2024e5582200309bd190832f2f931a8b8af65dd11c49f0583c92195582"
},
{
"kind": "metadata",
@@ -106,4 +185,4 @@
}
]
}
]
]

View File

@@ -1,10 +1,9 @@
{
"name": "root-workspace",
"version": "1.0.0",
"private": true,
"workspaces": [
"packages/app",
"packages/lib",
"packages/shared"
]
}
"version": "1.0.0",
"private": true,
"workspaces": [
"packages/*",
"packages/**"
]
}

View File

@@ -0,0 +1,7 @@
{
"name": "nested-tool",
"version": "0.0.5",
"devDependencies": {
"left-pad": "1.3.0"
}
}

View File

@@ -1,4 +1,41 @@
[
{
"analyzerId": "node-phase22",
"componentKey": "/app/native/addon.node",
"name": "addon.node",
"type": "node:native",
"usedByEntrypoint": false,
"metadata": {
"confidence": "0.82",
"reason": "native-addon-file"
},
"evidence": []
},
{
"analyzerId": "node-phase22",
"componentKey": "/app/pkg/pkg.wasm",
"name": "pkg.wasm",
"type": "node:wasm",
"usedByEntrypoint": false,
"metadata": {
"confidence": "0.80",
"reason": "wasm-file"
},
"evidence": []
},
{
"analyzerId": "node-phase22",
"componentKey": "/src/app.js",
"name": "app.js",
"type": "node:bundle",
"usedByEntrypoint": false,
"metadata": {
"confidence": "0.87",
"format": "esm",
"reason": "source-map"
},
"evidence": []
},
{
"analyzerId": "node",
"componentKey": "purl::pkg:npm/cached-lib@1.0.0",
@@ -8,9 +45,7 @@
"type": "npm",
"usedByEntrypoint": false,
"metadata": {
"lockLocator": "cached-lib@npm:1.0.0",
"lockSource": "pnp.data",
"path": ".yarn/cache/cached-lib-1.0.0.zip/node_modules/cached-lib",
"path": ".yarn/cache",
"yarnPnp": "true"
},
"evidence": [
@@ -31,8 +66,6 @@
"type": "npm",
"usedByEntrypoint": false,
"metadata": {
"lockLocator": "yarn-pnp-demo@workspace:.",
"lockSource": "pnp.data",
"path": ".",
"yarnPnp": "true"
},
@@ -40,9 +73,8 @@
{
"kind": "file",
"source": "package.json",
"locator": "package.json",
"sha256": "65e86ba14f0beebc4573039ac34a58f6dfa0133aa4a9e7f2dcdbb36a4e5c2814"
"locator": "package.json"
}
]
}
]
]

View File

@@ -125,6 +125,107 @@ public sealed class NodeDeterminismTests : IDisposable
#endregion
[Fact]
public async Task LockOnlyProject_EmitsDeclaredOnlyComponents_WithoutRangeAsPurl()
{
WriteFile("package.json", JsonSerializer.Serialize(new
{
name = "root",
version = "1.0.0",
dependencies = new Dictionary<string, string>
{
["express"] = "^4.18.2",
["left-pad"] = "^1.3.0"
}
}));
WriteFile("package-lock.json", JsonSerializer.Serialize(new
{
name = "root",
version = "1.0.0",
lockfileVersion = 3,
packages = new Dictionary<string, object>
{
[""] = new
{
name = "root",
version = "1.0.0"
},
["node_modules/express"] = new
{
version = "4.18.2",
resolved = "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
integrity = "sha512-deadbeef"
}
}
}));
var json = await RunAnalyzerAsync();
using var document = JsonDocument.Parse(json);
var components = document.RootElement.EnumerateArray().ToArray();
var express = components.Single(static element =>
element.TryGetProperty("purl", out var purl)
&& purl.ValueKind == JsonValueKind.String
&& purl.GetString() == "pkg:npm/express@4.18.2");
var expressMeta = express.GetProperty("metadata");
Assert.Equal("true", expressMeta.GetProperty("declaredOnly").GetString());
Assert.Equal("package-lock.json", expressMeta.GetProperty("declared.source").GetString());
Assert.Equal("package-lock.json:node_modules/express", expressMeta.GetProperty("declared.locator").GetString());
Assert.Equal("^4.18.2", expressMeta.GetProperty("declared.versionSpec").GetString());
Assert.Equal("4.18.2", expressMeta.GetProperty("declared.resolvedVersion").GetString());
var leftPad = components.Single(static element =>
element.GetProperty("name").GetString() == "left-pad");
Assert.False(leftPad.TryGetProperty("purl", out _));
Assert.StartsWith("explicit::node::npm::left-pad::sha256:", leftPad.GetProperty("componentKey").GetString(), StringComparison.Ordinal);
var leftPadMeta = leftPad.GetProperty("metadata");
Assert.Equal("true", leftPadMeta.GetProperty("declaredOnly").GetString());
Assert.Equal("package.json", leftPadMeta.GetProperty("declared.source").GetString());
Assert.Equal("^1.3.0", leftPadMeta.GetProperty("declared.versionSpec").GetString());
}
[Fact]
public async Task PnpmLock_IntegrityMissing_EmitsDeclaredOnlyMetadata()
{
WriteFile("package.json", JsonSerializer.Serialize(new
{
name = "root",
version = "1.0.0",
dependencies = new Dictionary<string, string>
{
["local-file"] = "file:../local-file-1.0.0.tgz"
}
}));
var pnpmLock = "lockfileVersion: '6.0'\n" +
"packages:\n" +
" /local-file/1.0.0:\n" +
" resolution: {tarball: file:../local-file-1.0.0.tgz}\n";
WriteFile("pnpm-lock.yaml", pnpmLock);
var json = await RunAnalyzerAsync();
using var document = JsonDocument.Parse(json);
var components = document.RootElement.EnumerateArray().ToArray();
var localFile = components.Single(static element =>
element.TryGetProperty("purl", out var purl)
&& purl.ValueKind == JsonValueKind.String
&& purl.GetString() == "pkg:npm/local-file@1.0.0");
var meta = localFile.GetProperty("metadata");
Assert.Equal("true", meta.GetProperty("declaredOnly").GetString());
Assert.Equal("pnpm-lock.yaml", meta.GetProperty("declared.source").GetString());
Assert.Equal("pnpm-lock.yaml:local-file/1.0.0", meta.GetProperty("declared.locator").GetString());
Assert.Equal("file:../local-file-1.0.0.tgz", meta.GetProperty("declared.versionSpec").GetString());
Assert.Equal("1.0.0", meta.GetProperty("declared.resolvedVersion").GetString());
Assert.Equal("true", meta.GetProperty("lockIntegrityMissing").GetString());
Assert.Equal("file", meta.GetProperty("lockIntegrityMissingReason").GetString());
}
#region Entrypoint Ordering
[Fact]

View File

@@ -0,0 +1,39 @@
using StellaOps.Scanner.Analyzers.Lang.Node.Internal;
namespace StellaOps.Scanner.Analyzers.Lang.Node.Tests.Node;
public sealed class NodeImportWalkerTests
{
[Fact]
public void AnalyzeImports_ParsesEsmImportsAndExports()
{
var content = """
import foo from "foo";
export { bar } from "bar";
export * from "baz";
""";
var edges = NodeImportWalker.AnalyzeImports("/repo", "src/index.mjs", content);
Assert.Contains(edges, e => e.TargetSpecifier == "foo" && e.Kind == "import");
Assert.Contains(edges, e => e.TargetSpecifier == "bar" && e.Kind == "export-from");
Assert.Contains(edges, e => e.TargetSpecifier == "baz" && e.Kind == "export-all");
}
[Fact]
public void AnalyzeImports_WhenParserFails_UsesTypeScriptRegexFallback()
{
var content = """
import type { Foo } from "foo";
export type { Bar } from "bar";
interface Thing { name: string }
""";
var edges = NodeImportWalker.AnalyzeImports("/repo", "src/index.ts", content);
Assert.Contains(edges, e => e.TargetSpecifier == "foo" && e.Evidence == "ts-regex");
Assert.Contains(edges, e => e.TargetSpecifier == "bar" && e.Kind == "export-from" && e.Evidence == "ts-regex");
}
}

View File

@@ -190,6 +190,54 @@ public sealed class NodeLanguageAnalyzerTests
cancellationToken);
}
[Fact]
public async Task LockOnlyPackageLockEmitsDeclaredOnlyComponentsAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var fixturePath = TestPaths.ResolveFixture("lang", "node", "lock-only-package-lock");
var goldenPath = Path.Combine(fixturePath, "expected.json");
var analyzers = new ILanguageAnalyzer[] { new NodeLanguageAnalyzer() };
await LanguageAnalyzerTestHarness.AssertDeterministicAsync(
fixturePath,
goldenPath,
analyzers,
cancellationToken);
}
[Fact]
public async Task LockOnlyYarnBerryEmitsDeclaredOnlyComponentsAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var fixturePath = TestPaths.ResolveFixture("lang", "node", "lock-only-yarn-berry");
var goldenPath = Path.Combine(fixturePath, "expected.json");
var analyzers = new ILanguageAnalyzer[] { new NodeLanguageAnalyzer() };
await LanguageAnalyzerTestHarness.AssertDeterministicAsync(
fixturePath,
goldenPath,
analyzers,
cancellationToken);
}
[Fact]
public async Task LockOnlyPnpmEmitsDeclaredOnlyComponentsAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var fixturePath = TestPaths.ResolveFixture("lang", "node", "lock-only-pnpm");
var goldenPath = Path.Combine(fixturePath, "expected.json");
var analyzers = new ILanguageAnalyzer[] { new NodeLanguageAnalyzer() };
await LanguageAnalyzerTestHarness.AssertDeterministicAsync(
fixturePath,
goldenPath,
analyzers,
cancellationToken);
}
[Fact]
public async Task PnpmVirtualStoreIsParsedAsync()
{
@@ -237,4 +285,20 @@ public sealed class NodeLanguageAnalyzerTests
analyzers,
cancellationToken);
}
[Fact]
public async Task PackageJsonDeclaredOnlyDependenciesUseExplicitKeyAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var fixturePath = TestPaths.ResolveFixture("lang", "node", "declared-only-package-json");
var goldenPath = Path.Combine(fixturePath, "expected.json");
var analyzers = new ILanguageAnalyzer[] { new NodeLanguageAnalyzer() };
await LanguageAnalyzerTestHarness.AssertDeterministicAsync(
fixturePath,
goldenPath,
analyzers,
cancellationToken);
}
}

View File

@@ -38,6 +38,34 @@ public sealed class NodeLockDataTests : IDisposable
Assert.Empty(result.DeclaredPackages);
}
[Fact]
public async Task LoadAsync_EmptyRootPath_DoesNotThrow_WhenCurrentDirectoryHasPackageJson()
{
var originalDirectory = Environment.CurrentDirectory;
var tempDirectory = Path.Combine(Path.GetTempPath(), "node-lock-tests-cwd-" + Guid.NewGuid().ToString("N")[..8]);
Directory.CreateDirectory(tempDirectory);
try
{
await File.WriteAllTextAsync(Path.Combine(tempDirectory, "package.json"), """
{
"name": "fixture",
"version": "0.0.0"
}
""");
Environment.CurrentDirectory = tempDirectory;
var result = await NodeLockData.LoadAsync(string.Empty, CancellationToken.None);
Assert.Empty(result.DeclaredPackages);
}
finally
{
Environment.CurrentDirectory = originalDirectory;
Directory.Delete(tempDirectory, recursive: true);
}
}
[Fact]
public async Task LoadAsync_OnlyPackageJson_CreatesDeclaredOnlyEntries()
{
@@ -232,7 +260,6 @@ public sealed class NodeLockDataTests : IDisposable
[Fact]
public async Task LoadPackageLockJson_V3Format_NestedNodeModules()
{
// Note: Nested node_modules require explicit name property for correct extraction
await File.WriteAllTextAsync(Path.Combine(_tempDir, "package-lock.json"), """
{
"lockfileVersion": 3,
@@ -241,7 +268,6 @@ public sealed class NodeLockDataTests : IDisposable
"version": "1.0.0"
},
"node_modules/parent/node_modules/child": {
"name": "child",
"version": "2.0.0"
}
}
@@ -253,6 +279,38 @@ public sealed class NodeLockDataTests : IDisposable
Assert.Equal(2, result.DeclaredPackages.Count);
Assert.Contains(result.DeclaredPackages, e => e.Name == "parent");
Assert.Contains(result.DeclaredPackages, e => e.Name == "child");
Assert.True(result.TryGet("node_modules/parent/node_modules/child", "child", "2.0.0", out var entry));
Assert.NotNull(entry);
Assert.Equal("2.0.0", entry!.Version);
}
[Fact]
public async Task LoadPackageLockJson_V3Format_NestedNodeModules_ScopedChild()
{
await File.WriteAllTextAsync(Path.Combine(_tempDir, "package-lock.json"), """
{
"lockfileVersion": 3,
"packages": {
"node_modules/parent": {
"version": "1.0.0"
},
"node_modules/parent/node_modules/@types/node": {
"version": "20.10.0"
}
}
}
""");
var result = await NodeLockData.LoadAsync(_tempDir, CancellationToken.None);
Assert.Equal(2, result.DeclaredPackages.Count);
Assert.Contains(result.DeclaredPackages, e => e.Name == "parent");
Assert.Contains(result.DeclaredPackages, e => e.Name == "@types/node");
Assert.True(result.TryGet("node_modules/parent/node_modules/@types/node", "@types/node", "20.10.0", out var entry));
Assert.NotNull(entry);
Assert.Equal("20.10.0", entry!.Version);
}
[Fact]
@@ -495,6 +553,35 @@ valid@^2.0.0:
Assert.Contains(result.DeclaredPackages, e => e.Name == "valid");
}
[Fact]
public async Task LoadYarnLock_BerryFormat_ParsesResolutionChecksum_AndSkipsMetadata()
{
await File.WriteAllTextAsync(Path.Combine(_tempDir, "yarn.lock"), """
__metadata:
version: 6
"lodash@npm:^4.17.21":
version: 4.17.21
resolution: "lodash@npm:4.17.21"
checksum: 10c0deadbeef
""");
var result = await NodeLockData.LoadAsync(_tempDir, CancellationToken.None);
Assert.Single(result.DeclaredPackages);
Assert.DoesNotContain(result.DeclaredPackages, e => e.Name == "__metadata");
var entry = result.DeclaredPackages.Single();
Assert.Equal("lodash", entry.Name);
Assert.Equal("4.17.21", entry.Version);
Assert.Equal("lodash@npm:4.17.21", entry.Resolved);
Assert.Equal("checksum:10c0deadbeef", entry.Integrity);
Assert.True(result.TryGet("", "lodash", "4.17.21", out var byVersion));
Assert.NotNull(byVersion);
Assert.Equal("lodash@npm:^4.17.21", byVersion!.Locator);
}
#endregion
#region pnpm-lock.yaml Parsing Tests
@@ -557,6 +644,24 @@ valid@^2.0.0:
Assert.Equal("4.18.2", result.DeclaredPackages.First().Version);
}
[Fact]
public async Task LoadPnpmLock_WhenVersionLineMissing_UsesVersionFromKey()
{
var content = "lockfileVersion: '6.0'\n" +
"packages:\n" +
" /express/4.18.2:\n" +
" resolution: {integrity: sha512-xyz}\n";
await File.WriteAllTextAsync(Path.Combine(_tempDir, "pnpm-lock.yaml"), content);
var result = await NodeLockData.LoadAsync(_tempDir, CancellationToken.None);
Assert.Single(result.DeclaredPackages);
var entry = result.DeclaredPackages.Single();
Assert.Equal("express", entry.Name);
Assert.Equal("4.18.2", entry.Version);
Assert.Equal("sha512-xyz", entry.Integrity);
}
[Fact]
public async Task LoadPnpmLock_ExtractsTarball()
{
@@ -573,6 +678,43 @@ valid@^2.0.0:
Assert.Contains("lodash-4.17.21.tgz", result.DeclaredPackages.First().Resolved);
}
[Fact]
public async Task LoadPnpmLock_IntegrityMissingReason_File()
{
var content = "lockfileVersion: '6.0'\n" +
"packages:\n" +
" /local-file/1.0.0:\n" +
" resolution: {tarball: file:../local-file-1.0.0.tgz}\n";
await File.WriteAllTextAsync(Path.Combine(_tempDir, "pnpm-lock.yaml"), content);
var result = await NodeLockData.LoadAsync(_tempDir, CancellationToken.None);
Assert.Single(result.DeclaredPackages);
var entry = result.DeclaredPackages.Single();
Assert.Equal("local-file", entry.Name);
Assert.True(entry.IntegrityMissing);
Assert.Equal("file", entry.IntegrityMissingReason);
Assert.StartsWith("file:", entry.Resolved, StringComparison.Ordinal);
}
[Fact]
public async Task LoadPnpmLock_SnapshotsSection_IsParsed()
{
var content = "lockfileVersion: '9.0'\n" +
"snapshots:\n" +
" /snap-only/1.0.0:\n" +
" resolution: {integrity: sha512-snap}\n";
await File.WriteAllTextAsync(Path.Combine(_tempDir, "pnpm-lock.yaml"), content);
var result = await NodeLockData.LoadAsync(_tempDir, CancellationToken.None);
Assert.Single(result.DeclaredPackages);
var entry = result.DeclaredPackages.Single();
Assert.Equal("snap-only", entry.Name);
Assert.Equal("1.0.0", entry.Version);
Assert.Equal("sha512-snap", entry.Integrity);
}
[Fact]
public async Task LoadPnpmLock_SeparateIntegrityLine()
{
@@ -590,7 +732,7 @@ valid@^2.0.0:
}
[Fact]
public async Task LoadPnpmLock_SkipsPackagesWithoutIntegrity()
public async Task LoadPnpmLock_PackagesWithoutIntegrity_AreKeptAndMarked()
{
var content = "lockfileVersion: '6.0'\n" +
"packages:\n" +
@@ -603,8 +745,19 @@ valid@^2.0.0:
var result = await NodeLockData.LoadAsync(_tempDir, CancellationToken.None);
Assert.Single(result.DeclaredPackages);
Assert.Equal("has-integrity", result.DeclaredPackages.First().Name);
Assert.Equal(2, result.DeclaredPackages.Count);
var noIntegrity = result.DeclaredPackages.Single(e => e.Name == "no-integrity");
Assert.Equal("1.0.0", noIntegrity.Version);
Assert.Null(noIntegrity.Integrity);
Assert.True(noIntegrity.IntegrityMissing);
Assert.Equal("missing", noIntegrity.IntegrityMissingReason);
var hasIntegrity = result.DeclaredPackages.Single(e => e.Name == "has-integrity");
Assert.Equal("2.0.0", hasIntegrity.Version);
Assert.Equal("sha512-valid", hasIntegrity.Integrity);
Assert.False(hasIntegrity.IntegrityMissing);
Assert.Null(hasIntegrity.IntegrityMissingReason);
}
[Fact]
@@ -863,6 +1016,12 @@ valid@^2.0.0:
Assert.True(result.TryGet("", "lodash", out var byNameEntry));
Assert.Equal("4.0.0", byNameEntry!.Version);
Assert.True(result.TryGet("", "lodash", "4.17.21", out var byVersionEntry));
Assert.Equal("4.17.21", byVersionEntry!.Version);
Assert.True(result.TryGet("", "lodash", "4.0.0", out var byVersionEntry2));
Assert.Equal("4.0.0", byVersionEntry2!.Version);
// For TryGet lookups by path, package-lock.json entry is found
Assert.True(result.TryGet("node_modules/lodash", "", out var byPathEntry));
Assert.Equal("4.17.21", byPathEntry!.Version);

View File

@@ -611,6 +611,177 @@ public sealed class NodePackageCollectorTraversalTests : IDisposable
#endregion
#region Lock Metadata Attachment
[Fact]
public async Task Traversal_NestedNodeModules_AttachesCorrectLockMetadata()
{
WritePackageJson(_tempDir, "root-app", "1.0.0", isPrivate: true);
var nodeModules = Path.Combine(_tempDir, "node_modules");
WritePackageJson(Path.Combine(nodeModules, "parent"), "parent", "1.0.0");
WritePackageJson(Path.Combine(nodeModules, "parent", "node_modules", "child"), "child", "2.0.0");
await File.WriteAllTextAsync(Path.Combine(_tempDir, "package-lock.json"), """
{
"lockfileVersion": 3,
"packages": {
"node_modules/parent": {
"version": "1.0.0",
"integrity": "sha512-parent"
},
"node_modules/parent/node_modules/child": {
"version": "2.0.0",
"resolved": "https://example.com/child.tgz",
"integrity": "sha512-child"
}
}
}
""");
var json = await RunAnalyzerAsync();
using var document = JsonDocument.Parse(json);
var components = document.RootElement.EnumerateArray().ToArray();
var child = components.Single(static element =>
element.TryGetProperty("purl", out var purl)
&& purl.ValueKind == JsonValueKind.String
&& purl.GetString() == "pkg:npm/child@2.0.0");
var metadata = child.GetProperty("metadata");
Assert.Equal("sha512-child", metadata.GetProperty("integrity").GetString());
Assert.Equal("https://example.com/child.tgz", metadata.GetProperty("resolved").GetString());
Assert.Equal("package-lock.json", metadata.GetProperty("lockSource").GetString());
Assert.Equal("package-lock.json:node_modules/parent/node_modules/child", metadata.GetProperty("lockLocator").GetString());
}
#endregion
#region Workspace Scope Attribution
[Fact]
public async Task Traversal_WorkspaceDependencyScopes_AreDerivedFromWorkspaceManifest()
{
File.WriteAllText(Path.Combine(_tempDir, "package.json"), """
{
"name": "root-app",
"version": "1.0.0",
"private": true,
"workspaces": ["packages/*"]
}
""");
var workspaceDir = Path.Combine(_tempDir, "packages", "app");
Directory.CreateDirectory(workspaceDir);
File.WriteAllText(Path.Combine(workspaceDir, "package.json"), """
{
"name": "app",
"version": "1.0.0",
"dependencies": {
"prod-dep": "^1.0.0"
},
"devDependencies": {
"dev-dep": "^1.0.0"
},
"optionalDependencies": {
"opt-dep": "^1.0.0"
}
}
""");
var nodeModules = Path.Combine(workspaceDir, "node_modules");
WritePackageJson(Path.Combine(nodeModules, "prod-dep"), "prod-dep", "1.0.0");
WritePackageJson(Path.Combine(nodeModules, "dev-dep"), "dev-dep", "1.0.0");
WritePackageJson(Path.Combine(nodeModules, "opt-dep"), "opt-dep", "1.0.0");
var json = await RunAnalyzerAsync();
using var document = JsonDocument.Parse(json);
var components = document.RootElement.EnumerateArray().ToArray();
var prod = components.Single(static element =>
element.TryGetProperty("purl", out var purl)
&& purl.ValueKind == JsonValueKind.String
&& purl.GetString() == "pkg:npm/prod-dep@1.0.0");
Assert.Equal("production", prod.GetProperty("metadata").GetProperty("scope").GetString());
Assert.Equal("production", prod.GetProperty("metadata").GetProperty("riskLevel").GetString());
var dev = components.Single(static element =>
element.TryGetProperty("purl", out var purl)
&& purl.ValueKind == JsonValueKind.String
&& purl.GetString() == "pkg:npm/dev-dep@1.0.0");
Assert.Equal("development", dev.GetProperty("metadata").GetProperty("scope").GetString());
Assert.Equal("development", dev.GetProperty("metadata").GetProperty("riskLevel").GetString());
var opt = components.Single(static element =>
element.TryGetProperty("purl", out var purl)
&& purl.ValueKind == JsonValueKind.String
&& purl.GetString() == "pkg:npm/opt-dep@1.0.0");
Assert.Equal("optional", opt.GetProperty("metadata").GetProperty("scope").GetString());
Assert.Equal("optional", opt.GetProperty("metadata").GetProperty("riskLevel").GetString());
Assert.Equal("true", opt.GetProperty("metadata").GetProperty("optional").GetString());
}
#endregion
#region Import Scanning Bounds
[Fact]
public async Task Traversal_ImportScan_SkipsNodeModulesPackagesByDefault()
{
WritePackageJson(_tempDir, "root-app", "1.0.0", isPrivate: true);
Directory.CreateDirectory(Path.Combine(_tempDir, "src"));
File.WriteAllText(Path.Combine(_tempDir, "src", "index.js"), "import foo from \"foo\";\n");
var fooDir = Path.Combine(_tempDir, "node_modules", "foo");
WritePackageJson(fooDir, "foo", "1.0.0");
File.WriteAllText(Path.Combine(fooDir, "index.js"), "import bar from \"bar\";\n");
var json = await RunAnalyzerAsync();
using var document = JsonDocument.Parse(json);
var components = document.RootElement.EnumerateArray().ToArray();
var root = components.Single(static element =>
element.TryGetProperty("purl", out var purl)
&& purl.ValueKind == JsonValueKind.String
&& purl.GetString() == "pkg:npm/root-app@1.0.0");
Assert.Contains(root.GetProperty("evidence").EnumerateArray(), e => e.GetProperty("source").GetString() == "node.import");
var foo = components.Single(static element =>
element.TryGetProperty("purl", out var purl)
&& purl.ValueKind == JsonValueKind.String
&& purl.GetString() == "pkg:npm/foo@1.0.0");
Assert.DoesNotContain(foo.GetProperty("evidence").EnumerateArray(), e => e.GetProperty("source").GetString() == "node.import");
}
[Fact]
public async Task Traversal_ImportScan_WhenCapped_EmitsImportScanSkippedMetadata()
{
WritePackageJson(_tempDir, "root-app", "1.0.0", isPrivate: true);
var srcDir = Path.Combine(_tempDir, "src");
Directory.CreateDirectory(srcDir);
for (var i = 0; i < 510; i++)
{
File.WriteAllText(Path.Combine(srcDir, $"file-{i:D4}.js"), ";\n");
}
var json = await RunAnalyzerAsync();
using var document = JsonDocument.Parse(json);
var components = document.RootElement.EnumerateArray().ToArray();
var root = components.Single(static element =>
element.TryGetProperty("purl", out var purl)
&& purl.ValueKind == JsonValueKind.String
&& purl.GetString() == "pkg:npm/root-app@1.0.0");
var metadata = root.GetProperty("metadata");
Assert.Equal("true", metadata.GetProperty("importScanSkipped").GetString());
Assert.Equal("500", metadata.GetProperty("importScan.filesScanned").GetString());
Assert.True(metadata.TryGetProperty("importScan.bytesScanned", out _));
}
#endregion
#region Deeply Nested Packages
[Fact]

View File

@@ -0,0 +1,102 @@
using StellaOps.Scanner.Analyzers.Lang.Node.Internal;
namespace StellaOps.Scanner.Analyzers.Lang.Node.Tests.Node;
public sealed class NodeWorkspaceIndexTests : IDisposable
{
private readonly string _tempDir;
public NodeWorkspaceIndexTests()
{
_tempDir = Path.Combine(Path.GetTempPath(), "node-workspace-index-tests-" + Guid.NewGuid().ToString("N")[..8]);
Directory.CreateDirectory(_tempDir);
}
public void Dispose()
{
if (Directory.Exists(_tempDir))
{
try
{
Directory.Delete(_tempDir, recursive: true);
}
catch
{
// Ignore cleanup failures in tests
}
}
}
private void WriteFile(string relativePath, string content)
{
var fullPath = Path.Combine(_tempDir, relativePath);
Directory.CreateDirectory(Path.GetDirectoryName(fullPath)!);
File.WriteAllText(fullPath, content);
}
[Fact]
public void Create_NoPackageJson_ReturnsEmpty()
{
var index = NodeWorkspaceIndex.Create(_tempDir);
Assert.Empty(index.GetMembers());
}
[Fact]
public void Create_PackagesStar_ExpandsImmediateChildren_Deterministically()
{
WriteFile("package.json", """
{
"workspaces": ["packages/*", "./tools/*"]
}
""");
WriteFile("packages/a/package.json", """{ "name": "a", "version": "1.0.0" }""");
WriteFile("packages/b/package.json", """{ "name": "b", "version": "1.0.0" }""");
Directory.CreateDirectory(Path.Combine(_tempDir, "packages", "empty"));
WriteFile("tools/t1/package.json", """{ "name": "t1", "version": "1.0.0" }""");
var index = NodeWorkspaceIndex.Create(_tempDir);
Assert.Equal(new[] { "packages/a", "packages/b", "tools/t1" }, index.GetMembers().ToArray());
}
[Fact]
public void Create_DoubleStar_ExpandsNestedMembers_AndSkipsNodeModules()
{
WriteFile("package.json", """
{
"workspaces": ["apps/**"]
}
""");
WriteFile("apps/app2/package.json", """{ "name": "app2", "version": "1.0.0" }""");
WriteFile("apps/team/app1/package.json", """{ "name": "app1", "version": "1.0.0" }""");
WriteFile("apps/team/node_modules/evil/package.json", """{ "name": "evil", "version": "1.0.0" }""");
var index = NodeWorkspaceIndex.Create(_tempDir);
Assert.Equal(new[] { "apps/app2", "apps/team/app1" }, index.GetMembers().ToArray());
Assert.False(index.TryGetMember("apps/team/node_modules/evil", out _));
}
[Fact]
public void Create_PackagesDoubleStar_DoesNotIncludeNodeModulesTree()
{
WriteFile("package.json", """
{
"workspaces": ["packages/**"]
}
""");
WriteFile("packages/ok/package.json", """{ "name": "ok", "version": "1.0.0" }""");
WriteFile("packages/node_modules/evil/package.json", """{ "name": "evil", "version": "1.0.0" }""");
var index = NodeWorkspaceIndex.Create(_tempDir);
Assert.Single(index.GetMembers());
Assert.Equal("packages/ok", index.GetMembers().Single());
}
}

View File

@@ -17,12 +17,10 @@
<PackageReference Remove="xunit" />
<PackageReference Remove="xunit.runner.visualstudio" />
<PackageReference Remove="Microsoft.AspNetCore.Mvc.Testing" />
<PackageReference Remove="Mongo2Go" />
<PackageReference Remove="coverlet.collector" />
<PackageReference Remove="Microsoft.Extensions.TimeProvider.Testing" />
<ProjectReference Remove="..\StellaOps.Concelier.Testing\StellaOps.Concelier.Testing.csproj" />
<Compile Remove="$(MSBuildThisFileDirectory)..\StellaOps.Concelier.Tests.Shared\AssemblyInfo.cs" />
<Compile Remove="$(MSBuildThisFileDirectory)..\StellaOps.Concelier.Tests.Shared\MongoFixtureCollection.cs" />
<Using Remove="StellaOps.Concelier.Testing" />
</ItemGroup>

View File

@@ -14,12 +14,10 @@
<PackageReference Remove="xunit" />
<PackageReference Remove="xunit.runner.visualstudio" />
<PackageReference Remove="Microsoft.AspNetCore.Mvc.Testing" />
<PackageReference Remove="Mongo2Go" />
<PackageReference Remove="coverlet.collector" />
<PackageReference Remove="Microsoft.Extensions.TimeProvider.Testing" />
<ProjectReference Remove="..\StellaOps.Concelier.Testing\StellaOps.Concelier.Testing.csproj" />
<Compile Remove="$(MSBuildThisFileDirectory)..\StellaOps.Concelier.Tests.Shared\AssemblyInfo.cs" />
<Compile Remove="$(MSBuildThisFileDirectory)..\StellaOps.Concelier.Tests.Shared\MongoFixtureCollection.cs" />
<Using Remove="StellaOps.Concelier.Testing" />
</ItemGroup>

View File

@@ -1,5 +1,6 @@
using StellaOps.Scanner.Analyzers.Lang.Python.Internal.Entrypoints;
using StellaOps.Scanner.Analyzers.Lang.Python.Internal.VirtualFileSystem;
using System.IO.Compression;
namespace StellaOps.Scanner.Analyzers.Lang.Python.Tests.Entrypoints;
@@ -86,6 +87,40 @@ mygui = mypackage.gui:start
}
}
[Fact]
public async Task DiscoverAsync_FindsZipappMain()
{
var cancellationToken = TestContext.Current.CancellationToken;
var tempPath = CreateTemporaryWorkspace();
try
{
var zipappPath = Path.Combine(tempPath, "app.pyz");
using (var stream = File.Create(zipappPath))
using (var archive = new ZipArchive(stream, ZipArchiveMode.Create, leaveOpen: false))
{
var entry = archive.CreateEntry("__main__.py");
await using var entryStream = entry.Open();
await using var writer = new StreamWriter(entryStream);
await writer.WriteAsync("print('hello')".AsMemory(), cancellationToken);
}
var vfs = PythonVirtualFileSystem.CreateBuilder()
.AddZipapp(zipappPath)
.Build();
var discovery = new PythonEntrypointDiscovery(vfs, tempPath);
await discovery.DiscoverAsync(cancellationToken);
Assert.Contains(discovery.Entrypoints, e =>
e.Kind == PythonEntrypointKind.ZipappMain &&
e.Name == "__main__");
}
finally
{
Directory.Delete(tempPath, recursive: true);
}
}
[Fact]
public async Task DiscoverAsync_FindsDjangoManage()
{

View File

@@ -0,0 +1,4 @@
Metadata-Version: 2.1
Name: hiddenpkg
Version: 0.1.0
Summary: Hidden layer package

View File

@@ -1,4 +1,38 @@
[
{
"analyzerId": "python",
"componentKey": "purl::pkg:pypi/hiddenpkg@0.1.0",
"purl": "pkg:pypi/hiddenpkg@0.1.0",
"name": "hiddenpkg",
"version": "0.1.0",
"type": "pypi",
"usedByEntrypoint": false,
"metadata": {
"distInfoPath": ".layers/layer0/usr/lib/python3.11/site-packages/hiddenpkg-0.1.0.dist-info",
"name": "hiddenpkg",
"normalizedName": "hiddenpkg",
"pkg.confidence": "High",
"pkg.kind": "Wheel",
"pkg.location": ".layers/layer0/usr/lib/python3.11/site-packages/hiddenpkg-0.1.0.dist-info",
"provenance": "dist-info",
"record.hashMismatches": "0",
"record.hashedEntries": "0",
"record.ioErrors": "0",
"record.missingFiles": "0",
"record.totalEntries": "0",
"runtime.libPaths.count": "2",
"runtime.versions": "3.11",
"summary": "Hidden layer package",
"version": "0.1.0"
},
"evidence": [
{
"kind": "file",
"source": "METADATA",
"locator": ".layers/layer0/usr/lib/python3.11/site-packages/hiddenpkg-0.1.0.dist-info/METADATA"
}
]
},
{
"analyzerId": "python",
"componentKey": "purl::pkg:pypi/layered@2.0",
@@ -6,37 +40,38 @@
"name": "layered",
"version": "2.0",
"type": "pypi",
"usedByEntrypoint": true,
"usedByEntrypoint": false,
"metadata": {
"author": "Layered Maintainer",
"authorEmail": "maintainer@example.com",
"classifier[0]": "Programming Language :: Python :: 3",
"classifiers": "Programming Language :: Python :: 3",
"distInfoPath": "layer1/usr/lib/python3.11/site-packages/layered-2.0.dist-info",
"classifier[0]": "License :: OSI Approved :: Apache Software License",
"classifiers": "License :: OSI Approved :: Apache Software License",
"distInfoPath": "layer2/usr/lib/python3.11/site-packages/layered-2.0.dist-info",
"editable": "true",
"entryPoints.console_scripts": "layered-cli=layered.cli:main",
"entryPoints.layered.hooks": "register=layered.plugins:register",
"installer": "pip",
"license": "Apache-2.0",
"license.classifier[0]": "License :: OSI Approved :: Apache Software License",
"license.file[0]": "layer2/usr/lib/python3.11/site-packages/LICENSE",
"licenseExpression": "Apache-2.0",
"name": "layered",
"normalizedName": "layered",
"pkg.confidence": "Definitive",
"pkg.kind": "Wheel",
"pkg.location": "layer2/usr/lib/python3.11/site-packages/layered-2.0.dist-info",
"projectUrl": "Documentation, https://example.com/layered/docs",
"provenance": "dist-info",
"record.hashMismatches": "0",
"record.hashedEntries": "7",
"record.hashedEntries": "8",
"record.ioErrors": "0",
"record.missingFiles": "1",
"record.missingFiles": "0",
"record.totalEntries": "9",
"requiresDist": "requests",
"requiresPython": "\u003E=3.9",
"runtime.libPaths.count": "2",
"runtime.versions": "3.11",
"sourceCommit": "abc123",
"sourceSubdirectory": "src/layered",
"sourceUrl": "https://git.example.com/layered",
"sourceVcs": "git",
"summary": "Base layer metadata",
"summary": "Overlay metadata adding direct URL information",
"version": "2.0",
"wheel.generator": "pip 24.0",
"wheel.rootIsPurelib": "true",
@@ -44,57 +79,26 @@
"wheel.version": "1.0"
},
"evidence": [
{
"kind": "derived",
"source": "RECORD",
"locator": "layer1/usr/bin/layered-cli",
"value": "missing"
},
{
"kind": "file",
"source": "INSTALLER",
"locator": "layer1/usr/lib/python3.11/site-packages/layered-2.0.dist-info/INSTALLER"
},
{
"kind": "file",
"source": "INSTALLER",
"locator": "layer2/usr/lib/python3.11/site-packages/layered-2.0.dist-info/INSTALLER"
},
{
"kind": "file",
"source": "METADATA",
"locator": "layer1/usr/lib/python3.11/site-packages/layered-2.0.dist-info/METADATA"
},
{
"kind": "file",
"source": "METADATA",
"locator": "layer2/usr/lib/python3.11/site-packages/layered-2.0.dist-info/METADATA"
},
{
"kind": "file",
"source": "RECORD",
"locator": "layer1/usr/lib/python3.11/site-packages/layered-2.0.dist-info/RECORD"
},
{
"kind": "file",
"source": "RECORD",
"locator": "layer2/usr/lib/python3.11/site-packages/layered-2.0.dist-info/RECORD"
},
{
"kind": "file",
"source": "WHEEL",
"locator": "layer1/usr/lib/python3.11/site-packages/layered-2.0.dist-info/WHEEL"
},
{
"kind": "file",
"source": "WHEEL",
"locator": "layer2/usr/lib/python3.11/site-packages/layered-2.0.dist-info/WHEEL"
},
{
"kind": "file",
"source": "entry_points.txt",
"locator": "layer1/usr/lib/python3.11/site-packages/layered-2.0.dist-info/entry_points.txt"
},
{
"kind": "file",
"source": "entry_points.txt",
@@ -112,5 +116,39 @@
"value": "https://git.example.com/layered"
}
]
},
{
"analyzerId": "python",
"componentKey": "purl::pkg:pypi/layerspkg@0.2.0",
"purl": "pkg:pypi/layerspkg@0.2.0",
"name": "layerspkg",
"version": "0.2.0",
"type": "pypi",
"usedByEntrypoint": false,
"metadata": {
"distInfoPath": "layers/layer3/usr/lib/python3.11/site-packages/layerspkg-0.2.0.dist-info",
"name": "layerspkg",
"normalizedName": "layerspkg",
"pkg.confidence": "High",
"pkg.kind": "Wheel",
"pkg.location": "layers/layer3/usr/lib/python3.11/site-packages/layerspkg-0.2.0.dist-info",
"provenance": "dist-info",
"record.hashMismatches": "0",
"record.hashedEntries": "0",
"record.ioErrors": "0",
"record.missingFiles": "0",
"record.totalEntries": "0",
"runtime.libPaths.count": "2",
"runtime.versions": "3.11",
"summary": "Layers/ root package",
"version": "0.2.0"
},
"evidence": [
{
"kind": "file",
"source": "METADATA",
"locator": "layers/layer3/usr/lib/python3.11/site-packages/layerspkg-0.2.0.dist-info/METADATA"
}
]
}
]

View File

@@ -0,0 +1,4 @@
Metadata-Version: 2.1
Name: layerspkg
Version: 0.2.0
Summary: Layers/ root package

View File

@@ -20,6 +20,9 @@
"license.file[0]": "LICENSE",
"name": "Cache-Pkg",
"normalizedName": "cache-pkg",
"pkg.confidence": "Definitive",
"pkg.kind": "Wheel",
"pkg.location": "lib/python3.11/site-packages/cache_pkg-1.2.3.dist-info",
"projectUrl": "Source, https://example.com/cache-pkg",
"provenance": "dist-info",
"record.hashMismatches": "1",
@@ -86,4 +89,4 @@
}
]
}
]
]

View File

@@ -22,6 +22,9 @@
"license.classifier[0]": "License :: OSI Approved :: Apache Software License",
"name": "simple",
"normalizedName": "simple",
"pkg.confidence": "Definitive",
"pkg.kind": "Wheel",
"pkg.location": "lib/python3.11/site-packages/simple-1.0.0.dist-info",
"projectUrl": "Source, https://example.com/simple/src",
"provenance": "dist-info",
"record.hashMismatches": "0",
@@ -84,4 +87,4 @@
}
]
}
]
]

View File

@@ -1,3 +1,4 @@
using System.IO.Compression;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
@@ -125,6 +126,137 @@ public sealed class PythonLanguageAnalyzerTests
}
}
[Fact]
public async Task EditableRequirementsUseExplicitKeyWithoutHostPathLeakAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var fixturePath = CreateTemporaryWorkspace();
try
{
var editableDir = Path.Combine(fixturePath, "editable-src");
Directory.CreateDirectory(editableDir);
var requirementsPath = Path.Combine(fixturePath, "requirements.txt");
await File.WriteAllTextAsync(requirementsPath, $"--editable {editableDir}{Environment.NewLine}", cancellationToken);
var analyzers = new ILanguageAnalyzer[]
{
new PythonLanguageAnalyzer()
};
var json = await LanguageAnalyzerTestHarness.RunToJsonAsync(
fixturePath,
analyzers,
cancellationToken);
using var document = JsonDocument.Parse(json);
var root = document.RootElement;
foreach (var component in root.EnumerateArray())
{
if (component.TryGetProperty("purl", out var purlElement) && purlElement.ValueKind == JsonValueKind.String)
{
Assert.DoesNotContain("@editable", purlElement.GetString(), StringComparison.OrdinalIgnoreCase);
}
}
var editableComponent = root.EnumerateArray().Single(static component =>
component.TryGetProperty("name", out var nameElement)
&& string.Equals("editable-src", nameElement.GetString(), StringComparison.OrdinalIgnoreCase));
Assert.True(!editableComponent.TryGetProperty("purl", out var purlValue) || purlValue.ValueKind == JsonValueKind.Null);
var componentKey = editableComponent.GetProperty("componentKey").GetString();
Assert.StartsWith("explicit::python::pypi::editable-src::sha256:", componentKey, StringComparison.Ordinal);
var metadata = editableComponent.GetProperty("metadata");
Assert.Equal("true", metadata.GetProperty("declaredOnly").GetString());
Assert.Equal("editable", metadata.GetProperty("declared.sourceType").GetString());
Assert.Equal("requirements.txt", metadata.GetProperty("declared.source").GetString());
Assert.Equal("requirements.txt", metadata.GetProperty("declared.locator").GetString());
var editableSpec = metadata.GetProperty("lockEditablePath").GetString();
Assert.Equal("editable-src", editableSpec);
Assert.DoesNotContain(fixturePath, editableSpec, StringComparison.OrdinalIgnoreCase);
Assert.DoesNotContain(":", editableSpec, StringComparison.Ordinal);
}
finally
{
Directory.Delete(fixturePath, recursive: true);
}
}
[Fact]
public async Task WheelArchiveDistInfo_IsVerifiedFromRecordAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var fixturePath = CreateTemporaryWorkspace();
try
{
var distDir = Path.Combine(fixturePath, "dist");
Directory.CreateDirectory(distDir);
var wheelPath = Path.Combine(distDir, "archivepkg-1.0.0-py3-none-any.whl");
var initBytes = Encoding.UTF8.GetBytes("__version__ = \"1.0.0\"\n");
var metadataBytes = Encoding.UTF8.GetBytes(
$"Metadata-Version: 2.1\nName: archivepkg\nVersion: 1.0.0\n{Environment.NewLine}");
var wheelBytes = Encoding.UTF8.GetBytes(
"Wheel-Version: 1.0\nGenerator: test\nRoot-Is-Purelib: true\nTag: py3-none-any\n");
var recordContent = new StringBuilder()
.AppendLine($"archivepkg/__init__.py,sha256={ComputeSha256Base64(initBytes)},{initBytes.Length}")
.AppendLine($"archivepkg-1.0.0.dist-info/METADATA,sha256={ComputeSha256Base64(metadataBytes)},{metadataBytes.Length}")
.AppendLine($"archivepkg-1.0.0.dist-info/WHEEL,sha256={ComputeSha256Base64(wheelBytes)},{wheelBytes.Length}")
.AppendLine("archivepkg-1.0.0.dist-info/RECORD,,")
.ToString();
var recordBytes = Encoding.UTF8.GetBytes(recordContent);
using (var stream = File.Create(wheelPath))
using (var archive = new ZipArchive(stream, ZipArchiveMode.Create, leaveOpen: false))
{
WriteEntry(archive, "archivepkg/__init__.py", initBytes);
WriteEntry(archive, "archivepkg-1.0.0.dist-info/METADATA", metadataBytes);
WriteEntry(archive, "archivepkg-1.0.0.dist-info/WHEEL", wheelBytes);
WriteEntry(archive, "archivepkg-1.0.0.dist-info/RECORD", recordBytes);
}
var analyzers = new ILanguageAnalyzer[]
{
new PythonLanguageAnalyzer()
};
var json = await LanguageAnalyzerTestHarness.RunToJsonAsync(
fixturePath,
analyzers,
cancellationToken);
using var document = JsonDocument.Parse(json);
var root = document.RootElement;
Assert.True(ComponentHasMetadata(root, "archivepkg", "record.totalEntries", "4"));
Assert.True(ComponentHasMetadata(root, "archivepkg", "record.hashedEntries", "3"));
Assert.True(ComponentHasMetadata(root, "archivepkg", "record.missingFiles", "0"));
Assert.True(ComponentHasMetadata(root, "archivepkg", "record.hashMismatches", "0"));
Assert.True(ComponentHasMetadata(root, "archivepkg", "record.ioErrors", "0"));
}
finally
{
Directory.Delete(fixturePath, recursive: true);
}
static void WriteEntry(ZipArchive archive, string entryName, byte[] content)
{
var entry = archive.CreateEntry(entryName);
using var entryStream = entry.Open();
entryStream.Write(content, 0, content.Length);
}
static string ComputeSha256Base64(byte[] content)
=> Convert.ToBase64String(SHA256.HashData(content));
}
private static async Task CreatePythonPackageAsync(string root, string name, string version, CancellationToken cancellationToken)
{
var sitePackages = Path.Combine(root, "lib", "python3.11", "site-packages");

View File

@@ -14,12 +14,10 @@
<PackageReference Remove="xunit" />
<PackageReference Remove="xunit.runner.visualstudio" />
<PackageReference Remove="Microsoft.AspNetCore.Mvc.Testing" />
<PackageReference Remove="Mongo2Go" />
<PackageReference Remove="coverlet.collector" />
<PackageReference Remove="Microsoft.Extensions.TimeProvider.Testing" />
<ProjectReference Remove="..\StellaOps.Concelier.Testing\StellaOps.Concelier.Testing.csproj" />
<Compile Remove="$(MSBuildThisFileDirectory)..\StellaOps.Concelier.Tests.Shared\AssemblyInfo.cs" />
<Compile Remove="$(MSBuildThisFileDirectory)..\StellaOps.Concelier.Tests.Shared\MongoFixtureCollection.cs" />
<!-- Exclude OpenSSL shim files - already provided by StellaOps.Scanner.Analyzers.Lang.Tests reference -->
<Compile Remove="$(MSBuildThisFileDirectory)..\..\..\..\tests\shared\OpenSslLegacyShim.cs" />
<Compile Remove="$(MSBuildThisFileDirectory)..\..\..\..\tests\shared\OpenSslAutoInit.cs" />

View File

@@ -100,11 +100,12 @@ public sealed class PythonVirtualFileSystemTests
.Build();
Assert.Equal(3, vfs.FileCount);
Assert.True(vfs.FileExists("mypackage/__init__.py"));
Assert.True(vfs.FileExists("mypackage/core.py"));
Assert.True(vfs.FileExists("mypackage-1.0.0.dist-info/METADATA"));
var wheelRoot = $"archives/wheel/{Path.GetFileName(wheelPath)}";
Assert.True(vfs.FileExists($"{wheelRoot}/mypackage/__init__.py"));
Assert.True(vfs.FileExists($"{wheelRoot}/mypackage/core.py"));
Assert.True(vfs.FileExists($"{wheelRoot}/mypackage-1.0.0.dist-info/METADATA"));
Assert.True(vfs.TryGetFile("mypackage/__init__.py", out var file));
Assert.True(vfs.TryGetFile($"{wheelRoot}/mypackage/__init__.py", out var file));
Assert.Equal(PythonFileSource.Wheel, file!.Source);
Assert.Equal(wheelPath, file.ArchivePath);
}
@@ -141,9 +142,10 @@ public sealed class PythonVirtualFileSystemTests
.Build();
Assert.Equal(1, vfs.FileCount);
Assert.True(vfs.FileExists("__main__.py"));
var zipappRoot = $"archives/zipapp/{Path.GetFileName(zipappPath)}";
Assert.True(vfs.FileExists($"{zipappRoot}/__main__.py"));
Assert.True(vfs.TryGetFile("__main__.py", out var file));
Assert.True(vfs.TryGetFile($"{zipappRoot}/__main__.py", out var file));
Assert.Equal(PythonFileSource.Zipapp, file!.Source);
}
finally
@@ -252,7 +254,7 @@ public sealed class PythonVirtualFileSystemTests
Assert.Single(sitePackagesFiles);
Assert.Single(wheelFiles);
Assert.Equal("installed/__init__.py", sitePackagesFiles[0].VirtualPath);
Assert.Equal("wheel/__init__.py", wheelFiles[0].VirtualPath);
Assert.Equal($"archives/wheel/{Path.GetFileName(wheelPath)}/wheel/__init__.py", wheelFiles[0].VirtualPath);
}
finally
{

View File

@@ -12,6 +12,7 @@
"ruby.observation.capability.schedulers": "0",
"ruby.observation.capability.serialization": "false",
"ruby.observation.dependency_edges": "6",
"ruby.observation.entrypoints": "0",
"ruby.observation.packages": "9",
"ruby.observation.ruby_version": "3.2.0",
"ruby.observation.runtime_edges": "0"

View File

@@ -7,12 +7,13 @@
"usedByEntrypoint": false,
"metadata": {
"ruby.observation.bundler_version": "2.5.3",
"ruby.observation.capability.exec": "false",
"ruby.observation.capability.exec": "true",
"ruby.observation.capability.net": "true",
"ruby.observation.capability.scheduler_list": "clockwork;sidekiq",
"ruby.observation.capability.schedulers": "2",
"ruby.observation.capability.serialization": "false",
"ruby.observation.dependency_edges": "4",
"ruby.observation.entrypoints": "1",
"ruby.observation.packages": "6",
"ruby.observation.runtime_edges": "5"
},
@@ -21,8 +22,8 @@
"kind": "derived",
"source": "ruby.observation",
"locator": "document",
"value": "{\u0022$schema\u0022:\u0022stellaops.ruby.observation@1\u0022,\u0022packages\u0022:[{\u0022name\u0022:\u0022clockwork\u0022,\u0022version\u0022:\u00223.0.0\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022ops\u0022]},{\u0022name\u0022:\u0022pagy\u0022,\u0022version\u0022:\u00226.5.0\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022web\u0022]},{\u0022name\u0022:\u0022pry\u0022,\u0022version\u0022:\u00220.14.2\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022tools\u0022]},{\u0022name\u0022:\u0022rack\u0022,\u0022version\u0022:\u00223.1.2\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022default\u0022]},{\u0022name\u0022:\u0022sidekiq\u0022,\u0022version\u0022:\u00227.2.1\u0022,\u0022source\u0022:\u0022vendor\u0022,\u0022declaredOnly\u0022:false,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022artifact\u0022:\u0022vendor/custom-bundle/cache/sidekiq-7.2.1.gem\u0022,\u0022groups\u0022:[\u0022jobs\u0022]},{\u0022name\u0022:\u0022sinatra\u0022,\u0022version\u0022:\u00223.1.0\u0022,\u0022source\u0022:\u0022vendor-cache\u0022,\u0022declaredOnly\u0022:false,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022artifact\u0022:\u0022vendor/cache/sinatra-3.1.0.gem\u0022,\u0022groups\u0022:[\u0022web\u0022]}],\u0022entrypoints\u0022:[{\u0022path\u0022:\u0022config/environment.rb\u0022,\u0022type\u0022:\u0022script\u0022,\u0022requiredGems\u0022:[\u0022pagy\u0022]}],\u0022dependencyEdges\u0022:[{\u0022from\u0022:\u0022pkg:gem/pry@0.14.2\u0022,\u0022to\u0022:\u0022coderay\u0022,\u0022constraint\u0022:\u0022~\\u003E 1.1\u0022},{\u0022from\u0022:\u0022pkg:gem/pry@0.14.2\u0022,\u0022to\u0022:\u0022method_source\u0022,\u0022constraint\u0022:\u0022~\\u003E 1.0\u0022},{\u0022from\u0022:\u0022pkg:gem/sidekiq@7.2.1\u0022,\u0022to\u0022:\u0022rack\u0022,\u0022constraint\u0022:\u0022~\\u003E 2.0\u0022},{\u0022from\u0022:\u0022pkg:gem/sinatra@3.1.0\u0022,\u0022to\u0022:\u0022rack\u0022,\u0022constraint\u0022:\u0022~\\u003E 3.0\u0022}],\u0022runtimeEdges\u0022:[{\u0022package\u0022:\u0022clockwork\u0022,\u0022usedByEntrypoint\u0022:false,\u0022files\u0022:[\u0022scripts/worker.rb\u0022],\u0022entrypoints\u0022:[],\u0022reasons\u0022:[\u0022require-static\u0022]},{\u0022package\u0022:\u0022pagy\u0022,\u0022usedByEntrypoint\u0022:true,\u0022files\u0022:[\u0022app/main.rb\u0022,\u0022config/environment.rb\u0022],\u0022entrypoints\u0022:[\u0022config/environment.rb\u0022],\u0022reasons\u0022:[\u0022require-static\u0022]},{\u0022package\u0022:\u0022rack\u0022,\u0022usedByEntrypoint\u0022:false,\u0022files\u0022:[\u0022app/main.rb\u0022],\u0022entrypoints\u0022:[],\u0022reasons\u0022:[\u0022require-static\u0022]},{\u0022package\u0022:\u0022sidekiq\u0022,\u0022usedByEntrypoint\u0022:false,\u0022files\u0022:[\u0022scripts/worker.rb\u0022],\u0022entrypoints\u0022:[],\u0022reasons\u0022:[\u0022require-static\u0022]},{\u0022package\u0022:\u0022sinatra\u0022,\u0022usedByEntrypoint\u0022:false,\u0022files\u0022:[\u0022app/main.rb\u0022],\u0022entrypoints\u0022:[],\u0022reasons\u0022:[\u0022require-static\u0022]}],\u0022jobs\u0022:[{\u0022name\u0022:\u0022clockwork\u0022,\u0022type\u0022:\u0022scheduler\u0022,\u0022scheduler\u0022:\u0022clockwork\u0022},{\u0022name\u0022:\u0022sidekiq\u0022,\u0022type\u0022:\u0022scheduler\u0022,\u0022scheduler\u0022:\u0022sidekiq\u0022}],\u0022environment\u0022:{\u0022bundlerVersion\u0022:\u00222.5.3\u0022,\u0022bundlePaths\u0022:[\u0022/mnt/e/dev/git.stella-ops.org/src/Scanner/__Tests/StellaOps.Scanner.Analyzers.Lang.Ruby.Tests/bin/Debug/net10.0/Fixtures/lang/ruby/complex-app/vendor/custom-bundle\u0022],\u0022gemfiles\u0022:[\u0022/mnt/e/dev/git.stella-ops.org/src/Scanner/__Tests/StellaOps.Scanner.Analyzers.Lang.Ruby.Tests/bin/Debug/net10.0/Fixtures/lang/ruby/complex-app/Gemfile\u0022],\u0022lockfiles\u0022:[\u0022Gemfile.lock\u0022],\u0022frameworks\u0022:[\u0022clockwork\u0022,\u0022sidekiq\u0022]},\u0022capabilities\u0022:{\u0022usesExec\u0022:false,\u0022usesNetwork\u0022:true,\u0022usesSerialization\u0022:false,\u0022jobSchedulers\u0022:[\u0022clockwork\u0022,\u0022sidekiq\u0022]},\u0022bundledWith\u0022:\u00222.5.3\u0022}",
"sha256": "sha256:bd15160e034ea5adf0a8384dc9ee18557f695b0952d4fb17214f1bd1381ad22a"
"value": "{\u0022$schema\u0022:\u0022stellaops.ruby.observation@1\u0022,\u0022packages\u0022:[{\u0022name\u0022:\u0022clockwork\u0022,\u0022version\u0022:\u00223.0.0\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022ops\u0022]},{\u0022name\u0022:\u0022pagy\u0022,\u0022version\u0022:\u00226.5.0\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022web\u0022]},{\u0022name\u0022:\u0022pry\u0022,\u0022version\u0022:\u00220.14.2\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022tools\u0022]},{\u0022name\u0022:\u0022rack\u0022,\u0022version\u0022:\u00223.1.2\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022default\u0022]},{\u0022name\u0022:\u0022sidekiq\u0022,\u0022version\u0022:\u00227.2.1\u0022,\u0022source\u0022:\u0022vendor\u0022,\u0022declaredOnly\u0022:false,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022artifact\u0022:\u0022vendor/custom-bundle/cache/sidekiq-7.2.1.gem\u0022,\u0022groups\u0022:[\u0022jobs\u0022]},{\u0022name\u0022:\u0022sinatra\u0022,\u0022version\u0022:\u00223.1.0\u0022,\u0022source\u0022:\u0022vendor-cache\u0022,\u0022declaredOnly\u0022:false,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022artifact\u0022:\u0022vendor/cache/sinatra-3.1.0.gem\u0022,\u0022groups\u0022:[\u0022web\u0022]}],\u0022entrypoints\u0022:[{\u0022path\u0022:\u0022config/environment.rb\u0022,\u0022type\u0022:\u0022script\u0022,\u0022requiredGems\u0022:[\u0022pagy\u0022]}],\u0022dependencyEdges\u0022:[{\u0022from\u0022:\u0022pkg:gem/pry@0.14.2\u0022,\u0022to\u0022:\u0022coderay\u0022,\u0022constraint\u0022:\u0022~\\u003E 1.1\u0022},{\u0022from\u0022:\u0022pkg:gem/pry@0.14.2\u0022,\u0022to\u0022:\u0022method_source\u0022,\u0022constraint\u0022:\u0022~\\u003E 1.0\u0022},{\u0022from\u0022:\u0022pkg:gem/sidekiq@7.2.1\u0022,\u0022to\u0022:\u0022rack\u0022,\u0022constraint\u0022:\u0022~\\u003E 2.0\u0022},{\u0022from\u0022:\u0022pkg:gem/sinatra@3.1.0\u0022,\u0022to\u0022:\u0022rack\u0022,\u0022constraint\u0022:\u0022~\\u003E 3.0\u0022}],\u0022runtimeEdges\u0022:[{\u0022package\u0022:\u0022clockwork\u0022,\u0022usedByEntrypoint\u0022:false,\u0022files\u0022:[\u0022scripts/worker.rb\u0022],\u0022entrypoints\u0022:[],\u0022reasons\u0022:[\u0022require-static\u0022]},{\u0022package\u0022:\u0022pagy\u0022,\u0022usedByEntrypoint\u0022:true,\u0022files\u0022:[\u0022app/main.rb\u0022,\u0022config/environment.rb\u0022],\u0022entrypoints\u0022:[\u0022config/environment.rb\u0022],\u0022reasons\u0022:[\u0022require-static\u0022]},{\u0022package\u0022:\u0022rack\u0022,\u0022usedByEntrypoint\u0022:false,\u0022files\u0022:[\u0022app/main.rb\u0022],\u0022entrypoints\u0022:[],\u0022reasons\u0022:[\u0022require-static\u0022]},{\u0022package\u0022:\u0022sidekiq\u0022,\u0022usedByEntrypoint\u0022:false,\u0022files\u0022:[\u0022scripts/worker.rb\u0022],\u0022entrypoints\u0022:[],\u0022reasons\u0022:[\u0022require-static\u0022]},{\u0022package\u0022:\u0022sinatra\u0022,\u0022usedByEntrypoint\u0022:false,\u0022files\u0022:[\u0022app/main.rb\u0022],\u0022entrypoints\u0022:[],\u0022reasons\u0022:[\u0022require-static\u0022]}],\u0022jobs\u0022:[{\u0022name\u0022:\u0022clockwork\u0022,\u0022type\u0022:\u0022scheduler\u0022,\u0022scheduler\u0022:\u0022clockwork\u0022},{\u0022name\u0022:\u0022sidekiq\u0022,\u0022type\u0022:\u0022scheduler\u0022,\u0022scheduler\u0022:\u0022sidekiq\u0022}],\u0022environment\u0022:{\u0022bundlerVersion\u0022:\u00222.5.3\u0022,\u0022bundlePaths\u0022:[\u0022vendor/custom-bundle\u0022],\u0022gemfiles\u0022:[\u0022Gemfile\u0022],\u0022lockfiles\u0022:[\u0022Gemfile.lock\u0022],\u0022frameworks\u0022:[\u0022clockwork\u0022,\u0022sidekiq\u0022]},\u0022capabilities\u0022:{\u0022usesExec\u0022:true,\u0022usesNetwork\u0022:true,\u0022usesSerialization\u0022:false,\u0022jobSchedulers\u0022:[\u0022clockwork\u0022,\u0022sidekiq\u0022]},\u0022bundledWith\u0022:\u00222.5.3\u0022}",
"sha256": "sha256:fd8d3da9563e77103a04ec6020d8ec87dd7d9fab2160a31e289035787895f15e"
}
]
},
@@ -35,6 +36,7 @@
"type": "gem",
"usedByEntrypoint": false,
"metadata": {
"capability.exec": "true",
"capability.net": "true",
"capability.scheduler": "clockwork;sidekiq",
"capability.scheduler.clockwork": "true",
@@ -64,6 +66,7 @@
"type": "gem",
"usedByEntrypoint": true,
"metadata": {
"capability.exec": "true",
"capability.net": "true",
"capability.scheduler": "clockwork;sidekiq",
"capability.scheduler.clockwork": "true",
@@ -94,6 +97,7 @@
"type": "gem",
"usedByEntrypoint": false,
"metadata": {
"capability.exec": "true",
"capability.net": "true",
"capability.scheduler": "clockwork;sidekiq",
"capability.scheduler.clockwork": "true",
@@ -120,6 +124,7 @@
"type": "gem",
"usedByEntrypoint": false,
"metadata": {
"capability.exec": "true",
"capability.net": "true",
"capability.scheduler": "clockwork;sidekiq",
"capability.scheduler.clockwork": "true",
@@ -150,6 +155,7 @@
"usedByEntrypoint": false,
"metadata": {
"artifact": "vendor/custom-bundle/cache/sidekiq-7.2.1.gem",
"capability.exec": "true",
"capability.net": "true",
"capability.scheduler": "clockwork;sidekiq",
"capability.scheduler.clockwork": "true",
@@ -180,6 +186,7 @@
"usedByEntrypoint": false,
"metadata": {
"artifact": "vendor/cache/sinatra-3.1.0.gem",
"capability.exec": "true",
"capability.net": "true",
"capability.scheduler": "clockwork;sidekiq",
"capability.scheduler.clockwork": "true",

View File

@@ -12,6 +12,8 @@
"ruby.observation.capability.schedulers": "0",
"ruby.observation.capability.serialization": "false",
"ruby.observation.dependency_edges": "3",
"ruby.observation.entrypoints": "2",
"ruby.observation.native_extensions": "2",
"ruby.observation.packages": "7",
"ruby.observation.ruby_version": "3.2.0",
"ruby.observation.runtime_edges": "3",
@@ -23,8 +25,8 @@
"kind": "derived",
"source": "ruby.observation",
"locator": "document",
"value": "{\u0022$schema\u0022:\u0022stellaops.ruby.observation@1\u0022,\u0022packages\u0022:[{\u0022name\u0022:\u0022mini_portile2\u0022,\u0022version\u0022:\u00222.8.4\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022default\u0022]},{\u0022name\u0022:\u0022nio4r\u0022,\u0022version\u0022:\u00222.5.9\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022default\u0022]},{\u0022name\u0022:\u0022nokogiri\u0022,\u0022version\u0022:\u00221.15.0\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022default\u0022]},{\u0022name\u0022:\u0022pg\u0022,\u0022version\u0022:\u00221.5.4\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022default\u0022]},{\u0022name\u0022:\u0022puma\u0022,\u0022version\u0022:\u00226.4.0\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022default\u0022]},{\u0022name\u0022:\u0022racc\u0022,\u0022version\u0022:\u00221.7.1\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022default\u0022]},{\u0022name\u0022:\u0022rack\u0022,\u0022version\u0022:\u00223.0.8\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022default\u0022]}],\u0022entrypoints\u0022:[{\u0022path\u0022:\u0022app.rb\u0022,\u0022type\u0022:\u0022script\u0022,\u0022requiredGems\u0022:[\u0022nokogiri\u0022,\u0022pg\u0022,\u0022rack\u0022]},{\u0022path\u0022:\u0022config.ru\u0022,\u0022type\u0022:\u0022rack\u0022,\u0022requiredGems\u0022:[\u0022rack\u0022]}],\u0022dependencyEdges\u0022:[{\u0022from\u0022:\u0022pkg:gem/nokogiri@1.15.0\u0022,\u0022to\u0022:\u0022mini_portile2\u0022,\u0022constraint\u0022:\u0022~\\u003E 2.8.2\u0022},{\u0022from\u0022:\u0022pkg:gem/nokogiri@1.15.0\u0022,\u0022to\u0022:\u0022racc\u0022,\u0022constraint\u0022:\u0022~\\u003E 1.4\u0022},{\u0022from\u0022:\u0022pkg:gem/puma@6.4.0\u0022,\u0022to\u0022:\u0022nio4r\u0022,\u0022constraint\u0022:\u0022~\\u003E 2.0\u0022}],\u0022runtimeEdges\u0022:[{\u0022package\u0022:\u0022nokogiri\u0022,\u0022usedByEntrypoint\u0022:true,\u0022files\u0022:[\u0022app.rb\u0022],\u0022entrypoints\u0022:[\u0022app.rb\u0022],\u0022reasons\u0022:[\u0022require-static\u0022]},{\u0022package\u0022:\u0022pg\u0022,\u0022usedByEntrypoint\u0022:true,\u0022files\u0022:[\u0022app.rb\u0022],\u0022entrypoints\u0022:[\u0022app.rb\u0022],\u0022reasons\u0022:[\u0022require-static\u0022]},{\u0022package\u0022:\u0022rack\u0022,\u0022usedByEntrypoint\u0022:true,\u0022files\u0022:[\u0022app.rb\u0022,\u0022config.ru\u0022],\u0022entrypoints\u0022:[\u0022app.rb\u0022,\u0022config.ru\u0022],\u0022reasons\u0022:[\u0022require-static\u0022]}],\u0022configs\u0022:[{\u0022name\u0022:\u0022puma\u0022,\u0022type\u0022:\u0022web-server\u0022,\u0022filePath\u0022:\u0022config/puma.rb\u0022,\u0022settings\u0022:{\u0022preload_app\u0022:\u0022true\u0022}}],\u0022environment\u0022:{\u0022rubyVersion\u0022:\u00223.2.0\u0022,\u0022bundlerVersion\u0022:\u00222.4.22\u0022,\u0022lockfiles\u0022:[\u0022Gemfile.lock\u0022],\u0022rubyVersionSources\u0022:[{\u0022version\u0022:\u00223.2.0\u0022,\u0022source\u0022:\u0022.ruby-version\u0022,\u0022sourceType\u0022:\u0022ruby-version\u0022},{\u0022version\u0022:\u00223.2.0\u0022,\u0022source\u0022:\u0022.tool-versions\u0022,\u0022sourceType\u0022:\u0022tool-versions\u0022},{\u0022version\u0022:\u00223.2.0\u0022,\u0022source\u0022:\u0022Gemfile\u0022,\u0022sourceType\u0022:\u0022gemfile\u0022}],\u0022webServers\u0022:[{\u0022serverType\u0022:\u0022puma\u0022,\u0022configPath\u0022:\u0022config/puma.rb\u0022,\u0022settings\u0022:{\u0022preload_app\u0022:\u0022true\u0022}}]},\u0022capabilities\u0022:{\u0022usesExec\u0022:false,\u0022usesNetwork\u0022:false,\u0022usesSerialization\u0022:false,\u0022jobSchedulers\u0022:[]},\u0022bundledWith\u0022:\u00222.4.22\u0022}",
"sha256": "sha256:d5c7da885e1d05805981e2080c9023cd653ed464e993d5e48de6b9f55334eca7"
"value": "{\u0022$schema\u0022:\u0022stellaops.ruby.observation@1\u0022,\u0022packages\u0022:[{\u0022name\u0022:\u0022mini_portile2\u0022,\u0022version\u0022:\u00222.8.4\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022default\u0022]},{\u0022name\u0022:\u0022nio4r\u0022,\u0022version\u0022:\u00222.5.9\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022default\u0022]},{\u0022name\u0022:\u0022nokogiri\u0022,\u0022version\u0022:\u00221.15.0\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022default\u0022]},{\u0022name\u0022:\u0022pg\u0022,\u0022version\u0022:\u00221.5.4\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022default\u0022]},{\u0022name\u0022:\u0022puma\u0022,\u0022version\u0022:\u00226.4.0\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022default\u0022]},{\u0022name\u0022:\u0022racc\u0022,\u0022version\u0022:\u00221.7.1\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022default\u0022]},{\u0022name\u0022:\u0022rack\u0022,\u0022version\u0022:\u00223.0.8\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022default\u0022]}],\u0022entrypoints\u0022:[{\u0022path\u0022:\u0022app.rb\u0022,\u0022type\u0022:\u0022script\u0022,\u0022requiredGems\u0022:[\u0022nokogiri\u0022,\u0022pg\u0022,\u0022rack\u0022]},{\u0022path\u0022:\u0022config.ru\u0022,\u0022type\u0022:\u0022rack\u0022,\u0022requiredGems\u0022:[\u0022rack\u0022]}],\u0022dependencyEdges\u0022:[{\u0022from\u0022:\u0022pkg:gem/nokogiri@1.15.0\u0022,\u0022to\u0022:\u0022mini_portile2\u0022,\u0022constraint\u0022:\u0022~\\u003E 2.8.2\u0022},{\u0022from\u0022:\u0022pkg:gem/nokogiri@1.15.0\u0022,\u0022to\u0022:\u0022racc\u0022,\u0022constraint\u0022:\u0022~\\u003E 1.4\u0022},{\u0022from\u0022:\u0022pkg:gem/puma@6.4.0\u0022,\u0022to\u0022:\u0022nio4r\u0022,\u0022constraint\u0022:\u0022~\\u003E 2.0\u0022}],\u0022runtimeEdges\u0022:[{\u0022package\u0022:\u0022nokogiri\u0022,\u0022usedByEntrypoint\u0022:true,\u0022files\u0022:[\u0022app.rb\u0022],\u0022entrypoints\u0022:[\u0022app.rb\u0022],\u0022reasons\u0022:[\u0022require-static\u0022]},{\u0022package\u0022:\u0022pg\u0022,\u0022usedByEntrypoint\u0022:true,\u0022files\u0022:[\u0022app.rb\u0022],\u0022entrypoints\u0022:[\u0022app.rb\u0022],\u0022reasons\u0022:[\u0022require-static\u0022]},{\u0022package\u0022:\u0022rack\u0022,\u0022usedByEntrypoint\u0022:true,\u0022files\u0022:[\u0022app.rb\u0022,\u0022config.ru\u0022],\u0022entrypoints\u0022:[\u0022app.rb\u0022,\u0022config.ru\u0022],\u0022reasons\u0022:[\u0022require-static\u0022]}],\u0022configs\u0022:[{\u0022name\u0022:\u0022puma\u0022,\u0022type\u0022:\u0022web-server\u0022,\u0022filePath\u0022:\u0022config/puma.rb\u0022,\u0022settings\u0022:{\u0022preload_app\u0022:\u0022true\u0022}}],\u0022environment\u0022:{\u0022rubyVersion\u0022:\u00223.2.0\u0022,\u0022bundlerVersion\u0022:\u00222.4.22\u0022,\u0022lockfiles\u0022:[\u0022Gemfile.lock\u0022],\u0022rubyVersionSources\u0022:[{\u0022version\u0022:\u00223.2.0\u0022,\u0022source\u0022:\u0022.ruby-version\u0022,\u0022sourceType\u0022:\u0022ruby-version\u0022},{\u0022version\u0022:\u00223.2.0\u0022,\u0022source\u0022:\u0022.tool-versions\u0022,\u0022sourceType\u0022:\u0022tool-versions\u0022},{\u0022version\u0022:\u00223.2.0\u0022,\u0022source\u0022:\u0022Gemfile\u0022,\u0022sourceType\u0022:\u0022gemfile\u0022}],\u0022webServers\u0022:[{\u0022serverType\u0022:\u0022puma\u0022,\u0022configPath\u0022:\u0022config/puma.rb\u0022,\u0022settings\u0022:{\u0022preload_app\u0022:\u0022true\u0022}}],\u0022nativeExtensions\u0022:[{\u0022gemName\u0022:\u0022nokogiri\u0022,\u0022gemVersion\u0022:\u00221.15.0\u0022,\u0022extensionPath\u0022:\u0022layers/ruby/usr/local/lib/ruby/gems/3.2.0/gems/nokogiri-1.15.0/lib/nokogiri/nokogiri.so\u0022,\u0022extensionType\u0022:\u0022so\u0022},{\u0022gemName\u0022:\u0022pg\u0022,\u0022gemVersion\u0022:\u00221.5.4\u0022,\u0022extensionPath\u0022:\u0022layers/ruby/usr/local/lib/ruby/gems/3.2.0/gems/pg-1.5.4/lib/pg_ext.so\u0022,\u0022extensionType\u0022:\u0022so\u0022}]},\u0022capabilities\u0022:{\u0022usesExec\u0022:false,\u0022usesNetwork\u0022:false,\u0022usesSerialization\u0022:false,\u0022jobSchedulers\u0022:[]},\u0022bundledWith\u0022:\u00222.4.22\u0022}",
"sha256": "sha256:59ac6bb07675933964aae184f8f581dfd33a334126e96f16fd0b1943374897c2"
}
]
},

View File

@@ -1 +1,28 @@
[]
[
{
"analyzerId": "ruby",
"componentKey": "observation::ruby",
"name": "Ruby Observation Summary",
"type": "ruby-observation",
"usedByEntrypoint": false,
"metadata": {
"ruby.observation.capability.exec": "true",
"ruby.observation.capability.net": "true",
"ruby.observation.capability.schedulers": "0",
"ruby.observation.capability.serialization": "false",
"ruby.observation.dependency_edges": "0",
"ruby.observation.entrypoints": "1",
"ruby.observation.packages": "0",
"ruby.observation.runtime_edges": "0"
},
"evidence": [
{
"kind": "derived",
"source": "ruby.observation",
"locator": "document",
"value": "{\u0022$schema\u0022:\u0022stellaops.ruby.observation@1\u0022,\u0022packages\u0022:[],\u0022entrypoints\u0022:[{\u0022path\u0022:\u0022app.rb\u0022,\u0022type\u0022:\u0022script\u0022}],\u0022dependencyEdges\u0022:[],\u0022runtimeEdges\u0022:[],\u0022environment\u0022:{},\u0022capabilities\u0022:{\u0022usesExec\u0022:true,\u0022usesNetwork\u0022:true,\u0022usesSerialization\u0022:false,\u0022jobSchedulers\u0022:[]}}",
"sha256": "sha256:60173f2b841ca2b231eb293dd6ccc9d7ed39ea77a0e86ed887111f84861092ed"
}
]
}
]

View File

@@ -12,6 +12,7 @@
"ruby.observation.capability.schedulers": "0",
"ruby.observation.capability.serialization": "false",
"ruby.observation.dependency_edges": "13",
"ruby.observation.entrypoints": "3",
"ruby.observation.packages": "18",
"ruby.observation.ruby_version": "3.2.0",
"ruby.observation.runtime_edges": "3"

View File

@@ -13,6 +13,7 @@
"ruby.observation.capability.schedulers": "1",
"ruby.observation.capability.serialization": "true",
"ruby.observation.dependency_edges": "4",
"ruby.observation.entrypoints": "4",
"ruby.observation.packages": "11",
"ruby.observation.ruby_version": "3.1.2",
"ruby.observation.runtime_edges": "3"

View File

@@ -12,6 +12,7 @@
"ruby.observation.capability.schedulers": "0",
"ruby.observation.capability.serialization": "false",
"ruby.observation.dependency_edges": "12",
"ruby.observation.entrypoints": "2",
"ruby.observation.packages": "11",
"ruby.observation.runtime_edges": "2"
},

View File

@@ -14,12 +14,10 @@
<PackageReference Remove="xunit" />
<PackageReference Remove="xunit.runner.visualstudio" />
<PackageReference Remove="Microsoft.AspNetCore.Mvc.Testing" />
<PackageReference Remove="Mongo2Go" />
<PackageReference Remove="coverlet.collector" />
<PackageReference Remove="Microsoft.Extensions.TimeProvider.Testing" />
<ProjectReference Remove="..\StellaOps.Concelier.Testing\StellaOps.Concelier.Testing.csproj" />
<Compile Remove="$(MSBuildThisFileDirectory)..\StellaOps.Concelier.Tests.Shared\AssemblyInfo.cs" />
<Compile Remove="$(MSBuildThisFileDirectory)..\StellaOps.Concelier.Tests.Shared\MongoFixtureCollection.cs" />
<Compile Remove="$(MSBuildThisFileDirectory)..\..\..\..\tests\shared\OpenSslLegacyShim.cs" />
<Compile Remove="$(MSBuildThisFileDirectory)..\..\..\..\tests\shared\OpenSslAutoInit.cs" />
<Using Remove="StellaOps.Concelier.Testing" />

View File

@@ -138,15 +138,17 @@
"metadata": {
"deno.container.identifier": "vendor-<hash>",
"deno.container.kind": "vendor",
"deno.container.layerDigest": "deadbeef",
"deno.container.meta.alias": "vendor-<hash>",
"deno.container.meta.path": "<workspace>/vendor"
"deno.container.meta.path": "<workspace>/layers/sha256-deadbeef/fs/vendor"
},
"evidence": [
{
"kind": "metadata",
"source": "deno.container",
"locator": "Vendor",
"value": "vendor-<hash>"
"value": "vendor-<hash>",
"sha256": "deadbeef"
}
]
},
@@ -159,17 +161,15 @@
"metadata": {
"deno.container.identifier": "vendor-<hash>",
"deno.container.kind": "vendor",
"deno.container.layerDigest": "deadbeef",
"deno.container.meta.alias": "vendor-<hash>",
"deno.container.meta.path": "<workspace>/layers/sha256-deadbeef/fs/vendor"
"deno.container.meta.path": "<workspace>/vendor"
},
"evidence": [
{
"kind": "metadata",
"source": "deno.container",
"locator": "Vendor",
"value": "vendor-<hash>",
"sha256": "deadbeef"
"value": "vendor-<hash>"
}
]
},

Some files were not shown because too many files have changed in this diff Show More