@clear-capabilities/agentic-security-scanner 0.195.16 → 0.195.17

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.
@@ -1 +1 @@
1
- 10035213d4490ba0c69e465083a4322a6cb5e7717ea62b3e9eaabd048b468ac3 agentic-security.mjs
1
+ bee6929f3252e721fd4e14c484fa7197f57d1a077957f5e1f3c14fe1e7de06ed agentic-security.mjs
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clear-capabilities/agentic-security-scanner",
3
- "version": "0.195.16",
3
+ "version": "0.195.17",
4
4
  "description": "Scanner engine for the agentic-security Claude Code plugin: SAST, SCA with reachability and CISA KEV, secrets, IaC, Haskell and Nix/NixOS, prompt-injection, MCP/agent-tool audit, authZ analysis, SBOM, compliance attestation, and more.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clear-capabilities/agentic-security-scanner",
3
- "version": "0.195.16",
3
+ "version": "0.195.17",
4
4
  "description": "Scanner engine for the agentic-security Claude Code plugin: SAST, SCA with reachability and CISA KEV, secrets, IaC, Haskell and Nix/NixOS, prompt-injection, MCP/agent-tool audit, authZ analysis, SBOM, compliance attestation, and more.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -66,6 +66,7 @@ or a capability.** Product-facing guides: `docs/guides/haskell.md`, `docs/guides
66
66
  - **Guards are recognised by what they do.** `haskell-guards.js`: a project function is an HTML escaper when it (or its local helpers) spells out the entities for `<`, `>` and `&` (`htmlEscapers`; never by name), and it neutralises a CWE-79 flow only when EVERY use of the value in the sink argument is under such a call (`allUsesEscaped`); a host allow-list may be a named constant and may sit in an `&&`; `sh -c SCRIPT arg0 arg1` is shell injection only when the SCRIPT is built from the value, and tainted positional parameters are data when the constant script expands them only inside double quotes after `--` (`quotedPositionals`, which decodes the Haskell string escapes the IR keeps raw).
67
67
  - **Handlers named by the framework's convention are routes.** `haskell-web.js` adds `getFooR`/`postFooR`/`handleFooR` functions whose signature mentions `Handler` in a module importing Yesod when no route table in an analysed file names them (the table usually lives in `config/routes`). Scotty's rejection order is judged by `inlineOrder` as well as by line: a `case` lists the success branch first, so `status 40x >> finish` in the LAST alternative guards it (a bare `status` with no halting call does not).
68
68
  - **A project definition of a modelled name keeps the registry's meaning (since 0.195.11).** `haskell-ir.js` `applyVar`/`lowerVarValue`: a resolution to a project `def` whose module-qualified name `isKnownApi` (the project IS the framework, or vendors it) is lowered as the LIBRARY call (`hs.status: 'external'`, `hs.modelledDefinition: qid`) and the definition is still referenced (`noteRef(..., 'modelled-definition')`, so it is never an uncalled entry point). Before this, IHP's own `IHP.Controller.Param.param` shadowed the catalog when IHP scanned itself, and a request parameter reaching `Process.system` through a helper was not reported (four lost true positives on the fresh set). A test stub named like a catalog module gets the same treatment, which is why the html-text remediation test keeps its scotty stub out of the scan. Pinned both ways in `test/haskell/haskell-taint-shapes.test.js`, with the three shapes below.
