@clear-capabilities/agentic-security-scanner 0.157.1 → 0.158.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/dist/3531.index.js +1 -1
- package/dist/4171.index.js +2 -2
- package/dist/8551.index.js +19 -4
- package/dist/agentic-security.mjs +1 -1
- package/dist/agentic-security.mjs.sha256 +1 -1
- package/package.json +5 -5
- package/src/dataflow/engine.js +23 -0
- package/src/engine.js +1 -0
- package/src/language/CLAUDE.md +6 -1
- package/src/language/haskell-advisory-feed.js +179 -0
- package/src/language/haskell-models.js +8 -2
- package/src/language/haskell-sca.js +18 -4
- package/src/language/haskell-security-rules.js +1 -1
- package/src/language/haskell-supply.js +28 -0
- package/src/language/haskell-web.js +29 -1
- package/src/language/nix-sca.js +9 -3
- package/src/language/nixos-hardening.js +37 -2
- package/src/language/nixos-module-resolver.js +26 -1
- package/src/language/nixos-option-catalog.js +1 -0
- package/src/language/resolved-pass.js +9 -1
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,30 @@
|
|
|
9
9
|
> make the history less accurate, not more.
|
|
10
10
|
|
|
11
11
|
|
|
12
|
+
## 0.158.0 - live Hackage advisories (opt in), and the detection gaps the second unseen set found
|
|
13
|
+
|
|
14
|
+
Two things, both in the Haskell and Nix/NixOS work.
|
|
15
|
+
|
|
16
|
+
**A live Hackage advisory feed.** Until now Haskell dependency scanning matched correctly but only against a snapshot you supplied. Set `AGENTIC_SECURITY_HACKAGE_ADVISORIES_LIVE=1` and a scan looks up the packages it is about to evaluate in the OSV `Hackage` ecosystem, keeping the result in the operator configuration directory (mode 0600), never the scanned project.
|
|
17
|
+
- **Off by default.** It needs the network, `AGENTIC_SECURITY_OFFLINE=1` / `--no-network` always wins, and a snapshot you name with `AGENTIC_SECURITY_HACKAGE_ADVISORIES` is used as given and never refreshed over.
|
|
18
|
+
- **It records what it covered.** A package it could not fully look up is reported `feed-incomplete` (unknown), never as having no advisories, and coverage older than the age limit reads `feed-stale`. This holds for the Nix matcher too: a Haskell package wrapped in a Nix closure that the feed did not cover is now `unknown`. Before this release that path would have said "no advisory in the supplied feed matches this version" for a package the feed never looked at; the closure's Haskell package names are now also fed to the lookup.
|
|
19
|
+
- **Failure degrades.** An unreachable feed keeps the previous snapshot and states its age; with none the scan is `partial` and says why.
|
|
20
|
+
- **Untrusted input is bounded.** Package names and advisory ids are validated before they reach a URL, a record that is not the one asked for is dropped, responses are size-bounded, and a cache whose records no longer match their hashes is discarded.
|
|
21
|
+
- It has been tested against a stand-in server that serves the real pinned HSEC records. It has not been measured against the live service on real projects.
|
|
22
|
+
|
|
23
|
+
**Detection gaps, fixed as general capabilities** (none of the fixtures used to prove them is a corpus case; each fix is pinned in both directions):
|
|
24
|
+
- `Network.Wreq.getWith opts url` and the other `*With`, custom-method and `Session` forms were checked at the options argument, so the URL was never examined and tainted options were treated as SSRF. The URL position is now modelled per function (the sink list grew from 8 to 32 entries).
|
|
25
|
+
- A credential check written inside a route handler (reads `Authorization`, rejects with a 401 and stops the handler, before the first sensitive operation) is recognised as a guard. A check that does not stop the handler, reads a non-credential header, or runs after the write is still reported.
|
|
26
|
+
- One flaw reached through nested sinks (`readCreateProcess (shell cmd)`) is reported once; the outer sink is recorded on the finding. Independent sinks on one line stay separate.
|
|
27
|
+
- Reading a request body whole with no size limit is CWE-770, like the sibling unbounded-read rules (it was CWE-400).
|
|
28
|
+
- NixOS `allowedTCPPortRanges` / `allowedUDPPortRanges` are evaluated: a range that opens every port is a high finding (`firewall-wide-port-range`), ten thousand or more ports medium, a sensitive port inside a smaller range is still caught, an interface-scoped range is not global, and a range that cannot be evaluated is disclosed instead of read as closed. A service port inside a global range is reported open in the firewall. The option evaluator now reads a list or attribute set when every element is statically known.
|
|
29
|
+
|
|
30
|
+
This entry makes no claim about how many requirements are verified, or about precision on the fixes above: the unseen set they came from is consumed, so a new number needs a fresh one.
|
|
31
|
+
|
|
32
|
+
## 0.157.2 - the npm package description names Haskell and Nix/NixOS
|
|
33
|
+
|
|
34
|
+
npm cuts a package description at 255 characters, and in 0.157.1 the Haskell and Nix/NixOS sentence sat past the cut, so the registry never showed it. The description is now 234 characters with both languages inside the limit. No scanner behaviour changed.
|
|
35
|
+
|
|
12
36
|
## 0.157.1 - `ci` summary adds up; Haskell and Nix/NixOS shown in the language table, the npm README and the manifests
|
|
13
37
|
|
|
14
38
|
One scanner fix, otherwise documentation and metadata.
|
package/dist/3531.index.js
CHANGED
|
@@ -14,7 +14,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
14
14
|
/* harmony export */ purlFromGitHost: () => (/* binding */ purlFromGitHost),
|
|
15
15
|
/* harmony export */ purlFromUpstreamUrl: () => (/* binding */ purlFromUpstreamUrl)
|
|
16
16
|
/* harmony export */ });
|
|
17
|
-
/* harmony import */ var _haskell_supply_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(
|
|
17
|
+
/* harmony import */ var _haskell_supply_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(43436);
|
|
18
18
|
/* harmony import */ var _haskell_sca_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(2437);
|
|
19
19
|
/* harmony import */ var _haskell_resolved_graph_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(86359);
|
|
20
20
|
/* harmony import */ var _nix_inventory_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(81303);
|
package/dist/4171.index.js
CHANGED
|
@@ -20,8 +20,8 @@ var resolved_pass = __webpack_require__(38551);
|
|
|
20
20
|
var nix_build_trust = __webpack_require__(36668);
|
|
21
21
|
// EXTERNAL MODULE: ./src/language/discovery.js
|
|
22
22
|
var discovery = __webpack_require__(30951);
|
|
23
|
-
// EXTERNAL MODULE: ./src/language/haskell-supply.js
|
|
24
|
-
var haskell_supply = __webpack_require__(
|
|
23
|
+
// EXTERNAL MODULE: ./src/language/haskell-supply.js + 1 modules
|
|
24
|
+
var haskell_supply = __webpack_require__(43436);
|
|
25
25
|
// EXTERNAL MODULE: ./src/language/nix-script-taint.js
|
|
26
26
|
var nix_script_taint = __webpack_require__(11793);
|
|
27
27
|
// EXTERNAL MODULE: ./src/language/nix-secrets.js
|
package/dist/8551.index.js
CHANGED
|
@@ -10,6 +10,7 @@ export const modules = {
|
|
|
10
10
|
__webpack_require__.d(__webpack_exports__, {
|
|
11
11
|
analyzeNixClosure: () => (/* binding */ analyzeNixClosure),
|
|
12
12
|
nixClosureOf: () => (/* binding */ nixClosureOf),
|
|
13
|
+
nixHaskellPackageNames: () => (/* binding */ nixHaskellPackageNames),
|
|
13
14
|
resolvedHackageComponents: () => (/* binding */ resolvedHackageComponents),
|
|
14
15
|
resolvedHaskellGraph: () => (/* binding */ resolvedHaskellGraph),
|
|
15
16
|
runSelectedNixEval: () => (/* binding */ runSelectedNixEval)
|
|
@@ -647,9 +648,14 @@ function matchNixVulnerabilities(opts = {}) {
|
|
|
647
648
|
if (!data) { push({ status: 'unknown', reason: 'no advisory feed was supplied: absence of a match is not a clean result' }); summary.unmapped++; continue; }
|
|
648
649
|
if (ident.versionRejected && !ident.haskell && !(node.upstream && node.upstream.rev)) { push({ status: 'unknown', reason: `the version cannot be queried: ${ident.versionRejected}` }); summary.unmapped++; continue; }
|
|
649
650
|
|
|
650
|
-
let matched = false; let mapped = false;
|
|
651
|
+
let matched = false; let mapped = false; let hackageGap = null;
|
|
651
652
|
// Hackage (wrapped Haskell packages): reuse the PVP matcher
|
|
652
|
-
if (ident.haskell && data.hackage && ident.version && (0,haskell_manifests/* parseVersion */.ot)(ident.version)) {
|
|
653
|
+
if (ident.haskell && data.hackage && ident.version && (0,haskell_manifests/* parseVersion */.ot)(ident.version) && data.hackage.coverage(node.pname) !== 'covered') {
|
|
654
|
+
// A feed that never looked up this package (or looked too long ago) says nothing about it: that is unknown, not "no advisory matches".
|
|
655
|
+
hackageGap = data.hackage.coverage(node.pname) === 'stale'
|
|
656
|
+
? 'the Hackage advisory feed last covered this package longer ago than its age limit: the absence of a match is not a clean result'
|
|
657
|
+
: 'the Hackage advisory feed never covered this package: the absence of a match is not a clean result';
|
|
658
|
+
} else if (ident.haskell && data.hackage && ident.version && (0,haskell_manifests/* parseVersion */.ot)(ident.version)) {
|
|
653
659
|
mapped = true;
|
|
654
660
|
for (const { adv, aff } of data.hackage.forPackage(node.pname)) {
|
|
655
661
|
if (adv.withdrawn) continue;
|
|
@@ -683,6 +689,7 @@ function matchNixVulnerabilities(opts = {}) {
|
|
|
683
689
|
}
|
|
684
690
|
}
|
|
685
691
|
if (matched) summary.matched++;
|
|
692
|
+
else if (hackageGap) { summary.unmapped++; push({ status: 'unknown', reason: hackageGap }, { identityMapped: false, feedCoverage: 'incomplete' }); }
|
|
686
693
|
else if (mapped) { summary.mappedNoMatch++; push({ status: 'not-affected', reason: 'mapped to an upstream identity and no advisory in the supplied feed matches this version' }, { identityMapped: true }); }
|
|
687
694
|
else { summary.unmapped++; push({ status: 'unknown', reason: ident.ambiguous ? 'the upstream identity is ambiguous: no verdict' : 'the upstream identity could not be mapped to anything an advisory is keyed by' }, { identityMapped: false }); }
|
|
688
695
|
}
|
|
@@ -724,8 +731,8 @@ function licenseReport(nodes, meta, policy) {
|
|
|
724
731
|
|
|
725
732
|
|
|
726
733
|
|
|
727
|
-
// EXTERNAL MODULE: ./src/language/haskell-supply.js
|
|
728
|
-
var haskell_supply = __webpack_require__(
|
|
734
|
+
// EXTERNAL MODULE: ./src/language/haskell-supply.js + 1 modules
|
|
735
|
+
var haskell_supply = __webpack_require__(43436);
|
|
729
736
|
// EXTERNAL MODULE: external "node:child_process"
|
|
730
737
|
var external_node_child_process_ = __webpack_require__(31421);
|
|
731
738
|
// EXTERNAL MODULE: external "node:net"
|
|
@@ -1120,6 +1127,14 @@ function loadNixExports(files, env = process.env) {
|
|
|
1120
1127
|
return { exports, sources, problems, expected, trust: [...trust] };
|
|
1121
1128
|
}
|
|
1122
1129
|
|
|
1130
|
+
/** Names of the Haskell packages inside an imported Nix closure (derivations whose env carries Haskell build attributes). */
|
|
1131
|
+
function nixHaskellPackageNames(files, opts = {}) {
|
|
1132
|
+
const c = nixClosureOf(files, opts);
|
|
1133
|
+
if (!c || (c.refused && c.refused.length)) return [];
|
|
1134
|
+
const nodes = (c.closure && c.closure.nodes) || [];
|
|
1135
|
+
return [...new Set(nodes.filter((n) => n.pname && n.deriver && c.drvEnv[n.deriver] && HASKELL_ENV_HINTS.some((k) => k in c.drvEnv[n.deriver])).map((n) => n.pname))];
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1123
1138
|
/** The imported closure (and each derivation's env block) for a file set, or null when there is no export. Cached per file set. */
|
|
1124
1139
|
function nixClosureOf(files, { now = Date.now() } = {}) {
|
|
1125
1140
|
return memoized(files, 'nix-closure', () => {
|