@clear-capabilities/agentic-security-scanner 0.195.9 → 0.195.11

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 CHANGED
@@ -9,6 +9,32 @@
9
9
  > make the history less accurate, not more.
10
10
 
11
11
 
12
+ ## 0.195.11 - Haskell taint: the project that is the framework, mutable cells, instance dispatch, record-held functions, request text into helpers; a scotty body-limit fixer; Haskell comments blanked for the generic detectors
13
+
14
+ **Measured, 0.195.10:** `TIMINGS preflight=3s push=6s pr=2s checks=338s merge=5s verify=6s tag=2s release=223s npm=237s total=822s release-clock=236s`, shipped in 24 min 47 s. The release clock (merge to published release) was 236 s; the run was long because the push stopped three times before the merge, each a host or capture artefact, never the engine (a transient `npm pack` retry, a warm gate run under GHC 9.4.8's package environment, a captured example whose compiler message lacked the error code the hosted GHC prints). The 0.195.9 line was not kept.
15
+
16
+ Why: the fresh real-code set recorded four true positives the engine had lost, all in IHP's own development tools (a request parameter reaching `Process.system` through a helper, two request-indexed `!!`), and the cause was one mechanism: when a framework repository scans itself, its own definition of a modelled request accessor shadowed the registry's model. The rest of the taint work came from two self-written probes of Haskell data-carrying idioms run against the engine (57 shapes; the 26 intra-module idioms of the first probe were all followed already, the second found four gaps). Nothing was read from a frozen split; the shapes are pinned both ways in `scanner/test/haskell/haskell-taint-shapes.test.js`.
17
+
18
+ - **A project definition of a modelled name keeps the registry's meaning** (`haskell-ir.js` `applyVar`, `lowerVarValue`): a resolution to a project `def` whose module-qualified name the registry models is lowered as the library call, and the definition is still referenced (never an uncalled entry point). On the real IHP clone this was not enough: the IHP preludes re-export `module CorePrelude` (not in the tree) before `module IHP.Controller.Param`, and `exportedLookup` returned the first possibility, an uncertain external name, so every `param` was `CorePrelude.param`. A wildcard re-export of a module the project does not contain is now the FALLBACK of the export-list search, and in the caller's resolution such a possibility goes to the wildcard list rather than competing with a definite hit (before that second change, `openEditor`, imported by name from a project module, read as ambiguous).
19
+ - **Mutable cells, instance dispatch, functions in record fields.** `writeIORef ref v` / `putMVar` / `writeTVar` (under `atomically` or not) / `modifyIORef ref f` assign a local cell and `readIORef` / `takeMVar` / `readMVar` / `readTVarIO` read it, also `STRef` (`CELL_OPS`; a cell shared across functions is not followed, disclosed in the guide). A class method applied to a constructor of a type with exactly one visible instance resolves to that instance's method; instance functions are named `Mod.$inst.Class.Type.method` so two instances resolve to different functions by name (the dataflow engine resolves callees by name; one test pin changed for it). `exec ops x`, with `ops` a top-level constant record literal, applies the held lambda or named function. The cell and STM names are in `HS_RULE_APIS` so wildcard imports qualify them.
20
+ - **Request text handed to a project helper is caller text in the helper** (`haskell-security-rules.js` `requestTextParams`: a pre-pass over the resolved call edges, partial applications and the edges of inlined helpers, which now carry their lowered arguments; one hop per round, four rounds). `list !! tableId` with a caller-supplied INDEX is reported like a caller-supplied list, a relational test on the index covers it, and the partial-function emits carry `inlineCtx` so an inlined call-site copy is dropped when the helper reports it. On the fresh set this restores the two Enums sites and finds a third request-indexed `!!` in the schema designer, labelled a true positive on sight.
21
+ - **`hs-scotty-body-limit` (CWE-770), a MITIGATION.** scotty's `body` has no per-read bound; the library's bound is `setMaxRequestBodySize :: Kilobytes -> ScottyM ()` (scotty 0.12 and later, a 413 before any handler runs), so the fixer adds it as the first statement of the ScottyM block that contains the read or names the handler, with `agenticRequestBodyLimitKb = 1024` (a declared default) and a qualified import; refused when the block is in another file, when a cabal bound excludes every version with the call (`parseCabalFile`, `versionSatisfies`), or when a name is taken. The rule learned the same thing: a limit in the same function or in a ScottyM function that references the handler is certain (`controls` kind `scotty-body-limit`, verified), a limit elsewhere keeps the finding at low with the reach stated as unverified. The effective check requires exactly one added call at the planned line and unchanged body reads; the test type-checks the patch under GHC against a declared stub of the scotty API. One real example (`basic.hs`) was blocked at first because the import was placed after an `import` inside a commented-out block; the fixer now reads the last import of the comment-stripped code.
22
+ - **The language-neutral detectors see Haskell comments blanked** (`engine.js` `_commentLangFor`, `_blankCached`, via `haskell-comments.js`). Under the C-family view, `crypto.createHash('md5')` and a JWT verify with `none` quoted in a Haskell doc comment were reported as calls (two findings and a cipher entry on a comment-only file); pinned both ways in `haskell-generic-detectors.test.js`, with the JavaScript code still firing.
23
+
24
+ **Measured on real code** (`bench/dag/dev-haskell`, re-recorded with `bench:haskell-precision:update`; every new site was read and labelled after the scan found it, `LABEL-CHANGES.md`, and none fed back into a rule): the fresh set flags 31 labelled sites, 21 true positives (67.7%, Wilson 50.1% to 81.4%) against 26 and 17 (65.4%); true positives not flagged 4 to 1 (the multi-sink chain at the restored command-injection line). The development censuses grew where the preludes now resolve (xmonad-contrib 1 to 5 flagged, hackage-server 1 to 2, IHP 20 to 25): the five development additions are false positives by the sets' policy (paths from the user's own configuration, the operator's machine, the server's own temporary file; one `last` of a list with a literal prepended) and are labelled so. The remediation ledger over the labelled true positives: 8 of 26 to **11 of 27** (`fix-real.mjs`): the three scotty `body` reads verify; the restored command injection builds a `Text` command for `Process.system`, which the argv rewrite refuses by class. The sealed independent set, measured once and logged: 77 flagged (73), 1 true positive of 35 decided, 2.9%, unchanged. The frozen unseen-v6 split, measured once and logged: identical to 0.195.9, F1 72.5%, taint families 66.7%; the shapes above are not the ones it exercises, and that is the recorded reading. The support registry was re-measured under GHC 9.4.8: Haskell 9/9, Nix 8/10 (the two `nix`-blocked rows), numbers unchanged.
25
+
26
+ Gates, from the runs recorded for this entry: `test:haskell` 450/450 (two new suites, `haskell-taint-shapes` and `haskell-remediation-scotty-body`, wired into the script and checked for a glued name; two tests added to `haskell-generic-detectors`), `test:nix` 305/305, `test:language` 257/257, `test:lineage` 1554/1554, `bench:mutation:check` 460/460, `bench:layer-recall:check` unchanged, `bench:language-support:check` current, `bench:haskell-precision:check` PASS after the deliberate re-record, generated language docs current.
27
+
28
+ ## 0.195.10 - Haskell remediation measured on real code: a password-hash migration fixer, an HTML-from-text fixer, and the registry's GHC check
29
+
30
+ Why: the fix preview was run over every labelled real-code true positive of `bench/dag/dev-haskell` (26 sites in gitit, scotty, dhall, hackage-server and IHP; `fix-real.mjs <project> --all`) and 3 of them got a verified patch (one cookie, two wai request bodies). The rest were refusals: five scotty `html $ mconcat ["<h1>", v, "</h1>"]` sites, which the blaze rewrite cannot touch; three scotty `body` reads; two dhall path operations spread over several functions; three generators in files that import no entropy package; an OpenSSL `VerifyNone`; an open redirect; two partial functions; and gitit's `hashPassword salt pass = showDigest $ sha512 $ ...`, refused by the CWE-916 class reason. Two of those classes had a sound rewrite that was never written. After this release 8 of the 26 sites get a verified patch, on the same 47 flagged sites (nothing moved in detection).
31
+
32
+ - **`hs-password-kdf-migrate` (CWE-916), a MITIGATION that is the migration story the old refusal asked for.** The class reason was right that a swap locks every user out, so the fixer does not swap: a pure one-line `name salt password = ...` of one text type (`String`, `Text` or strict `ByteString`) is kept verbatim as `nameLegacy` for verification only; `name` becomes PBKDF2-HMAC-SHA256 (600000 iterations, 32 bytes, over the same salt and password; a declared default, named in the plan) with a tag in front of the hex so a stored hash says how it was made; `nameNeedsRehash` reads the tag; `nameVerify stored salt password` checks either form; every `stored == name salt password` in the file calls it; an export list gains the helpers. Only in a file that already imports a cryptonite or crypton module (`Crypto.KDF.PBKDF2` is then in a declared package) and `Data.Text` or `Data.ByteString`; refused, naming the decision, when the function takes no salt (a pure function cannot add one), when the types differ, when a name is taken, and when ANOTHER file compares with the function, because that comparison would be left checking the old form against the new one. The rule it relies on is semantic: `haskell-security-rules.js` `legacyVerifierOnly` reports a fast password hash at `info` when every use of its function, in every file, is an operand of an equality and one of those functions also computes a password KDF within one project hop; a function that also stores a hash, or compares beside no KDF, keeps the full finding (pinned both ways, from the IR, never from the name). `defaultRescan` now drops a finding the engine lowered to `info` for a stated reason (`originalSeverity`, or this legacy-verifier evidence) as it drops proven-clean ones; a finding that is `info` in its own right is still counted, so fixing it can still be shown to remove it. The API is cryptonite's from its documentation; the tests type-check the patch against a declared stub of that shape and RUN the patched program under GHC: a legacy hash verifies, a new hash verifies, a wrong password does not, the tag decides re-hashing. gitit's real site stays refused on purpose: it imports the `SHA` package, which has no KDF, and the decision (which library) is the operator's.
33
+ - **`hs-html-escape-text` (CWE-79), a MITIGATION for scotty's `html` of concatenated text.** `html $ mconcat ["<h1>", v, "</h1>"]` becomes `html $ mconcat ["<h1>", agenticEscapeHtml v, "</h1>"]`, and `html (f v)` with no literal in the argument becomes `html (agenticEscapeHtml (f v))`, with the escaper (the five characters that open markup or end a quoted attribute) defined in the file; the rescan recognises it by the entities it spells out (`htmlEscapers`), not by its name, so the finding goes quiet for the right reason and the effective check reads the application count back. Stated limits: HTML text and quoted attribute values only (a value inside a script, a style, a URL or an unquoted attribute is not made safe), markup the value carried is now text. Only with `Web.Scotty` and `Data.Text.Lazy` imported; a nested construction, a renderer in the argument, a literal alone and another framework's sink are refused. The escaper is run under GHC on hostile input in the test. On scotty's own examples this verifies all five labelled XSS sites.
34
+ - **The registry's GHC check.** `bench/language-support/suites.mjs` `probeTools` records the GHC version on PATH and whether it is the recorded toolchain (`docs/language-toolchain.json`, 9.4.8); `promote.mjs` refuses to measure the suites under another GHC (`--allow-ghc-mismatch` records the mismatch in the registry's limits instead); the `auth` compile criterion's failure message says "unavailable compiler" when a module is missing under a mismatched GHC, so the row is recorded as blocked, not as an engine failure; `judgeGhc` is pinned both ways in `test/language/language-package-ci.test.js`. Found while doing it: GHC 9.4.8's package environment file (written by `cabal install --lib`) hides `bytestring`, so under the recorded GHC every stub-compiling remediation test reported its compile gate as not-run or failed; those four files now run their compiles with `GHC_ENVIRONMENT=-` (boot packages only; the auth suite keeps the environment it needs). The remediation suites pass under both GHCs on the maintainer's machine for the first time.
35
+
36
+ Gates, from the runs recorded for this entry: `test:haskell` 438/438 (two new suites, `haskell-remediation-kdf` and `haskell-remediation-html-text`, both wired into the script and checked for a glued name), `test:nix` 305/305, `test:language` 257/257, `bench:haskell-precision:check` PASS (the labelled real-code rows unchanged), `bench:language-support:check`, mutation and layer-recall unchanged. The registry's capability suites were measured again under GHC 9.4.8 (the holdout result reused, not re-run) so the stored tools record carries the version; every Haskell row stays supported (9 of 9), Nix 8 of 10 with `nix-eval` and `nixos-host` still blocked on a host with no `nix`, and the registry's limits now name the GHC that was present (9.4.8, the recorded toolchain).
37
+
12
38
  ## 0.195.9 - Haskell real-code precision, a blind unseen-v6 and the first blind Nix split, the unseen-v5 and Nix development shapes