69
+ - **Graph and SCA items examined and NOT built (0.195.17; each premise of the plan turned out false or a contract change).** (1) CWE-78/88/94 sink nodes stay `unsupported` in `lineage/sink-registry.js`: `SINK_CATEGORIES` is FR-201's EGRESS taxonomy, process and code execution are in-process computation (82 of 194 sink entries, `lineage/DESIGN_REGISTRIES.md` section 9), and a new category is a signed cross-language contract change touching the schema, 50 references and the frontend parity fixture, not a Haskell improvement. (2) `findingRefs` is empty for every language, and a sink NODE has `location: null` by design (category-granular); a finding could attach to a FLOW through the sites of its path group, but the Haskell rule findings are produced after the graph is built, so it needs a post-pass over a signed artifact and flows that retain their sites. (3) `haskell-sca.js` `PACKAGE_MODULES` stays a nine-package table: a cabal `plan.json` carries units and dependencies, not module names; module lists live in the package database of the user's cabal store, outside the project, and this layer reads only explicitly supplied exact paths. Deriving them would need an opt-in package-database path. (4) `docs/capability-ledger.json` entries are all `proposed` by rule (`scripts/capability-ledger/check.mjs` fails any other status for an applicable entry): implemented status is tracked by the support registry, so CAP-009 and CAP-112 are not moved.
69
70
  - **Fixers for the real true positives (0.195.16; the count in the older "Ten fixers" bullet is now fifteen: `hs-path-contain` and `hs-xml-no-entity-substitution` are new).** `hs-process-argv` reads a `Text` command (`system (cs t)` / `(T.unpack t)` of a `<>` concatenation, on the line or in a one-line `let` of the same function, which is removed; `concatParts` splits on `<>` when no `++` is present) and keeps the unpack per argument; `spawnCommand` is recognised; `CLASS_REASONS['CWE-78']` (new) names the decision when the program is a variable (IHP's editor from an environment variable stays refused: `code --goto` is not one program name). `hs-path-contain` (`fixPathContain`, `signatureAt`): a guard equation before the function's first equation, `| ".." \`elem\` splitDirectories p || isAbsolute p = pure Nothing`, only for exactly one String/FilePath parameter and a `Maybe` result (bare or under one monad); the shape is the one `haskell-guards.js` already classifies as a traversal check, so the rescan passes on semantics; effective kind `path-contain` (one added equation, the guard present). `hs-tls-validate` gains `fixOpenSslVerify`: `contextSetVerificationMode ctx VerifyNone` as a do-block statement becomes `contextSetDefaultVerifyPaths ctx` + `(VerifyPeer True True Nothing)` (HsOpenSSL 0.11.7.11), MITIGATION because the host name is not checked (`enableHostnameValidation` needs the connection object); effective kind `tls-openssl`. `hs-log-redact` no longer rewrites inside a string literal (the guide's captured example had shown `" "[REDACTED]"="`). `hs-xml-no-entity-substitution` (`fixXmlEntities`): `withSubstDTDEntities yes|True` to `no|False`, FULL, effective kind `hxt-entities`. `hs-pbkdf2-low-iterations` findings are routed to `hs-password-kdf-migrate` (the old count becomes the legacy verifier). Tests: `haskell-remediation-text-command.test.js`, `haskell-remediation-path-contain.test.js`, `haskell-remediation-0195-16.test.js`.
70
71
  - **`hs-random-os-entropy` v2 (since 0.195.16; `test/haskell/haskell-remediation-entropy.test.js`).** `haskell-fix.js` `fixRandomLine`: three shapes from the real-code true positives, `replicateM n (randomRIO (c1, c2))` (n bytes mapped into the character range), `randomRIO (lo, hi)` with integer literals (8 bytes decoded big-endian, reduced into the range), `(take n . map f . randomRs (lo, hi)) <$> newStdGen` (n bytes reduced and mapped). The reduction is a modulus (bias stated in the consequences). `System.Entropy` is imported when missing, so the patch is a MITIGATION (the operator declares `entropy`) and FULL only when the file already imported it; the same relaxation applies to the older `name = randomIO` shape. The unused System.Random names are dropped from the import. Effective-check kind `random-line` (the edited line, located by content after the import removal may have moved it, reads `getEntropy n` and calls no System.Random name). Verified on the three real sites (scotty, gitit, hackage-server) with `entropy` installed for GHC 9.4.8 on the measuring host.
71
72
  - **Rule precision from semantics (since 0.195.15; `test/haskell/haskell-rule-precision.test.js`).** `haskell-security-rules.js`: a partial list function (`NONEMPTY_TOTAL`) on a list that is a cons, a non-empty literal, or a concatenation with one of those (`nonEmptyList`) is total and not reported; the conditions a function tests include the first argument of `when`/`unless`/`guard` (`COND_COMBINATOR`), not only `if` nodes; a comparison of a list's LENGTH with a number (`case length xs of 3`, `length xs == 2`; `LENGTH_CALL`, `lengthVars` for the scratch variable the IR puts a case scrutinee in) tests the list. `hash x :: Digest MD5` / `hashlazy x :: Digest SHA1`: `haskell-syntax.js` keeps a `Digest <algo>` annotation as a `typed` node (`DIGEST_TYPE`), `haskell-ir.js` returns the inner call stamped `hs.digest`, and `hs-weak-hash` treats it like `hashWith MD5`. `hs-unsafe-io-boundary` is already lowered to `info` for every site by `engine.js` (`originalSeverity` kept), so no shape rule was added for it.
@@ -1321,8 +1321,9 @@ Limits: value versions come from per-function reaching definitions; mutation thr
1321
1321
  `haskell-view.js` rewrites a clone of each Haskell function so a monadic source read becomes a seedable member read on the
1322
1322
  synthetic root `$hs`, seeds record-parameter sources, and treats `length`/`null` as non-content. `nix-view.js` builds
1323
1323
  field-to-store flows from `language/nix-privacy.js` (a personal-data `config.<module>.<field>` interpolated into another option;
1324
- hashed, measured or compared references are protected and make no flow). Coverage reports `unknown` for both languages: no
1325
- taint-recall tier is borrowed. See `scanner/src/language/CLAUDE.md`.
1324
+ hashed, measured or compared references are protected and make no flow). Coverage reports tier `partial` for both languages (since 0.195.17; it was `unknown`), with NO recall number: the
1325
+ only measured Haskell figure is the nine self-authored entries of `docs/METRICS.md`, and showing it as "100% measured recall" in the impact
1326
+ report would read as a real-code measurement. No taint-recall tier is borrowed. See `scanner/src/language/CLAUDE.md`.
1326
1327
 
1327
1328
  ## Runtime twin additions (PRD 2 gap closure)
1328
1329
 
@@ -38,6 +38,14 @@ export const LANGUAGE_COVERAGE_TIERS = Object.freeze([
38
38
  Object.freeze({ language: 'php', tier: 'partial', irTaintRecallPct: 52, measuredAt: '2026-08-19', source: 'docs/METRICS.md' }),
39
39
  Object.freeze({ language: 'kotlin', tier: 'partial', irTaintRecallPct: 48, measuredAt: '2026-08-19', source: 'docs/METRICS.md' }),
40
40
  Object.freeze({ language: 'cpp', tier: 'partial', irTaintRecallPct: 18, measuredAt: '2026-08-19', source: 'docs/METRICS.md' }),
41
+ // Haskell and Nix are wired into the lineage graph (haskell-view.js seeds request/record sources through the taint engine; nix-view.js
42
+ // builds field-to-store flows from the NixOS option analysis), so they are not "unknown". `partial` is the honest categorical claim: some
43
+ // flows are seen, the stated limits apply (docs/guides/haskell.md), and neither reaches `full`. NO recall number is attached, on purpose:
44
+ // docs/METRICS.md's Haskell row (9 of 9 by IR-TAINT) is a sample of nine self-authored entries, and shown as "100% measured recall" in the
45
+ // impact report it would read as a measurement of real code, which it is not (the real-code figure is precision on a fresh set, 21 of 51
46
+ // flagged sites true, bench/dag/dev-haskell). Nix has no taint recall at all: it is configuration reasoning, not a flow engine.
47
+ Object.freeze({ language: 'haskell', tier: 'partial', irTaintRecallPct: null, measuredAt: null, source: null }),
48
+ Object.freeze({ language: 'nix', tier: 'partial', irTaintRecallPct: null, measuredAt: null, source: null }),
41
49
  Object.freeze({ language: 'rust', tier: 'pattern-only', irTaintRecallPct: null, measuredAt: null, source: null }),
42
50
  Object.freeze({ language: 'solidity', tier: 'pattern-only', irTaintRecallPct: null, measuredAt: null, source: null }),
43
51
  Object.freeze({ language: 'swift', tier: 'pattern-only', irTaintRecallPct: null, measuredAt: null, source: null }),
@@ -118,7 +118,8 @@ export const CWE_MAP = Object.freeze({
118
118
  'CWE-918': Object.freeze({ category: 'external-api', status: 'modeled', why: 'SSRF sinks are outbound HTTP client calls' }),
119
119
  // X-004 / haskell-models.js: untrusted text placed in a model request BODY. The call is an ordinary outbound HTTP
120
120
  // body setter, so it is an external-api destination; whether the destination is a model provider is decided by
121
- // the AI-evidence overlay (lineage/haskell-ai.js), never by this table.
121
+ // the technical provider catalog in recipient-registry.js (the destination host, e.g. an anthropic.com or openai.com
122
+ // base URL), never by this table.
122
123
  'CWE-1427': Object.freeze({ category: 'external-api', status: 'modeled', why: 'a model request body is an outbound HTTP request body' }),
123
124
  'CWE-601': Object.freeze({ category: 'http-response', status: 'modeled', why: 'a redirect is written as a response header' }),
124
125
  'CWE-113': Object.freeze({ category: 'http-response', status: 'modeled', why: 'response splitting — the sink IS the response header writer' }),