@clear-capabilities/agentic-security-scanner 0.174.0 → 0.175.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.
@@ -1 +1 @@
1
- 238ea18c5a820dabb585208a014c0bbf8459796faeb0711f25852fb9a37a7c7c agentic-security.mjs
1
+ 9b99aab752e33a23af53d8562691f3e713d9e00c02bce5ce1d26edd1513479de agentic-security.mjs
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clear-capabilities/agentic-security-scanner",
3
- "version": "0.174.0",
3
+ "version": "0.175.0",
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",
@@ -92,8 +92,8 @@
92
92
  "test:lineage": "node --test 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/cli/runtime-twin-cli.test.js",
93
93
  "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",
94
94
  "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",
95
- "test:haskell": "node --test 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-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",
96
- "test:nix": "node --test 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",
95
+ "test:haskell": "node --test 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-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",
96
+ "test:nix": "node --test 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",
97
97
  "test:language": "node --test 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/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-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",
98
98
  "test:language-stress": "node --test --test-concurrency=1 test/language/language-stress-offline.test.js",
99
99
  "test:language-tools": "node --test test/haskell/haskell-web-auth.test.js test/haskell/haskell-support-gate.test.js",
@@ -46,7 +46,7 @@ export const BOUNDARIES = Object.freeze({
46
46
  unsafeCoerce: 'Reinterprets a value as another type without a check. Modelled as a boundary only.',
47
47
  });
48
48
 
49
- const WEAK_HASH_CALLS = new Set(['Crypto.Hash.MD5.hash', 'Crypto.Hash.MD5.hashlazy', 'Crypto.Hash.SHA1.hash', 'Crypto.Hash.SHA1.hashlazy', 'Data.Digest.Pure.MD5.md5', 'Data.Digest.Pure.SHA.sha1']);
49
+ const WEAK_HASH_CALLS = new Set(['Crypto.Hash.MD5.hash', 'Crypto.Hash.MD5.hashlazy', 'Crypto.Hash.SHA1.hash', 'Crypto.Hash.SHA1.hashlazy', 'Data.Digest.Pure.MD5.md5', 'Data.Digest.Pure.MD5.md5DigestBytes', 'Data.Digest.Pure.SHA.sha1', 'Data.Digest.Pure.SHA.sha1Incremental']);
50
50
  const WEAK_ALGO_CONS = new Set(['MD5', 'MD4', 'MD2', 'SHA1']);
51
51
  const ANY_HASH_CALLS = new Set([...WEAK_HASH_CALLS, 'Crypto.Hash.hash', 'Crypto.Hash.hashlazy', 'Crypto.Hash.hashWith', 'Crypto.Hash.SHA256.hash', 'Crypto.Hash.SHA256.hashlazy', 'Crypto.Hash.SHA512.hash', 'Crypto.Hash.SHA512.hashlazy', 'Data.Digest.Pure.SHA.sha256', 'Data.Digest.Pure.SHA.sha512']);
52
52
  const WEAK_CIPHER_MODULES = new Set(['Crypto.Cipher.DES', 'Crypto.Cipher.RC4', 'Crypto.Cipher.RC2', 'Crypto.Cipher.Blowfish']);
