praxis-sec 1.0.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/LICENSE +21 -0
- package/README.md +170 -0
- package/ai-defense/cost-protection.md +292 -0
- package/ai-defense/llm-security-checklist.md +324 -0
- package/ai-defense/prompt-injection-patterns.js +283 -0
- package/ai-defense/system-prompt-armor.md +327 -0
- package/checklists/launch-day.md +168 -0
- package/cli/agents/abom-generator.js +225 -0
- package/cli/agents/agent-attestation-agent.js +318 -0
- package/cli/agents/agent-config-scanner.js +787 -0
- package/cli/agents/agent-telemetry-agent.js +415 -0
- package/cli/agents/agentic-security-agent.js +296 -0
- package/cli/agents/agentic-supply-chain-agent.js +463 -0
- package/cli/agents/ai-infra-inventory-agent.js +449 -0
- package/cli/agents/api-fuzzer.js +345 -0
- package/cli/agents/auth-bypass-agent.js +348 -0
- package/cli/agents/base-agent.js +280 -0
- package/cli/agents/cicd-scanner.js +300 -0
- package/cli/agents/config-auditor.js +757 -0
- package/cli/agents/deep-analyzer.js +776 -0
- package/cli/agents/endpoint-agent-abuse-agent.js +404 -0
- package/cli/agents/exception-handler-agent.js +187 -0
- package/cli/agents/git-history-scanner.js +169 -0
- package/cli/agents/governance-audits.js +138 -0
- package/cli/agents/hermes-security-agent.js +536 -0
- package/cli/agents/html-reporter.js +1125 -0
- package/cli/agents/index.js +147 -0
- package/cli/agents/injection-tester.js +502 -0
- package/cli/agents/legal-risk-agent.js +328 -0
- package/cli/agents/llm-redteam.js +199 -0
- package/cli/agents/managed-agent-scanner.js +333 -0
- package/cli/agents/mcp-security-agent.js +588 -0
- package/cli/agents/memory-poisoning-agent.js +305 -0
- package/cli/agents/mobile-scanner.js +231 -0
- package/cli/agents/model-file-scanner.js +259 -0
- package/cli/agents/orchestrator.js +355 -0
- package/cli/agents/pii-compliance-agent.js +301 -0
- package/cli/agents/policy-engine.js +229 -0
- package/cli/agents/prompt-injection-prober.js +224 -0
- package/cli/agents/rag-security-agent.js +204 -0
- package/cli/agents/recon-agent.js +207 -0
- package/cli/agents/sbom-generator.js +265 -0
- package/cli/agents/scoring-engine.js +273 -0
- package/cli/agents/ssrf-prober.js +130 -0
- package/cli/agents/stateful-watcher.js +238 -0
- package/cli/agents/supabase-rls-agent.js +154 -0
- package/cli/agents/supply-chain-agent.js +857 -0
- package/cli/agents/swarm-orchestrator.js +200 -0
- package/cli/agents/verifier-agent.js +303 -0
- package/cli/agents/vibe-coding-agent.js +250 -0
- package/cli/bin/praxis.js +866 -0
- package/cli/commands/abom.js +73 -0
- package/cli/commands/agent-fix.js +1245 -0
- package/cli/commands/audit.js +1180 -0
- package/cli/commands/autofix.js +383 -0
- package/cli/commands/baseline.js +193 -0
- package/cli/commands/benchmark.js +327 -0
- package/cli/commands/checklist.js +223 -0
- package/cli/commands/ci.js +403 -0
- package/cli/commands/deps.js +516 -0
- package/cli/commands/diff.js +200 -0
- package/cli/commands/doctor.js +195 -0
- package/cli/commands/env-audit.js +349 -0
- package/cli/commands/fix.js +218 -0
- package/cli/commands/guard.js +396 -0
- package/cli/commands/hooks.js +278 -0
- package/cli/commands/init.js +514 -0
- package/cli/commands/legal.js +158 -0
- package/cli/commands/live-advisories.js +241 -0
- package/cli/commands/mcp.js +660 -0
- package/cli/commands/openclaw.js +386 -0
- package/cli/commands/red-team.js +350 -0
- package/cli/commands/redteam.js +78 -0
- package/cli/commands/remediate.js +797 -0
- package/cli/commands/rotate.js +768 -0
- package/cli/commands/rules.js +196 -0
- package/cli/commands/scan-mcp.js +534 -0
- package/cli/commands/scan-skill.js +588 -0
- package/cli/commands/scan-standard.js +251 -0
- package/cli/commands/scan.js +524 -0
- package/cli/commands/score.js +449 -0
- package/cli/commands/shell.js +514 -0
- package/cli/commands/team-report.js +398 -0
- package/cli/commands/undo.js +161 -0
- package/cli/commands/update-intel.js +126 -0
- package/cli/commands/vibe-check.js +276 -0
- package/cli/commands/watch.js +757 -0
- package/cli/commands/web.js +63 -0
- package/cli/core/ast/guardrail-detector.js +141 -0
- package/cli/core/ast/index.js +22 -0
- package/cli/core/ast/parser.js +676 -0
- package/cli/core/ast/scope-tree.js +287 -0
- package/cli/core/ast/taint-tracker.js +158 -0
- package/cli/core/branding.js +37 -0
- package/cli/core/env.js +38 -0
- package/cli/core/errors.js +61 -0
- package/cli/core/fs.js +62 -0
- package/cli/core/output/compliance.js +90 -0
- package/cli/core/output/html-theme.js +158 -0
- package/cli/core/output/index.js +57 -0
- package/cli/core/output/json.js +48 -0
- package/cli/core/output/sarif.js +240 -0
- package/cli/core/version.js +67 -0
- package/cli/core/web/jobs.js +183 -0
- package/cli/core/web/projects.js +146 -0
- package/cli/core/web/server.js +439 -0
- package/cli/data/atlas-knowledge.json +5640 -0
- package/cli/data/eaa-catalog.json +39 -0
- package/cli/data/known-mcps.json +26 -0
- package/cli/data/probes/prompt-injection-corpus.json +271 -0
- package/cli/data/threat-intel.json +85 -0
- package/cli/data/threatpacks/latest.json +41 -0
- package/cli/hooks/patterns.js +313 -0
- package/cli/hooks/post-tool-use.js +140 -0
- package/cli/hooks/pre-tool-use.js +186 -0
- package/cli/index.js +90 -0
- package/cli/providers/llm-provider.js +766 -0
- package/cli/utils/autofix-rules.js +74 -0
- package/cli/utils/cache-manager.js +310 -0
- package/cli/utils/compliance-map.js +191 -0
- package/cli/utils/entropy.js +132 -0
- package/cli/utils/fix-ledger.js +127 -0
- package/cli/utils/hermes-tool-registry.js +252 -0
- package/cli/utils/intel/cache.js +61 -0
- package/cli/utils/intel/http.js +88 -0
- package/cli/utils/intel/index.js +235 -0
- package/cli/utils/intel/merge.js +229 -0
- package/cli/utils/intel/sources/epss.js +54 -0
- package/cli/utils/intel/sources/ghsa.js +81 -0
- package/cli/utils/intel/sources/gitguardian.js +40 -0
- package/cli/utils/intel/sources/gitleaks.js +101 -0
- package/cli/utils/intel/sources/kev.js +38 -0
- package/cli/utils/intel/sources/nvd.js +84 -0
- package/cli/utils/intel/sources/osv.js +132 -0
- package/cli/utils/intel/sources/phylum.js +44 -0
- package/cli/utils/intel/sources/snyk.js +46 -0
- package/cli/utils/intel/sources/socket.js +69 -0
- package/cli/utils/intel/sources/sonatype.js +84 -0
- package/cli/utils/intel/sources/threatpack.js +69 -0
- package/cli/utils/mcp-trust.js +60 -0
- package/cli/utils/output.js +251 -0
- package/cli/utils/patterns.js +1130 -0
- package/cli/utils/pdf-generator.js +94 -0
- package/cli/utils/plugin-loader.js +364 -0
- package/cli/utils/rule-import.js +228 -0
- package/cli/utils/rule-registry.js +426 -0
- package/cli/utils/scan-fingerprint.js +109 -0
- package/cli/utils/scan-playbook.js +312 -0
- package/cli/utils/score-history.js +119 -0
- package/cli/utils/secrets-verifier.js +247 -0
- package/cli/utils/security-memory.js +296 -0
- package/cli/utils/standards/atlas-knowledge.js +87 -0
- package/cli/utils/standards/index.js +127 -0
- package/cli/utils/standards/sources/avid.js +45 -0
- package/cli/utils/standards/sources/eu-ai-act.js +89 -0
- package/cli/utils/standards/sources/google-saif.js +39 -0
- package/cli/utils/standards/sources/iso-42001.js +94 -0
- package/cli/utils/standards/sources/mitre-atlas.js +54 -0
- package/cli/utils/standards/sources/nist-ai-600-1.js +45 -0
- package/cli/utils/standards/sources/owasp-llm.js +45 -0
- package/cli/utils/standards/sources/owasp-ml.js +45 -0
- package/cli/utils/threat-intel.js +265 -0
- package/configs/firebase/firestore-rules.txt +215 -0
- package/configs/firebase/security-checklist.md +236 -0
- package/configs/firebase/storage-rules.txt +206 -0
- package/configs/gitignore-template +258 -0
- package/configs/nextjs-security-headers.js +220 -0
- package/configs/praxisignore-template +50 -0
- package/configs/supabase/secure-client.ts +225 -0
- package/configs/supabase/security-checklist.md +278 -0
- package/docs/THIRD_PARTY_NOTICES.md +26 -0
- package/docs/THREAT_INTEL.md +292 -0
- package/docs/USAGE.md +1205 -0
- package/docs/design/WEB-UI.md +82 -0
- package/package.json +71 -0
- package/scripts/check-determinism.mjs +119 -0
- package/snippets/README.md +122 -0
- package/snippets/api-security/api-security-checklist.md +412 -0
- package/snippets/api-security/cors-config.ts +322 -0
- package/snippets/api-security/input-validation.ts +430 -0
- package/snippets/auth/jwt-checklist.md +322 -0
- package/snippets/rate-limiting/nextjs-middleware.ts +211 -0
- package/snippets/rate-limiting/upstash-ratelimit.ts +229 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ISO/IEC 42001:2023 — AI Management System (AIMS).
|
|
3
|
+
* https://www.iso.org/standard/81230.html
|
|
4
|
+
*
|
|
5
|
+
* Control titles and structure follow the public Annex A outline; descriptions
|
|
6
|
+
* are paraphrased by Praxis (ISO text is copyrighted — not reproduced).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export const name = 'iso-42001';
|
|
10
|
+
export const version = '2023';
|
|
11
|
+
export const title = 'ISO/IEC 42001 (AI Management System)';
|
|
12
|
+
export const description = 'International standard for establishing, implementing, and maintaining an AI management system.';
|
|
13
|
+
export const url = 'https://www.iso.org/standard/81230.html';
|
|
14
|
+
|
|
15
|
+
export const controls = [
|
|
16
|
+
// A.5 — Policies related to AI
|
|
17
|
+
{ id: 'A.5.2', title: 'AI policy', description: 'Documented policy providing direction and support for AI development and use.' },
|
|
18
|
+
{ id: 'A.5.5', title: 'AI policy review', description: 'Policy reviewed for continued suitability and updated when AI usage changes.' },
|
|
19
|
+
// A.6 — Internal organization
|
|
20
|
+
{ id: 'A.6.1.2', title: 'AI roles and responsibilities', description: 'Roles, responsibilities, and authorities for AI-related work defined and assigned.' },
|
|
21
|
+
{ id: 'A.6.2.4', title: 'AI system impact assessment', description: 'Assess potential impacts of AI systems on individuals, groups of individuals, and societies.' },
|
|
22
|
+
{ id: 'A.6.2.6', title: 'AI system design — privacy & security', description: 'Privacy and security embedded in AI system design through the lifecycle.' },
|
|
23
|
+
// A.7 — Resources for AI systems
|
|
24
|
+
{ id: 'A.7.2', title: 'Resources', description: 'Resources needed across the AI system lifecycle are determined and provided.' },
|
|
25
|
+
{ id: 'A.7.4', title: 'Quality of data for AI systems', description: 'Data quality and integrity requirements defined for training, validation, and inference data.' },
|
|
26
|
+
{ id: 'A.7.6', title: 'AI system documentation', description: 'Documentation of the AI system and its lifecycle phases maintained and traceable.' },
|
|
27
|
+
// A.8 — Assessment of AI system impact
|
|
28
|
+
{ id: 'A.8.2', title: 'Resources for AI systems', description: 'AI compute, models, and tooling assets managed as organizational resources.' },
|
|
29
|
+
{ id: 'A.8.4', title: 'Tooling for AI systems', description: 'Tools used in AI development, validation, and operation are evaluated and controlled.' },
|
|
30
|
+
// A.9 — AI system lifecycle
|
|
31
|
+
{ id: 'A.9.2', title: 'Information for users of AI systems', description: 'Intended use, limitations, and risks communicated to users and interested parties.' },
|
|
32
|
+
{ id: 'A.9.3', title: 'AI system design and development', detectable: false, description: 'Lifecycle objectives, processes, and acceptance criteria defined and followed.' },
|
|
33
|
+
{ id: 'A.9.4', title: 'AI system verification and validation', description: 'Verification and validation against acceptance criteria before deployment.' },
|
|
34
|
+
{ id: 'A.9.5', title: 'AI system operation and monitoring', description: 'Deployed AI systems operated per policy and monitored for drift, misuse, and incidents.' },
|
|
35
|
+
// A.10 — Data for AI systems
|
|
36
|
+
{ id: 'A.10.2', title: 'Allocation of responsibilities', description: 'Responsibilities for AI systems distributed among suppliers, customers, and partners.' },
|
|
37
|
+
{ id: 'A.10.3', title: 'Suppliers', description: 'AI-relevant supplier relationships governed by defined requirements and controls.' },
|
|
38
|
+
{ id: 'A.10.4', title: 'Data provenance and preparation', description: 'Origin, transformation, and preparation of AI data is recorded and controlled.' },
|
|
39
|
+
// A.11 — Information for interested parties
|
|
40
|
+
{ id: 'A.11.2', title: 'Reporting concerns', detectable: false, description: 'Concerns about AI system impacts can be raised, reviewed, and resolved.' },
|
|
41
|
+
{ id: 'A.11.5', title: 'Communication of incidents', description: 'AI incidents communicated to relevant interested parties, including authorities where required.' },
|
|
42
|
+
// A.12 — Use of AI systems
|
|
43
|
+
{ id: 'A.12.4', title: 'Human oversight of AI use', description: 'Appropriate human oversight applied to AI use to limit unintended consequences.' },
|
|
44
|
+
// A.13 — Third-party and customer relationships
|
|
45
|
+
{ id: 'A.13.2', title: 'Third-party AI supply', description: 'Third-party AI components and services assessed before adoption and monitored thereafter.' },
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
export function mapFinding(finding) {
|
|
49
|
+
const ids = new Set();
|
|
50
|
+
const cwe = finding.cwe || '';
|
|
51
|
+
const owasp = finding.owasp || '';
|
|
52
|
+
const cat = finding.category || '';
|
|
53
|
+
const rule = (finding.rule || '').toLowerCase();
|
|
54
|
+
const title = (finding.title || '').toLowerCase();
|
|
55
|
+
const text = rule + title;
|
|
56
|
+
|
|
57
|
+
// A.5 — policy / governance
|
|
58
|
+
if (cat === 'agentic' || /policy|governance|\.cursorrules|CLAUDE\.md|agent.?config/.test(text)) ids.add('A.5.2');
|
|
59
|
+
if (/no.?policy|missing.?policy/.test(text)) ids.add('A.5.5');
|
|
60
|
+
|
|
61
|
+
// A.6 — organization, impact, privacy/security by design
|
|
62
|
+
if (/poison|rag\b|retrieval|embedding|memory/.test(text)) {
|
|
63
|
+
ids.add('A.7.4'); ids.add('A.6.2.4');
|
|
64
|
+
}
|
|
65
|
+
if (cat === 'secrets' || cwe === 'CWE-200' || cwe === 'CWE-312' || cwe === 'CWE-798'
|
|
66
|
+
|| owasp === 'ASI06' || /prompt.?inject|jailbreak/.test(text) || owasp === 'ASI01') {
|
|
67
|
+
ids.add('A.6.2.6');
|
|
68
|
+
}
|
|
69
|
+
if (/permission|role|segregation|least.?privilege/.test(text)) ids.add('A.6.1.2');
|
|
70
|
+
|
|
71
|
+
// A.7 — resources, data quality, documentation
|
|
72
|
+
if (cat === 'llm' || /inference|compute|gpu/.test(text)) ids.add('A.7.2');
|
|
73
|
+
if (/no.?model.?card|missing.?documentation/.test(text)) ids.add('A.7.6');
|
|
74
|
+
|
|
75
|
+
// A.8 — tooling
|
|
76
|
+
if (/mcp|tool.?poison|tool.?registry/.test(text) || cat === 'config') ids.add('A.8.4');
|
|
77
|
+
if (cat === 'llm') ids.add('A.8.2');
|
|
78
|
+
|
|
79
|
+
// A.9 — lifecycle: info to users, verification, monitoring
|
|
80
|
+
if (owasp === 'ASI08' || /human.?oversight|missing.?confirmation|auto.?accept|yolo/.test(text)) { ids.add('A.9.2'); ids.add('A.12.4'); }
|
|
81
|
+
if (/no.?verification|no.?validation|unverified/.test(text)) ids.add('A.9.4');
|
|
82
|
+
if (/no.?monitoring|no.?logging|telemetry|no.?observability/.test(text)) ids.add('A.9.5');
|
|
83
|
+
|
|
84
|
+
// A.10/A.13 — supply chain, third parties
|
|
85
|
+
if (cat === 'supply-chain' || cat === 'deps' || owasp === 'ASI04') {
|
|
86
|
+
ids.add('A.10.3'); ids.add('A.10.2'); ids.add('A.13.2');
|
|
87
|
+
}
|
|
88
|
+
if (/provenance|signature|hash|attestation/.test(text)) ids.add('A.10.4');
|
|
89
|
+
|
|
90
|
+
// A.11 — incident communication
|
|
91
|
+
if (/incident|report/.test(text) && cat !== 'secrets') ids.add('A.11.5');
|
|
92
|
+
|
|
93
|
+
return [...ids];
|
|
94
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MITRE ATLAS — Adversarial Threat Landscape for AI Systems.
|
|
3
|
+
* https://atlas.mitre.org/
|
|
4
|
+
*
|
|
5
|
+
* Subset of techniques observable from static code/config analysis.
|
|
6
|
+
* Enriched with the vendored ATLAS knowledge snapshot (see atlas-knowledge.js).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { getTechniqueDetails, atlasSnapshot, isKnownTechnique } from '../atlas-knowledge.js';
|
|
10
|
+
|
|
11
|
+
export const name = 'mitre-atlas';
|
|
12
|
+
export const version = '2024';
|
|
13
|
+
export const title = 'MITRE ATLAS';
|
|
14
|
+
export const description = 'Adversarial tactics, techniques, and case studies for AI systems.';
|
|
15
|
+
export const url = 'https://atlas.mitre.org/';
|
|
16
|
+
export const enrichmentSnapshot = () => atlasSnapshot();
|
|
17
|
+
|
|
18
|
+
export { getTechniqueDetails, atlasSnapshot, isKnownTechnique };
|
|
19
|
+
|
|
20
|
+
export const controls = [
|
|
21
|
+
{ id: 'AML.T0010', title: 'ML Supply Chain Compromise', description: 'Adversary compromises models/datasets/dependencies to gain access.' },
|
|
22
|
+
{ id: 'AML.T0018', title: 'Manipulate AI Model', description: 'Modify deployed model parameters or artifacts.' },
|
|
23
|
+
{ id: 'AML.T0024', title: 'Exfiltration via ML Inference', description: 'Leak training data or system info through inference outputs.' },
|
|
24
|
+
{ id: 'AML.T0034', title: 'ML Model Poisoning', description: 'Insert poisoned data into training/fine-tuning pipeline.' },
|
|
25
|
+
{ id: 'AML.T0040', title: 'ML Model Inference API Access', description: 'Abuse exposed inference APIs for evasion/extraction.' },
|
|
26
|
+
{ id: 'AML.T0043', title: 'Craft Adversarial Data', description: 'Carefully crafted input bypasses model decision boundaries.' },
|
|
27
|
+
{ id: 'AML.T0048', title: 'External Harms', description: 'AI output causes financial, reputational, or societal damage.' },
|
|
28
|
+
{ id: 'AML.T0051', title: 'LLM Prompt Injection', description: 'Direct or indirect prompt injection alters LLM behavior.' },
|
|
29
|
+
{ id: 'AML.T0053', title: 'LLM Plugin Compromise', description: 'Malicious or vulnerable plugin/tool exposed to the LLM.' },
|
|
30
|
+
{ id: 'AML.T0054', title: 'LLM Jailbreak', description: 'Bypass safety alignment via crafted prompts or roleplay.' },
|
|
31
|
+
{ id: 'AML.T0057', title: 'LLM Data Leakage', description: 'LLM emits training data, secrets, or system prompts.' },
|
|
32
|
+
{ id: 'AML.T0070', title: 'RAG Poisoning', description: 'Inject malicious documents into retrieval index.' },
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
export function mapFinding(finding) {
|
|
36
|
+
const ids = new Set();
|
|
37
|
+
const cwe = finding.cwe || '';
|
|
38
|
+
const owasp = finding.owasp || '';
|
|
39
|
+
const cat = finding.category || '';
|
|
40
|
+
const rule = (finding.rule || '').toLowerCase();
|
|
41
|
+
const title = (finding.title || '').toLowerCase();
|
|
42
|
+
|
|
43
|
+
if (cat === 'supply-chain' || cat === 'deps' || owasp === 'ASI04') ids.add('AML.T0010');
|
|
44
|
+
if (owasp === 'ASI05' || /poison|tamper|memory/.test(rule + title)) { ids.add('AML.T0018'); ids.add('AML.T0034'); }
|
|
45
|
+
if (cat === 'secrets' || cwe === 'CWE-200' || cwe === 'CWE-312' || owasp === 'ASI06') { ids.add('AML.T0024'); ids.add('AML.T0057'); }
|
|
46
|
+
if (cat === 'api' && /llm|inference|model/.test(rule + title)) ids.add('AML.T0040');
|
|
47
|
+
if (/jailbreak|dan|roleplay|ignore.?instructions/.test(rule + title)) ids.add('AML.T0054');
|
|
48
|
+
if (/prompt.?inject|indirect.?inject/.test(rule + title) || owasp === 'ASI01') { ids.add('AML.T0043'); ids.add('AML.T0051'); }
|
|
49
|
+
if (owasp === 'ASI02' || /mcp|tool.?poison/.test(rule + title)) ids.add('AML.T0053');
|
|
50
|
+
if (/retrieval|vector.?store|rag\b|embedding/.test(rule + title)) ids.add('AML.T0070');
|
|
51
|
+
if (owasp === 'ASI10') ids.add('AML.T0048');
|
|
52
|
+
|
|
53
|
+
return [...ids];
|
|
54
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NIST AI 600-1 — Generative AI Profile (companion to AI RMF 1.0).
|
|
3
|
+
* https://nvlpubs.nist.gov/nistpubs/ai/NIST.AI.600-1.pdf
|
|
4
|
+
*
|
|
5
|
+
* The profile lists actions tagged with prefixes for each RMF function
|
|
6
|
+
* (GV/MP/MS/MG) tailored to GenAI. We use a representative subset.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export const name = 'nist-ai-600-1';
|
|
10
|
+
export const version = '1.0';
|
|
11
|
+
export const title = 'NIST AI 600-1 (Generative AI Profile)';
|
|
12
|
+
export const description = 'GenAI-specific risk management actions complementing NIST AI RMF 1.0.';
|
|
13
|
+
export const url = 'https://nvlpubs.nist.gov/nistpubs/ai/NIST.AI.600-1.pdf';
|
|
14
|
+
|
|
15
|
+
export const controls = [
|
|
16
|
+
{ id: 'GV-1.3-GAI', title: 'Govern: GenAI policies & procedures', description: 'Establish policies addressing GenAI-specific risks.' },
|
|
17
|
+
{ id: 'GV-4.1-GAI', title: 'Govern: organizational responsibility', description: 'Define roles for GenAI risk decisions and tool use.' },
|
|
18
|
+
{ id: 'MP-2.3-GAI', title: 'Map: dangerous/violent content risks', description: 'Identify CBRN, malicious code, and harmful-output risks.' },
|
|
19
|
+
{ id: 'MP-5.1-GAI', title: 'Map: information integrity', description: 'Identify confabulation, prompt injection, data integrity risks.' },
|
|
20
|
+
{ id: 'MS-2.6-GAI', title: 'Measure: harmful content / safety', description: 'Assess outputs for harmful, biased, or unsafe content.' },
|
|
21
|
+
{ id: 'MS-2.7-GAI', title: 'Measure: security and adversarial robustness', description: 'Evaluate robustness against adversarial inputs and exfiltration.' },
|
|
22
|
+
{ id: 'MS-2.10-GAI', title: 'Measure: information security', description: 'Test for leakage of training data, secrets, system prompts.' },
|
|
23
|
+
{ id: 'MG-2.2-GAI', title: 'Manage: data privacy & confidentiality', description: 'Mitigate disclosure of PII and confidential data.' },
|
|
24
|
+
{ id: 'MG-3.2-GAI', title: 'Manage: third-party / supply chain', description: 'Manage risks from third-party models, datasets, components.' },
|
|
25
|
+
{ id: 'MG-4.1-GAI', title: 'Manage: ongoing monitoring', description: 'Continuous monitoring for new GenAI risks post-deployment.' },
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
export function mapFinding(finding) {
|
|
29
|
+
const ids = new Set();
|
|
30
|
+
const cwe = finding.cwe || '';
|
|
31
|
+
const owasp = finding.owasp || '';
|
|
32
|
+
const cat = finding.category || '';
|
|
33
|
+
const rule = (finding.rule || '').toLowerCase();
|
|
34
|
+
const title = (finding.title || '').toLowerCase();
|
|
35
|
+
|
|
36
|
+
if (cat === 'agentic' || /agent.?config|\.cursorrules|CLAUDE\.md|\.windsurfrules/.test(rule + title)) { ids.add('GV-1.3-GAI'); ids.add('GV-4.1-GAI'); }
|
|
37
|
+
if (/prompt.?inject|jailbreak/.test(rule + title) || owasp === 'ASI01') { ids.add('MP-5.1-GAI'); ids.add('MS-2.7-GAI'); }
|
|
38
|
+
if (owasp === 'ASI05' || /poison|tamper|memory|rag\b|embedding/.test(rule + title)) ids.add('MP-5.1-GAI');
|
|
39
|
+
if (cat === 'llm' && /toxic|harm|unsafe/.test(rule + title)) { ids.add('MP-2.3-GAI'); ids.add('MS-2.6-GAI'); }
|
|
40
|
+
if (cat === 'secrets' || cwe === 'CWE-200' || cwe === 'CWE-312' || cwe === 'CWE-798' || owasp === 'ASI06') { ids.add('MG-2.2-GAI'); ids.add('MS-2.10-GAI'); }
|
|
41
|
+
if (cat === 'supply-chain' || cat === 'deps' || owasp === 'ASI04') ids.add('MG-3.2-GAI');
|
|
42
|
+
if (/mcp|tool.?poison/.test(rule + title) || owasp === 'ASI08' || owasp === 'ASI10') ids.add('MG-4.1-GAI');
|
|
43
|
+
|
|
44
|
+
return [...ids];
|
|
45
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OWASP Top 10 for LLM Applications (2025).
|
|
3
|
+
* https://genai.owasp.org/llm-top-10/
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const name = 'owasp-llm';
|
|
7
|
+
export const version = '2025';
|
|
8
|
+
export const title = 'OWASP Top 10 for LLM Applications';
|
|
9
|
+
export const description = 'Most critical security risks for applications using large language models.';
|
|
10
|
+
export const url = 'https://genai.owasp.org/llm-top-10/';
|
|
11
|
+
|
|
12
|
+
export const controls = [
|
|
13
|
+
{ id: 'LLM01', title: 'Prompt Injection', description: 'User or indirect input manipulates LLM behavior, bypassing instructions.' },
|
|
14
|
+
{ id: 'LLM02', title: 'Sensitive Information Disclosure', description: 'LLM reveals secrets, PII, or proprietary data via outputs.' },
|
|
15
|
+
{ id: 'LLM03', title: 'Supply Chain', description: 'Compromised models, datasets, plugins, or upstream packages.' },
|
|
16
|
+
{ id: 'LLM04', title: 'Data and Model Poisoning', description: 'Tampered training, fine-tuning, or retrieval data biases the model.' },
|
|
17
|
+
{ id: 'LLM05', title: 'Improper Output Handling', description: 'Downstream systems trust LLM output without validation/escaping.' },
|
|
18
|
+
{ id: 'LLM06', title: 'Excessive Agency', description: 'LLM granted excessive autonomy, permissions, or tool scope.' },
|
|
19
|
+
{ id: 'LLM07', title: 'System Prompt Leakage', description: 'System prompts containing secrets or rules are exposed.' },
|
|
20
|
+
{ id: 'LLM08', title: 'Vector and Embedding Weaknesses', description: 'RAG/vector stores leak data or accept poisoned embeddings.' },
|
|
21
|
+
{ id: 'LLM09', title: 'Misinformation', description: 'Confidently wrong outputs propagate as fact.' },
|
|
22
|
+
{ id: 'LLM10', title: 'Unbounded Consumption', description: 'Resource exhaustion (cost, tokens, latency) via crafted prompts.' },
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
export function mapFinding(finding) {
|
|
26
|
+
const ids = new Set();
|
|
27
|
+
const cwe = finding.cwe || '';
|
|
28
|
+
const owasp = finding.owasp || '';
|
|
29
|
+
const cat = finding.category || '';
|
|
30
|
+
const rule = (finding.rule || '').toLowerCase();
|
|
31
|
+
const title = (finding.title || '').toLowerCase();
|
|
32
|
+
|
|
33
|
+
if (owasp === 'ASI01' || /prompt.?inject|jailbreak|goal.?hijack/.test(rule + title)) ids.add('LLM01');
|
|
34
|
+
if (cat === 'secrets' || cat === 'git-history' || cwe === 'CWE-200' || cwe === 'CWE-312' || cwe === 'CWE-522' || cwe === 'CWE-798' || owasp === 'ASI06') ids.add('LLM02');
|
|
35
|
+
if (cat === 'supply-chain' || cat === 'deps' || owasp === 'ASI04') ids.add('LLM03');
|
|
36
|
+
if (owasp === 'ASI05' || /poison|tamper/.test(rule + title)) ids.add('LLM04');
|
|
37
|
+
if (cwe === 'CWE-79' || cwe === 'CWE-94' || cwe === 'CWE-116' || /output.?handl|unescaped/.test(rule + title)) ids.add('LLM05');
|
|
38
|
+
if (cwe === 'CWE-269' || cwe === 'CWE-250' || owasp === 'ASI02' || owasp === 'ASI03') ids.add('LLM06');
|
|
39
|
+
if (/system.?prompt|prompt.?leak/.test(rule + title)) ids.add('LLM07');
|
|
40
|
+
if (/vector|embedding|rag\b|retrieval/.test(rule + title)) ids.add('LLM08');
|
|
41
|
+
if (owasp === 'ASI10' || /halluc|misinform/.test(rule + title)) ids.add('LLM09');
|
|
42
|
+
if (cat === 'api' || /rate.?limit|token.?abuse|unbounded|dos/.test(rule + title)) ids.add('LLM10');
|
|
43
|
+
|
|
44
|
+
return [...ids];
|
|
45
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OWASP Machine Learning Security Top 10.
|
|
3
|
+
* https://owasp.org/www-project-machine-learning-security-top-10/
|
|
4
|
+
*
|
|
5
|
+
* Covers classical ML model attacks (separate from the LLM Top 10).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export const name = 'owasp-ml';
|
|
9
|
+
export const version = '2023';
|
|
10
|
+
export const title = 'OWASP Machine Learning Security Top 10';
|
|
11
|
+
export const description = 'Top risks specific to machine learning systems.';
|
|
12
|
+
export const url = 'https://owasp.org/www-project-machine-learning-security-top-10/';
|
|
13
|
+
|
|
14
|
+
export const controls = [
|
|
15
|
+
{ id: 'ML01', title: 'Input Manipulation Attack', description: 'Adversarial inputs cause incorrect predictions.' },
|
|
16
|
+
{ id: 'ML02', title: 'Data Poisoning Attack', description: 'Tainted training data biases or backdoors model.' },
|
|
17
|
+
{ id: 'ML03', title: 'Model Inversion Attack', description: 'Reconstruct training data from model outputs.' },
|
|
18
|
+
{ id: 'ML04', title: 'Membership Inference Attack', description: 'Determine if a record was in the training set.' },
|
|
19
|
+
{ id: 'ML05', title: 'Model Theft', description: 'Steal model weights via API or filesystem access.' },
|
|
20
|
+
{ id: 'ML06', title: 'AI Supply Chain Attacks', description: 'Compromised pretrained models or libraries.' },
|
|
21
|
+
{ id: 'ML07', title: 'Transfer Learning Attack', description: 'Backdoors propagated from upstream pretrained model.' },
|
|
22
|
+
{ id: 'ML08', title: 'Model Skewing', description: 'Feedback loops bias online models.' },
|
|
23
|
+
{ id: 'ML09', title: 'Output Integrity Attack', description: 'Tamper with model outputs in transit.' },
|
|
24
|
+
{ id: 'ML10', title: 'Model Poisoning', description: 'Malicious modifications to model parameters.' },
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
export function mapFinding(finding) {
|
|
28
|
+
const ids = new Set();
|
|
29
|
+
const owasp = finding.owasp || '';
|
|
30
|
+
const cat = finding.category || '';
|
|
31
|
+
const rule = (finding.rule || '').toLowerCase();
|
|
32
|
+
const title = (finding.title || '').toLowerCase();
|
|
33
|
+
|
|
34
|
+
if (/adversarial|prompt.?inject|jailbreak/.test(rule + title) || owasp === 'ASI01') ids.add('ML01');
|
|
35
|
+
if (owasp === 'ASI05' || /poison|tamper|rag\b|embedding/.test(rule + title)) { ids.add('ML02'); ids.add('ML10'); }
|
|
36
|
+
if (/inversion|extract.?training/.test(rule + title)) ids.add('ML03');
|
|
37
|
+
if (/membership.?inference/.test(rule + title)) ids.add('ML04');
|
|
38
|
+
if (/model.?(?:theft|steal|exfil)|weights.?leak/.test(rule + title)) ids.add('ML05');
|
|
39
|
+
if (cat === 'supply-chain' || cat === 'deps' || owasp === 'ASI04') ids.add('ML06');
|
|
40
|
+
if (/pretrain|fine.?tune.?backdoor|transfer.?learn/.test(rule + title)) ids.add('ML07');
|
|
41
|
+
if (/feedback.?loop|skew/.test(rule + title)) ids.add('ML08');
|
|
42
|
+
if (/output.?integrity|response.?tamper/.test(rule + title)) ids.add('ML09');
|
|
43
|
+
|
|
44
|
+
return [...ids];
|
|
45
|
+
}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Threat Intelligence Feed
|
|
3
|
+
* =========================
|
|
4
|
+
*
|
|
5
|
+
* Loads and queries Praxis's threat intelligence database.
|
|
6
|
+
*
|
|
7
|
+
* Data sources (merged by `cli/utils/intel/`):
|
|
8
|
+
* - OSV.dev package advisories (npm, PyPI, Go, ...)
|
|
9
|
+
* - GitHub Advisory DB cross-ecosystem advisories
|
|
10
|
+
* - CISA KEV actively-exploited CVEs
|
|
11
|
+
* - EPSS exploit-likelihood scores
|
|
12
|
+
* - NVD CVE detail enrichment
|
|
13
|
+
* - Gitleaks rules extra secret-detection patterns
|
|
14
|
+
* - Snyk / Socket / GitGuardian / Sonatype / Phylum (paid, optional)
|
|
15
|
+
*
|
|
16
|
+
* Bundled-seed fields (maliciousSkillHashes, compromisedMcpServers,
|
|
17
|
+
* maliciousConfigSignatures) are loaded from `cli/data/threat-intel.json`
|
|
18
|
+
* when no merged feed is on disk yet.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import fs from 'fs';
|
|
22
|
+
import path from 'path';
|
|
23
|
+
import os from 'os';
|
|
24
|
+
import { fileURLToPath } from 'url';
|
|
25
|
+
import { createHash } from 'crypto';
|
|
26
|
+
import * as intel from './intel/index.js';
|
|
27
|
+
|
|
28
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
29
|
+
const __dirname = path.dirname(__filename);
|
|
30
|
+
|
|
31
|
+
const SEED_PATH = path.resolve(__dirname, '..', 'data', 'threat-intel.json');
|
|
32
|
+
const mergedFeedPath = () => path.join(os.homedir(), '.praxis', 'threat-intel.json');
|
|
33
|
+
|
|
34
|
+
let _cache = null;
|
|
35
|
+
|
|
36
|
+
export class ThreatIntel {
|
|
37
|
+
/**
|
|
38
|
+
* Load threat intel data.
|
|
39
|
+
*
|
|
40
|
+
* Resolution order:
|
|
41
|
+
* 1. Merged multi-source feed at ~/.praxis/threat-intel.json (if present)
|
|
42
|
+
* 2. Bundled seed at cli/data/threat-intel.json
|
|
43
|
+
*/
|
|
44
|
+
static load() {
|
|
45
|
+
if (_cache) return _cache;
|
|
46
|
+
|
|
47
|
+
let data = null;
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
if (fs.existsSync(mergedFeedPath())) {
|
|
51
|
+
data = JSON.parse(fs.readFileSync(mergedFeedPath(), 'utf-8'));
|
|
52
|
+
}
|
|
53
|
+
} catch { /* fall through to seed */ }
|
|
54
|
+
|
|
55
|
+
if (!data) {
|
|
56
|
+
try {
|
|
57
|
+
data = JSON.parse(fs.readFileSync(SEED_PATH, 'utf-8'));
|
|
58
|
+
} catch {
|
|
59
|
+
data = emptyFeed();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Ensure new fields exist even when reading the old seed.
|
|
64
|
+
data.cveAdvisories ||= [];
|
|
65
|
+
data.kevList ||= [];
|
|
66
|
+
data.kevDetails ||= {};
|
|
67
|
+
data.epssScores ||= {};
|
|
68
|
+
data.osvIndex ||= {};
|
|
69
|
+
data.ghsaIndex ||= {};
|
|
70
|
+
data.nvdDetails ||= {};
|
|
71
|
+
data.secretRules ||= [];
|
|
72
|
+
|
|
73
|
+
_cache = data;
|
|
74
|
+
return data;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** @internal — for tests */
|
|
78
|
+
static _resetCache() { _cache = null; }
|
|
79
|
+
|
|
80
|
+
// ---- Legacy API (unchanged) ---------------------------------------------
|
|
81
|
+
|
|
82
|
+
static lookupHash(sha256) {
|
|
83
|
+
const data = ThreatIntel.load();
|
|
84
|
+
return (data.maliciousSkillHashes || []).find(h => h.sha256 === sha256) || null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
static lookupMcpServer(name, version = '*') {
|
|
88
|
+
const data = ThreatIntel.load();
|
|
89
|
+
return (data.compromisedMcpServers || []).find(s => {
|
|
90
|
+
if (s.name !== name) return false;
|
|
91
|
+
if (s.versions.includes('*')) return true;
|
|
92
|
+
return s.versions.some(v => {
|
|
93
|
+
if (v.startsWith('<')) return version < v.slice(1);
|
|
94
|
+
return v === version;
|
|
95
|
+
});
|
|
96
|
+
}) || null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
static matchSignatures(content) {
|
|
100
|
+
const data = ThreatIntel.load();
|
|
101
|
+
const matches = [];
|
|
102
|
+
for (const sig of data.maliciousConfigSignatures || []) {
|
|
103
|
+
try {
|
|
104
|
+
const re = new RegExp(sig.pattern, 'gi');
|
|
105
|
+
if (re.test(content)) matches.push(sig);
|
|
106
|
+
} catch { /* skip bad patterns */ }
|
|
107
|
+
}
|
|
108
|
+
return matches;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
static hash(content) {
|
|
112
|
+
return createHash('sha256').update(content).digest('hex');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ---- New: multi-source query API ----------------------------------------
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Look up advisories for a package version. Checks the merged OSV index
|
|
119
|
+
* first; affected version ranges are evaluated naively (any match in
|
|
120
|
+
* the package's advisories is returned with isAffected=true|false).
|
|
121
|
+
*
|
|
122
|
+
* @param {string} pkg
|
|
123
|
+
* @param {string} version
|
|
124
|
+
* @param {string} ecosystem 'npm', 'PyPI', 'Go', etc.
|
|
125
|
+
*/
|
|
126
|
+
static lookupOsv(pkg, version, ecosystem = 'npm') {
|
|
127
|
+
const data = ThreatIntel.load();
|
|
128
|
+
const advisories = data.osvIndex[`${ecosystem}/${pkg}`] || [];
|
|
129
|
+
return advisories.map(a => ({
|
|
130
|
+
...a,
|
|
131
|
+
isAffected: version ? versionInRanges(version, a.ranges) : null,
|
|
132
|
+
}));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Look up GHSA advisories for a package. */
|
|
136
|
+
static lookupGhsa(pkg, ecosystem = 'npm') {
|
|
137
|
+
const data = ThreatIntel.load();
|
|
138
|
+
return data.ghsaIndex[`${ecosystem}/${pkg}`] || [];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** EPSS exploit-likelihood for a CVE. Returns null if unknown. */
|
|
142
|
+
static getEpss(cve) {
|
|
143
|
+
const data = ThreatIntel.load();
|
|
144
|
+
return data.epssScores[cve] || null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** True if a CVE is in CISA KEV (actively exploited). */
|
|
148
|
+
static isInKev(cve) {
|
|
149
|
+
const data = ThreatIntel.load();
|
|
150
|
+
return Array.isArray(data.kevList) && data.kevList.includes(cve);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
static getKevDetails(cve) {
|
|
154
|
+
const data = ThreatIntel.load();
|
|
155
|
+
return data.kevDetails?.[cve] || null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Full NVD record for a CVE if previously enriched. */
|
|
159
|
+
static getNvdDetail(cve) {
|
|
160
|
+
const data = ThreatIntel.load();
|
|
161
|
+
return data.nvdDetails[cve] || null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Extra secret detection regexes pulled from Gitleaks / GitGuardian.
|
|
166
|
+
* The scanner can merge these with its hardcoded SECRET_PATTERNS.
|
|
167
|
+
*/
|
|
168
|
+
static getExtraSecretRules() {
|
|
169
|
+
const data = ThreatIntel.load();
|
|
170
|
+
return data.secretRules || [];
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ---- Update / freshness -------------------------------------------------
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Run a multi-source threat-intel update.
|
|
177
|
+
*
|
|
178
|
+
* @param {object} [options] forwarded to `intel.runUpdate`
|
|
179
|
+
* ({ sources, force, onProgress })
|
|
180
|
+
*/
|
|
181
|
+
static async update(options = {}) {
|
|
182
|
+
const result = await intel.runUpdate(options);
|
|
183
|
+
_cache = null; // force re-read on next load()
|
|
184
|
+
return {
|
|
185
|
+
updated: true,
|
|
186
|
+
version: result.merged.version,
|
|
187
|
+
sources: result.meta.sources,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** True if the merged feed is older than `maxAgeMs`. */
|
|
192
|
+
static isStale(maxAgeMs) {
|
|
193
|
+
return intel.isStale(maxAgeMs);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
static stats() {
|
|
197
|
+
const data = ThreatIntel.load();
|
|
198
|
+
return {
|
|
199
|
+
version: data.version,
|
|
200
|
+
updated: data.updated,
|
|
201
|
+
hashes: data.maliciousSkillHashes?.length || 0,
|
|
202
|
+
servers: data.compromisedMcpServers?.length || 0,
|
|
203
|
+
signatures: data.maliciousConfigSignatures?.length || 0,
|
|
204
|
+
configs: data.knownVulnerableConfigs?.length || 0,
|
|
205
|
+
cveAdvisories: (data.cveAdvisories || []).length,
|
|
206
|
+
kevEntries: (data.kevList || []).length,
|
|
207
|
+
epssScores: Object.keys(data.epssScores || {}).length,
|
|
208
|
+
nvdDetails: Object.keys(data.nvdDetails || {}).length,
|
|
209
|
+
extraSecretRules: (data.secretRules || []).length,
|
|
210
|
+
sources: data.sources || null,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function emptyFeed() {
|
|
216
|
+
return {
|
|
217
|
+
version: '0.0.0',
|
|
218
|
+
maliciousSkillHashes: [],
|
|
219
|
+
compromisedMcpServers: [],
|
|
220
|
+
maliciousConfigSignatures: [],
|
|
221
|
+
knownVulnerableConfigs: [],
|
|
222
|
+
cveAdvisories: [],
|
|
223
|
+
kevList: [],
|
|
224
|
+
kevDetails: {},
|
|
225
|
+
epssScores: {},
|
|
226
|
+
osvIndex: {},
|
|
227
|
+
ghsaIndex: {},
|
|
228
|
+
nvdDetails: {},
|
|
229
|
+
secretRules: [],
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Naive evaluation of OSV "ranges" against a version. OSV's range model is
|
|
235
|
+
* { type: 'SEMVER'|'ECOSYSTEM'|'GIT', events: [{introduced}, {fixed}] }.
|
|
236
|
+
* We compare numerically for the SEMVER case and string-equal for fixed
|
|
237
|
+
* versions otherwise. A null/empty range list returns true (assume affected).
|
|
238
|
+
*/
|
|
239
|
+
function versionInRanges(version, ranges) {
|
|
240
|
+
if (!Array.isArray(ranges) || ranges.length === 0) return true;
|
|
241
|
+
for (const r of ranges) {
|
|
242
|
+
let introduced = '0';
|
|
243
|
+
let fixed = null;
|
|
244
|
+
for (const evt of r.events || []) {
|
|
245
|
+
if (evt.introduced) introduced = evt.introduced;
|
|
246
|
+
if (evt.fixed) fixed = evt.fixed;
|
|
247
|
+
}
|
|
248
|
+
if (compareSemver(version, introduced) >= 0) {
|
|
249
|
+
if (!fixed) return true;
|
|
250
|
+
if (compareSemver(version, fixed) < 0) return true;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function compareSemver(a, b) {
|
|
257
|
+
const pa = String(a).split('.').map(p => parseInt(p, 10) || 0);
|
|
258
|
+
const pb = String(b).split('.').map(p => parseInt(p, 10) || 0);
|
|
259
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
260
|
+
const da = pa[i] || 0;
|
|
261
|
+
const db = pb[i] || 0;
|
|
262
|
+
if (da !== db) return da - db;
|
|
263
|
+
}
|
|
264
|
+
return 0;
|
|
265
|
+
}
|