13
39
 
14
40
  Four pieces of work, in the order they were done, each measured where it could be measured. Every number below comes from a run recorded in this repository on 2026-11-01.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clear-capabilities/agentic-security-scanner",
3
- "version": "0.195.9",
3
+ "version": "0.195.11",
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",
@@ -40,7 +40,7 @@
40
40
  "node": ">=24.0.0"
41
41
  },
42
42
  "dependencies": {
43
- "@babel/core": "^8.0.10",
43
+ "@babel/core": "^8.1.0",
44
44
  "@babel/preset-react": "^8.0.1",
45
45
  "@babel/preset-typescript": "^8.0.1",
46
46
  "js-yaml": "^5.5.0",
@@ -93,7 +93,7 @@
93
93
  "test:lineage": "node --test test/lineage/analysis-completion.test.js test/lineage/schema.test.js test/lineage/ids.test.js test/lineage/protection.test.js test/lineage/classification.test.js test/lineage/validate.test.js test/lineage/json-schema-parity.test.js test/lineage/flagship-fixture.test.js test/lineage/flagship-fixture-semantics.test.js test/lineage/field-identity.test.js test/lineage/engine-expr-resolver.test.js test/lineage/engine-walker.test.js test/lineage/engine-integration.test.js test/lineage/engine-receiver-identity.test.js test/lineage/summaries.test.js test/lineage/driver.test.js test/lineage/engine-provenance.test.js test/lineage/engine-provenance-interprocedural.test.js test/lineage/path-store.test.js test/lineage/path-query.test.js test/lineage/flow-grade.test.js test/lineage/source-registry.test.js test/lineage/sink-registry.test.js test/lineage/transform-catalog.test.js test/lineage/handling-analyzer.test.js test/lineage/registry-real-code.test.js test/lineage/source-seeding.test.js test/lineage/graph-builder.test.js test/lineage/coverage.test.js test/lineage/resolve-destination.test.js test/lineage/queue-detail.test.js test/lineage/index.test.js test/lineage/obligation-mapping.test.js test/lineage/obligation-predicates.test.js test/lineage/obligation-predicates-walkthrough.test.js test/lineage/transit-protection.test.js test/lineage/at-rest-protection.test.js test/lineage/protection-summary.test.js test/lineage/ac01-multi-sink.test.js test/lineage/policy-verdict.test.js test/lineage/governance-refs.test.js test/lineage/export-privacy.test.js test/lineage/export-json.test.js test/lineage/export-csv.test.js test/lineage/decision-story.test.js test/lineage/export-briefing.test.js test/lineage/graph-snapshot.test.js test/lineage/graph-diff.test.js test/lineage/drift-policy.test.js test/lineage/recipient-profile.test.js test/lineage/recipient-registry.test.js test/lineage/recipient-wiring.test.js test/lineage/redact-graph.test.js test/lineage-scan-wiring.test.js test/lineage-artifact-write.test.js test/bench-data-lineage-runner.test.js test/bundle-frontend.test.js test/bundle-frontend-golden.test.js test/generate-html-report.test.js test/export-image.test.js test/lineage/scenario.test.js test/lineage/scenario-engine.test.js test/lineage/scenario-diff.test.js test/lineage/scenario-no-obligation-wiring.test.js test/lineage/impact-assessment.test.js test/lineage/impact-engine.test.js test/lineage/impact-no-obligation-wiring.test.js test/lineage/governance-edit.test.js test/lineage/remediation.test.js test/lineage/runtime-observation.test.js test/lineage/observation-correlation.test.js test/lineage/observation-adapters.test.js test/lineage/observation-store.test.js test/lineage/runtime-corroboration-wiring.test.js test/lineage/cross-repo-link.test.js test/lineage/federation-loader.test.js test/lineage/cross-repo-link-wiring.test.js test/lineage/runtime-assurance.test.js test/lineage/runtime-edge-states.test.js test/lineage/runtime-explorer-view.test.js test/cli/runtime-explorer-cli.test.js test/cli/runtime-identity-cli.test.js test/cli/runtime-twin-cli.test.js test/lineage/cut-planner.test.js test/lineage/scc.test.js test/lineage/scc-solver.test.js test/graph/graph-views.test.js test/lineage/dag07-completion.test.js test/lineage/owned-build.test.js test/dataflow/boundary-propagation.test.js test/lineage/boundary-contract.test.js test/lineage/correlation-v2.test.js test/lineage/path-control-evidence.test.js test/lineage/smt-z3-backend.test.js test/cli/dag-commands.test.js",
94
94
  "test:bench-modules": "node --test test/phase4-harness.test.js test/pipeline.test.js test/proof-corpus-lib.test.js test/proof-corpus-runner.test.js",
95
95
  "test:server": "node --test test/server/graph-loader.test.js test/server/security.test.js test/server/routes.test.js test/server/http-server.test.js test/server/cmd-explore.test.js test/server/cmd-dataflow-export.test.js test/server/static-assets.test.js test/server/dataflow-scenario-cli.test.js test/server/dataflow-impact-cli.test.js",
96
- "test:haskell": "node --test test/haskell/haskell-reachability.test.js test/haskell/haskell-real-code-shapes.test.js test/haskell/haskell-unseen-v5-shapes.test.js test/haskell/haskell-unseen-v4-shapes.test.js test/haskell/haskell-alias-taint.test.js test/haskell/haskell-weak-hash-names.test.js test/haskell/haskell-models-extended.test.js test/haskell/haskell-rules-extended.test.js test/haskell/haskell-injection.test.js test/haskell/haskell-ir.test.js test/haskell/haskell-manifests.test.js test/haskell/haskell-parser.test.js test/haskell/haskell-remediation.test.js test/haskell/haskell-remediation-0190.test.js test/haskell/haskell-resolved-graph.test.js test/haskell/haskell-sca.test.js test/haskell/haskell-sca-severity.test.js test/haskell/haskell-web-guards.test.js test/haskell/haskell-advisory-feed.test.js test/haskell/haskell-security-rules.test.js test/haskell/haskell-taint.test.js test/haskell/haskell-boundaries.test.js test/haskell/haskell-live-feed-scan.test.js test/haskell/haskell-supply-bounds.test.js test/haskell/haskell-ir-scaling.test.js test/haskell/haskell-boundary-propagation.test.js test/haskell/haskell-generalization.test.js test/haskell/haskell-trust-boundary.test.js test/haskell/haskell-comment-invariance.test.js test/haskell/haskell-remediation-classes.test.js test/haskell/haskell-trust-boundary-frameworks.test.js test/haskell/haskell-generic-detectors.test.js",
96
+ "test:haskell": "node --test test/haskell/haskell-reachability.test.js test/haskell/haskell-real-code-shapes.test.js test/haskell/haskell-unseen-v5-shapes.test.js test/haskell/haskell-unseen-v4-shapes.test.js test/haskell/haskell-alias-taint.test.js test/haskell/haskell-weak-hash-names.test.js test/haskell/haskell-models-extended.test.js test/haskell/haskell-rules-extended.test.js test/haskell/haskell-injection.test.js test/haskell/haskell-ir.test.js test/haskell/haskell-manifests.test.js test/haskell/haskell-parser.test.js test/haskell/haskell-remediation.test.js test/haskell/haskell-remediation-0190.test.js test/haskell/haskell-remediation-kdf.test.js test/haskell/haskell-remediation-html-text.test.js test/haskell/haskell-remediation-scotty-body.test.js test/haskell/haskell-taint-shapes.test.js test/haskell/haskell-resolved-graph.test.js test/haskell/haskell-sca.test.js test/haskell/haskell-sca-severity.test.js test/haskell/haskell-web-guards.test.js test/haskell/haskell-advisory-feed.test.js test/haskell/haskell-security-rules.test.js test/haskell/haskell-taint.test.js test/haskell/haskell-boundaries.test.js test/haskell/haskell-live-feed-scan.test.js test/haskell/haskell-supply-bounds.test.js test/haskell/haskell-ir-scaling.test.js test/haskell/haskell-boundary-propagation.test.js test/haskell/haskell-generalization.test.js test/haskell/haskell-trust-boundary.test.js test/haskell/haskell-comment-invariance.test.js test/haskell/haskell-remediation-classes.test.js test/haskell/haskell-trust-boundary-frameworks.test.js test/haskell/haskell-generic-detectors.test.js",
97
97
  "test:nix": "node --test test/nix/nix-option-types.test.js test/nix/nix-development-shapes.test.js test/nix/nixos-hardening-daemon.test.js test/nix/nixos-hardening-extended.test.js test/nix/nix-build-trust.test.js test/nix/nix-eval-isolation.test.js test/nix/nix-input-inventory.test.js test/nix/nix-parser-ir.test.js test/nix/nix-remediation.test.js test/nix/nix-resolved-closure.test.js test/nix/nix-sca-patches.test.js test/nix/nix-sca-severity.test.js test/nix/nix-haskell-closure-names.test.js test/nix/nix-script-taint.test.js test/nix/nix-secrets-store.test.js test/nix/nixos-hardening.test.js test/nix/nixos-module-resolution.test.js test/nix/nix-fix-multifile.test.js test/nix/nixos-evaluator-forms.test.js test/nix/nix-advisory-feed.test.js test/nix/nix-fix-prewrite.test.js test/nix/nix-boundary-scope.test.js",
98
98
  "test:language": "node --test test/bench-haskell-precision.test.js test/bench-independent-haskell-panel.test.js test/language/capability-ledger.test.js test/language/language-accuracy-regression.test.js test/language/language-aibom.test.js test/language/language-assurance.test.js test/language/language-bom.test.js test/language/language-compliance.test.js test/language/language-contracts.test.js test/language/language-corpora-integrity.test.js test/language/language-discovery.test.js test/language/language-doc-coverage.test.js test/language/language-evidence-posture.test.js test/language/language-explorer.test.js test/language/frozen-splits.test.js test/language/language-gap-fixes.test.js test/language/language-gap-fixes-2.test.js test/language/language-governance.test.js test/language/language-integrations.test.js test/language/language-llm-agent.test.js test/language/language-local-ai.test.js test/language/language-parity-haskell.test.js test/language/language-polyglot-bridges.test.js test/language/language-privacy-lineage.test.js test/language/language-proof-remediation.test.js test/language/language-report-formats.test.js test/language/language-secrets.test.js test/language/language-trust-boundary.test.js",
99
99
  "test:language-stress": "node --test --test-concurrency=1 test/language/language-stress-offline.test.js",