@@ -59,6 +59,12 @@ export const HARDENING_RULES = Object.freeze({
59
59
  'getty-autologin': { family: 'login-access', cwe: 'CWE-306', severity: 'high', ruleVersion: 1, vuln: 'The console logs a user in without a password', why: 'services.getty.autologinUser starts a session on every virtual console with no authentication. For root that is a root shell for anyone with the keyboard, a serial line or a hypervisor console.', fix: 'Remove services.getty.autologinUser. Keep autologin to throwaway virtual machines and tests.' },
60
60
  'display-autologin': { family: 'login-access', cwe: 'CWE-306', severity: 'low', ruleVersion: 1, vuln: 'The display manager logs a user in without a password', why: 'Automatic login skips authentication at the graphical login, so anyone who can power the machine on gets the session. Disk encryption is the only remaining barrier.', fix: 'Disable autoLogin, or keep it only where physical access is controlled and the disk is encrypted.' },
61
61
  'pam-null-password': { family: 'login-access', cwe: 'CWE-258', severity: 'high', ruleVersion: 1, vuln: 'A PAM service accepts accounts with an empty password', why: 'allowNullPassword lets an account with no password log in through this service with an empty answer, which turns every passwordless account into an open login.', fix: 'Remove allowNullPassword (it defaults to false) and give every account a password hash or a key.' },
62
+ 'nix-sandbox-off': { family: 'nix-daemon-trust', cwe: 'CWE-693', severity: 'medium', ruleVersion: 1, vuln: 'The Nix build sandbox is disabled', why: 'With the sandbox off, a build can read the host filesystem and the network, so a malicious or impure derivation can steal files and secrets or change its output depending on the host.', fix: 'Remove nix.settings.sandbox = false (the default is true). Fix the one derivation that needs impurity instead of weakening every build.' },
63
+ 'nix-signatures-off': { family: 'nix-daemon-trust', cwe: 'CWE-347', severity: 'high', ruleVersion: 1, vuln: 'Nix accepts unsigned store paths', why: 'require-sigs = false lets the daemon import any store path from a client or substituter without a trusted signature, so whoever can push a path can run code as every user of that path.', fix: 'Remove nix.settings.require-sigs = false and sign the paths you copy with a key listed in trusted-public-keys.' },
64
+ 'nix-trusted-users-wildcard': { family: 'nix-daemon-trust', cwe: 'CWE-269', severity: 'high', ruleVersion: 1, vuln: 'Every user is a Nix trusted user', why: 'A trusted user can import unsigned paths and set daemon options, which is root on the machine. Listing "*" or a broad group extends that to everyone who can reach the daemon.', fix: 'Keep nix.settings.trusted-users to root (and named administrators). Give other users nix.settings.allowed-users instead.' },
65
+ 'kernel-sysctl-weakened': { family: 'kernel-hardening', cwe: 'CWE-693', severity: 'medium', ruleVersion: 1, vuln: 'A kernel hardening sysctl is set to a weaker value', why: 'Turning off address space randomisation, kernel pointer hiding or the unprivileged BPF restriction removes a mitigation that makes local exploits harder.', fix: 'Remove the boot.kernel.sysctl entry or restore the hardened value (randomize_va_space = 2, kptr_restrict = 1 or 2, unprivileged_bpf_disabled = 1).' },
66
+ 'nfs-no-root-squash': { family: 'network-filesystem', cwe: 'CWE-269', severity: 'high', ruleVersion: 1, vuln: 'NFS export keeps remote root as root', why: 'no_root_squash maps a client\'s root user to root on the exported files, so anyone with root on a client can plant setuid binaries on the server.', fix: 'Drop no_root_squash (root_squash is the default) and restrict the client list to specific hosts.' },
67
+ 'docker-tcp-no-tls': { family: 'container-declaration', cwe: 'CWE-306', severity: 'high', ruleVersion: 1, vuln: 'The Docker daemon listens on an unauthenticated TCP socket', why: 'A tcp:// host without TLS client verification gives anyone who can reach the port root on the machine, since the Docker API starts privileged containers.', fix: 'Remove the tcp:// host, or enable TLS with --tlsverify and a CA, and bind only to a private address.' },
62
68
  });
63
69
 
64
70
  /** Which rules explain each privileged entry-point kind (reconciliation join). */
@@ -408,6 +414,37 @@ export function analyzeNixosHardening(opts, extra = {}) {
408
414
  if (j === 'yes' || j === 'conditional') addFinding('pam-null-password', { anchor: anchorOf(o), scope: o.path.replace(/\.allowNullPassword$/, ''), state: j === 'conditional' ? 'conditional' : 'active', uncertain: !view(o).definite, evidence: [evidenceOf(o)] });
409
415
  }
410
416
 
417
+ // ── nix daemon trust, kernel sysctls, network filesystems, docker socket ──
418
+ const sandboxRes = note(nixos('nix.settings.sandbox'), 'nix');
419
+ { const j = judge(sandboxRes, (x) => x === false); if (j === 'yes' || j === 'conditional') addFinding('nix-sandbox-off', { anchor: anchorOf(sandboxRes), scope: 'nix.settings.sandbox', state: j === 'conditional' ? 'conditional' : 'active', uncertain: !view(sandboxRes).definite, evidence: [evidenceOf(sandboxRes)] }); }
420
+ const sigsRes = note(nixos('nix.settings.require-sigs'), 'nix');
421
+ { const j = judge(sigsRes, (x) => x === false); if (j === 'yes' || j === 'conditional') addFinding('nix-signatures-off', { anchor: anchorOf(sigsRes), scope: 'nix.settings.require-sigs', state: j === 'conditional' ? 'conditional' : 'active', uncertain: !view(sigsRes).definite, evidence: [evidenceOf(sigsRes)] }); }
422
+ const trustedRes = note(nixos('nix.settings.trusted-users'), 'nix');
423
+ { const v = view(trustedRes);
424
+ const items = v.state === 'known' ? asList(v.value) : v.state === 'conditional' ? asList(v.definiteItems) : [];
425
+ if (items.some((u) => u === '*' || u === '@users' || u === '@everyone')) addFinding('nix-trusted-users-wildcard', { anchor: anchorOf(trustedRes), scope: 'nix.settings.trusted-users', state: v.state === 'conditional' ? 'conditional' : 'active', uncertain: !v.definite, evidence: [evidenceOf(trustedRes)] }); }
426
+ const SYSCTL_WEAK = new Map([['kernel.randomize_va_space', (x) => String(x) === '0'], ['kernel.kptr_restrict', (x) => String(x) === '0'], ['kernel.unprivileged_bpf_disabled', (x) => String(x) === '0'], ['kernel.dmesg_restrict', (x) => String(x) === '0'], ['kernel.yama.ptrace_scope', (x) => String(x) === '0'], ['fs.protected_symlinks', (x) => String(x) === '0'], ['fs.protected_hardlinks', (x) => String(x) === '0']]);
427
+ for (const o of cfg.options.filter((x) => x.namespace === 'nixos' && /^boot\.kernel\.sysctl\./.test(x.path))) {
428
+ const key = o.path.slice('boot.kernel.sysctl.'.length).replace(/^"|"$/g, '');
429
+ const weak = SYSCTL_WEAK.get(key);
430
+ if (!weak) continue;
431
+ const j = judge(note(o, 'kernel'), weak);
432
+ if (j === 'yes' || j === 'conditional') addFinding('kernel-sysctl-weakened', { anchor: anchorOf(o), scope: `boot.kernel.sysctl.${key}`, state: j === 'conditional' ? 'conditional' : 'active', uncertain: !view(o).definite, evidence: [evidenceOf(o)], extra: { sysctl: key } });
433
+ }
434
+ const nfsRes = note(nixos('services.nfs.server.exports'), 'nfs');
435
+ { const j = judge(nfsRes, (x) => typeof x === 'string' && /(^|[\s,(])no_root_squash(?=[\s,)]|$)/.test(x.split('\n').filter((l) => !/^\s*#/.test(l)).join('\n')));
436
+ if (j === 'yes' || j === 'conditional') addFinding('nfs-no-root-squash', { anchor: anchorOf(nfsRes), scope: 'services.nfs.server.exports', state: j === 'conditional' ? 'conditional' : 'active', uncertain: !view(nfsRes).definite, evidence: [evidenceOf(nfsRes)] }); }
437
+ { const dockerOn = judge(nixos('virtualisation.docker.enable'), (x) => x === true);
438
+ if (dockerOn === 'yes' || dockerOn === 'conditional') {
439
+ const optRes = nixos('virtualisation.docker.extraOptions');
440
+ const hostsRes = nixos('virtualisation.docker.daemon.settings.hosts');
441
+ const tlsOn = ['tls', 'tlsverify'].some((k) => view(nixos(`virtualisation.docker.daemon.settings.${k}`)).value === true);
442
+ const j1 = judge(optRes, (x) => typeof x === 'string' && /(?:-H|--host)[ =]+tcp:\/\//.test(x) && !/--tlsverify|--tls\b/.test(x));
443
+ const j2 = tlsOn ? 'no' : judge(hostsRes, (x) => Array.isArray(x) && x.some((h) => typeof h === 'string' && /^tcp:\/\//.test(h)));
444
+ const j = j1 === 'yes' ? 'yes' : j2 === 'yes' ? 'yes' : j1 === 'conditional' || j2 === 'conditional' ? 'conditional' : 'no';
445
+ if (j === 'yes' || j === 'conditional') { const res = j1 !== 'no' ? optRes : hostsRes; addFinding('docker-tcp-no-tls', { anchor: anchorOf(res), scope: 'virtualisation.docker', state: j === 'conditional' || dockerOn === 'conditional' ? 'conditional' : 'active', uncertain: false, evidence: [evidenceOf(res)] }); }
446
+ } }
447
+
411
448
  // ── databases / listeners ──
412
449
  if (hasDefs('services.postgresql.')) {
413
450
  const enableRes = nixos('services.postgresql.enable');