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,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gitleaks rules — community-maintained secret detection regex set.
|
|
3
|
+
* https://github.com/gitleaks/gitleaks
|
|
4
|
+
*
|
|
5
|
+
* We sync the upstream rules list (TOML config) and convert to praxis's
|
|
6
|
+
* SECRET_PATTERNS shape. Rules already present (by name) in our hardcoded
|
|
7
|
+
* patterns.js are skipped to avoid duplicates.
|
|
8
|
+
*
|
|
9
|
+
* Result: ~150 → ~400 secret detection patterns without code changes.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { fetchText } from '../http.js';
|
|
13
|
+
|
|
14
|
+
const URL = 'https://raw.githubusercontent.com/gitleaks/gitleaks/master/config/gitleaks.toml';
|
|
15
|
+
const TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7d
|
|
16
|
+
|
|
17
|
+
export const name = 'gitleaks';
|
|
18
|
+
export const tier = 'core';
|
|
19
|
+
export const description = 'Gitleaks regex rule set (additional secret patterns)';
|
|
20
|
+
|
|
21
|
+
export async function fetchAll() {
|
|
22
|
+
const toml = await fetchText(URL);
|
|
23
|
+
const rules = parseRules(toml);
|
|
24
|
+
return {
|
|
25
|
+
sourceUrl: URL,
|
|
26
|
+
count: rules.length,
|
|
27
|
+
rules,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Tiny TOML parser — only handles what gitleaks.toml uses:
|
|
33
|
+
* [[rules]]
|
|
34
|
+
* id = "..."
|
|
35
|
+
* description = "..."
|
|
36
|
+
* regex = '''...'''
|
|
37
|
+
* keywords = ["..."]
|
|
38
|
+
*
|
|
39
|
+
* Avoids pulling in a TOML dependency for one file.
|
|
40
|
+
*/
|
|
41
|
+
function parseRules(toml) {
|
|
42
|
+
const rules = [];
|
|
43
|
+
const sections = toml.split(/^\[\[rules\]\]\s*$/m).slice(1);
|
|
44
|
+
|
|
45
|
+
for (const section of sections) {
|
|
46
|
+
const lines = section.split('\n');
|
|
47
|
+
const rule = {};
|
|
48
|
+
let collectingMultiline = null;
|
|
49
|
+
let multilineBuf = '';
|
|
50
|
+
|
|
51
|
+
for (const line of lines) {
|
|
52
|
+
if (collectingMultiline) {
|
|
53
|
+
if (line.includes("'''")) {
|
|
54
|
+
multilineBuf += line.slice(0, line.indexOf("'''"));
|
|
55
|
+
rule[collectingMultiline] = multilineBuf;
|
|
56
|
+
collectingMultiline = null;
|
|
57
|
+
multilineBuf = '';
|
|
58
|
+
} else {
|
|
59
|
+
multilineBuf += line + '\n';
|
|
60
|
+
}
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Stop at next section
|
|
65
|
+
if (/^\[/.test(line.trim())) break;
|
|
66
|
+
|
|
67
|
+
const m = line.match(/^\s*(id|description|regex|tags)\s*=\s*(.*)$/);
|
|
68
|
+
if (!m) continue;
|
|
69
|
+
const [, key, rawVal] = m;
|
|
70
|
+
const val = rawVal.trim();
|
|
71
|
+
|
|
72
|
+
if (val.startsWith("'''")) {
|
|
73
|
+
if (val.length > 3 && val.endsWith("'''")) {
|
|
74
|
+
rule[key] = val.slice(3, -3);
|
|
75
|
+
} else {
|
|
76
|
+
collectingMultiline = key;
|
|
77
|
+
multilineBuf = val.slice(3) + '\n';
|
|
78
|
+
}
|
|
79
|
+
} else if (val.startsWith('"') || val.startsWith("'")) {
|
|
80
|
+
rule[key] = val.slice(1, -1);
|
|
81
|
+
} else if (val.startsWith('[')) {
|
|
82
|
+
rule[key] = val.replace(/[[\]"']/g, '').split(',').map(s => s.trim()).filter(Boolean);
|
|
83
|
+
} else {
|
|
84
|
+
rule[key] = val;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (rule.id && rule.regex) {
|
|
89
|
+
rules.push({
|
|
90
|
+
id: rule.id,
|
|
91
|
+
description: rule.description || rule.id,
|
|
92
|
+
regex: rule.regex,
|
|
93
|
+
tags: Array.isArray(rule.tags) ? rule.tags : [],
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return rules;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export const TTL = TTL_MS;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CISA KEV — Known Exploited Vulnerabilities catalog.
|
|
3
|
+
* https://www.cisa.gov/known-exploited-vulnerabilities-catalog
|
|
4
|
+
*
|
|
5
|
+
* Single JSON, refreshed daily. ~1300 entries. We keep just the CVE IDs
|
|
6
|
+
* + minimal metadata; downstream code uses isInKev(cve) for prioritization.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { fetchJson } from '../http.js';
|
|
10
|
+
|
|
11
|
+
const URL = 'https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json';
|
|
12
|
+
const TTL_MS = 24 * 60 * 60 * 1000; // 24h
|
|
13
|
+
|
|
14
|
+
export const name = 'kev';
|
|
15
|
+
export const tier = 'core';
|
|
16
|
+
export const description = 'CISA Known Exploited Vulnerabilities catalog';
|
|
17
|
+
|
|
18
|
+
export async function fetchAll() {
|
|
19
|
+
const data = await fetchJson(URL);
|
|
20
|
+
const entries = (data.vulnerabilities || []).map(v => ({
|
|
21
|
+
cve: v.cveID,
|
|
22
|
+
vendor: v.vendorProject,
|
|
23
|
+
product: v.product,
|
|
24
|
+
name: v.vulnerabilityName,
|
|
25
|
+
dateAdded: v.dateAdded,
|
|
26
|
+
dueDate: v.dueDate,
|
|
27
|
+
requiredAction: v.requiredAction,
|
|
28
|
+
knownRansomware: v.knownRansomwareCampaignUse === 'Known',
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
catalogVersion: data.catalogVersion,
|
|
33
|
+
dateReleased: data.dateReleased,
|
|
34
|
+
entries,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const TTL = TTL_MS;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NVD 2.0 — National Vulnerability Database (NIST).
|
|
3
|
+
* https://services.nvd.nist.gov/rest/json/cves/2.0
|
|
4
|
+
*
|
|
5
|
+
* Used for on-demand CVE detail enrichment, not bulk download.
|
|
6
|
+
* Bulk would be 250k+ entries; we let the merger pull only the CVEs that
|
|
7
|
+
* also appear in OSV/GHSA/KEV. With NVD_API_KEY set, rate limit lifts to
|
|
8
|
+
* 50 req / 30s; otherwise 5 req / 30s.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { fetchJson } from '../http.js';
|
|
12
|
+
|
|
13
|
+
const BASE = 'https://services.nvd.nist.gov/rest/json/cves/2.0';
|
|
14
|
+
const TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7d (CVE descriptions don't change often)
|
|
15
|
+
|
|
16
|
+
export const name = 'nvd';
|
|
17
|
+
export const tier = 'core';
|
|
18
|
+
export const description = 'NVD 2.0 — CVE detail enrichment (NIST)';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Bulk cache build: enrich a list of CVE IDs (typically pulled from KEV +
|
|
22
|
+
* OSV during merge). Returns { details: { CVE-YYYY-NNNN: {...} } }.
|
|
23
|
+
*
|
|
24
|
+
* Respects NVD's 6 req / 30s no-key limit (we sleep 6s between requests).
|
|
25
|
+
* With NVD_API_KEY in env, drops to 600ms.
|
|
26
|
+
*/
|
|
27
|
+
export async function fetchAll({ cveList = [] } = {}) {
|
|
28
|
+
const apiKey = process.env.NVD_API_KEY;
|
|
29
|
+
const delayMs = apiKey ? 600 : 6000;
|
|
30
|
+
const headers = apiKey ? { apiKey } : {};
|
|
31
|
+
const details = {};
|
|
32
|
+
|
|
33
|
+
// Cap to avoid an unbounded job. Caller can pass a smaller list.
|
|
34
|
+
const targets = cveList.slice(0, 500);
|
|
35
|
+
let lastError = null;
|
|
36
|
+
|
|
37
|
+
for (const cve of targets) {
|
|
38
|
+
try {
|
|
39
|
+
const res = await fetchJson(`${BASE}?cveId=${encodeURIComponent(cve)}`, { headers });
|
|
40
|
+
const item = res.vulnerabilities?.[0]?.cve;
|
|
41
|
+
if (!item) continue;
|
|
42
|
+
details[cve] = {
|
|
43
|
+
id: item.id,
|
|
44
|
+
published: item.published,
|
|
45
|
+
lastModified: item.lastModified,
|
|
46
|
+
descriptions: (item.descriptions || []).filter(d => d.lang === 'en').map(d => d.value),
|
|
47
|
+
cvss: extractCvss(item.metrics),
|
|
48
|
+
references: (item.references || []).slice(0, 5).map(r => r.url),
|
|
49
|
+
weaknesses: (item.weaknesses || []).flatMap(w => (w.description || []).map(d => d.value)),
|
|
50
|
+
};
|
|
51
|
+
} catch (err) {
|
|
52
|
+
lastError = err.message;
|
|
53
|
+
}
|
|
54
|
+
await sleep(delayMs);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
fetchedCount: Object.keys(details).length,
|
|
59
|
+
requestedCount: targets.length,
|
|
60
|
+
apiKeyUsed: !!apiKey,
|
|
61
|
+
lastError,
|
|
62
|
+
details,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function extractCvss(metrics) {
|
|
67
|
+
if (!metrics) return null;
|
|
68
|
+
const v31 = metrics.cvssMetricV31?.[0]?.cvssData;
|
|
69
|
+
const v40 = metrics.cvssMetricV40?.[0]?.cvssData;
|
|
70
|
+
const data = v40 || v31;
|
|
71
|
+
if (!data) return null;
|
|
72
|
+
return {
|
|
73
|
+
version: data.version,
|
|
74
|
+
score: data.baseScore,
|
|
75
|
+
severity: data.baseSeverity?.toLowerCase(),
|
|
76
|
+
vector: data.vectorString,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function sleep(ms) {
|
|
81
|
+
return new Promise(r => setTimeout(r, ms));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export const TTL = TTL_MS;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OSV.dev — Open Source Vulnerabilities (Google).
|
|
3
|
+
*
|
|
4
|
+
* Strategy: pull the per-ecosystem advisory zip dumps that OSV publishes daily.
|
|
5
|
+
* https://google.github.io/osv.dev/data/
|
|
6
|
+
*
|
|
7
|
+
* Each ecosystem zip has one JSON per advisory. We download a small set of
|
|
8
|
+
* common ecosystems, extract just the fields praxis needs, and produce a
|
|
9
|
+
* single normalized array of records:
|
|
10
|
+
*
|
|
11
|
+
* { id, ecosystem, package, ranges, severity, cves, summary, references }
|
|
12
|
+
*
|
|
13
|
+
* Two query helpers are exposed via the merged ThreatIntel: lookupOsv() and
|
|
14
|
+
* a faster live-query mode that hits POST https://api.osv.dev/v1/query for
|
|
15
|
+
* a single package.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { fetchJson } from '../http.js';
|
|
19
|
+
|
|
20
|
+
const ECOSYSTEMS = ['npm', 'PyPI', 'Go', 'RubyGems', 'crates.io', 'Maven', 'Packagist', 'NuGet'];
|
|
21
|
+
const TTL_MS = 12 * 60 * 60 * 1000; // 12h
|
|
22
|
+
|
|
23
|
+
export const name = 'osv';
|
|
24
|
+
export const tier = 'core';
|
|
25
|
+
export const description = 'OSV.dev — Google open-source vuln database (npm, PyPI, Go, etc.)';
|
|
26
|
+
|
|
27
|
+
export async function fetchAll() {
|
|
28
|
+
// The full OSV dump is large (~hundreds of MB). For the bundled feed we keep
|
|
29
|
+
// the seed lean by querying the OSV API for the most common malicious /
|
|
30
|
+
// high-severity advisories. For richer offline coverage, praxis queries
|
|
31
|
+
// OSV live during scans (see lookupLive() below).
|
|
32
|
+
//
|
|
33
|
+
// Here we batch-query OSV for a curated list of historically compromised
|
|
34
|
+
// packages so the merged threat-intel.json always carries a usable seed,
|
|
35
|
+
// even when the user is offline at scan time.
|
|
36
|
+
const knownCompromised = [
|
|
37
|
+
{ ecosystem: 'npm', name: 'event-stream' },
|
|
38
|
+
{ ecosystem: 'npm', name: 'flatmap-stream' },
|
|
39
|
+
{ ecosystem: 'npm', name: 'eslint-scope' },
|
|
40
|
+
{ ecosystem: 'npm', name: 'ua-parser-js' },
|
|
41
|
+
{ ecosystem: 'npm', name: 'coa' },
|
|
42
|
+
{ ecosystem: 'npm', name: 'rc' },
|
|
43
|
+
{ ecosystem: 'npm', name: 'colors' },
|
|
44
|
+
{ ecosystem: 'npm', name: 'node-ipc' },
|
|
45
|
+
{ ecosystem: 'npm', name: '@solana/web3.js' },
|
|
46
|
+
{ ecosystem: 'npm', name: 'lottie-player' },
|
|
47
|
+
{ ecosystem: 'npm', name: 'axios' },
|
|
48
|
+
{ ecosystem: 'PyPI', name: 'litellm' },
|
|
49
|
+
{ ecosystem: 'PyPI', name: 'ctx' },
|
|
50
|
+
{ ecosystem: 'PyPI', name: 'phpass' },
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
const records = [];
|
|
54
|
+
for (const target of knownCompromised) {
|
|
55
|
+
try {
|
|
56
|
+
const adv = await fetchJson('https://api.osv.dev/v1/query', {
|
|
57
|
+
method: 'POST',
|
|
58
|
+
headers: { 'content-type': 'application/json' },
|
|
59
|
+
body: JSON.stringify({ package: { name: target.name, ecosystem: target.ecosystem } }),
|
|
60
|
+
});
|
|
61
|
+
for (const v of adv.vulns || []) {
|
|
62
|
+
records.push(normalize(v, target.ecosystem, target.name));
|
|
63
|
+
}
|
|
64
|
+
} catch {
|
|
65
|
+
// Skip individual failures — orchestrator will report partial success
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return { records, ecosystems: ECOSYSTEMS };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Live single-package lookup. Used by the supply-chain agent at scan time
|
|
74
|
+
* when we have a package name + version to check.
|
|
75
|
+
*/
|
|
76
|
+
export async function lookupLive(name, version, ecosystem = 'npm') {
|
|
77
|
+
try {
|
|
78
|
+
const body = { package: { name, ecosystem } };
|
|
79
|
+
if (version) body.version = version;
|
|
80
|
+
const res = await fetchJson('https://api.osv.dev/v1/query', {
|
|
81
|
+
method: 'POST',
|
|
82
|
+
headers: { 'content-type': 'application/json' },
|
|
83
|
+
body: JSON.stringify(body),
|
|
84
|
+
timeout: 8000,
|
|
85
|
+
retries: 1,
|
|
86
|
+
});
|
|
87
|
+
return (res.vulns || []).map(v => normalize(v, ecosystem, name));
|
|
88
|
+
} catch {
|
|
89
|
+
return [];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function normalize(vuln, ecosystem, packageName) {
|
|
94
|
+
const cves = (vuln.aliases || []).filter(a => /^CVE-/i.test(a));
|
|
95
|
+
const severity = pickSeverity(vuln);
|
|
96
|
+
const ranges = (vuln.affected || [])
|
|
97
|
+
.filter(a => a.package?.name === packageName || !a.package?.name)
|
|
98
|
+
.flatMap(a => a.ranges || []);
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
id: vuln.id,
|
|
102
|
+
ecosystem,
|
|
103
|
+
package: packageName,
|
|
104
|
+
summary: vuln.summary || vuln.details?.slice(0, 200) || '',
|
|
105
|
+
severity,
|
|
106
|
+
cves,
|
|
107
|
+
ranges: ranges.map(r => ({
|
|
108
|
+
type: r.type,
|
|
109
|
+
events: (r.events || []).map(e => ({ ...e })),
|
|
110
|
+
})),
|
|
111
|
+
references: (vuln.references || []).slice(0, 5).map(r => r.url),
|
|
112
|
+
published: vuln.published || vuln.modified || null,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function pickSeverity(vuln) {
|
|
117
|
+
// OSV severity comes in either CVSS_V3 or DB-specific fields. Prefer CVSS.
|
|
118
|
+
for (const s of vuln.severity || []) {
|
|
119
|
+
if (s.type === 'CVSS_V3' || s.type === 'CVSS_V4') {
|
|
120
|
+
const score = parseFloat((s.score || '').match(/\/([0-9.]+)$/)?.[1] || s.score);
|
|
121
|
+
if (!Number.isNaN(score)) {
|
|
122
|
+
if (score >= 9.0) return 'critical';
|
|
123
|
+
if (score >= 7.0) return 'high';
|
|
124
|
+
if (score >= 4.0) return 'medium';
|
|
125
|
+
return 'low';
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return vuln.database_specific?.severity?.toLowerCase() || 'medium';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export const TTL = TTL_MS;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phylum — paid.
|
|
3
|
+
* https://docs.phylum.io
|
|
4
|
+
*
|
|
5
|
+
* Requires PHYLUM_API_KEY. Phylum surfaces malicious-package telemetry
|
|
6
|
+
* and dependency risk scores via /api/data/packages and /api/data/jobs.
|
|
7
|
+
* Skipped silently when not set.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { fetchJson } from '../http.js';
|
|
11
|
+
|
|
12
|
+
const TTL_MS = 6 * 60 * 60 * 1000;
|
|
13
|
+
|
|
14
|
+
export const name = 'phylum';
|
|
15
|
+
export const tier = 'optional';
|
|
16
|
+
export const description = 'Phylum supply-chain risk feed (requires PHYLUM_API_KEY)';
|
|
17
|
+
export const envKey = 'PHYLUM_API_KEY';
|
|
18
|
+
|
|
19
|
+
export async function fetchAll() {
|
|
20
|
+
const key = process.env.PHYLUM_API_KEY;
|
|
21
|
+
if (!key) return { skipped: true, reason: 'PHYLUM_API_KEY not set' };
|
|
22
|
+
|
|
23
|
+
const headers = { Authorization: `Bearer ${key}` };
|
|
24
|
+
const data = await fetchJson(
|
|
25
|
+
'https://api.phylum.io/api/v0/data/malicious-packages?limit=200',
|
|
26
|
+
{ headers, timeout: 20000 },
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const advisories = (data.items || data || []).map(p => ({
|
|
30
|
+
package: p.name,
|
|
31
|
+
ecosystem: p.ecosystem || p.type,
|
|
32
|
+
version: p.version,
|
|
33
|
+
severity: p.severity || 'high',
|
|
34
|
+
summary: p.summary || p.description,
|
|
35
|
+
published: p.published_at || p.created_at,
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
count: advisories.length,
|
|
40
|
+
advisories,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const TTL = TTL_MS;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Snyk Vulnerability DB — paid.
|
|
3
|
+
* https://docs.snyk.io/snyk-api
|
|
4
|
+
*
|
|
5
|
+
* Requires SNYK_TOKEN. Skipped silently when not set.
|
|
6
|
+
* The /rest/orgs/{org_id}/issues endpoint returns issues across an org's
|
|
7
|
+
* monitored projects. We expose a thin pull that grabs recent issues.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { fetchJson } from '../http.js';
|
|
11
|
+
|
|
12
|
+
const TTL_MS = 12 * 60 * 60 * 1000;
|
|
13
|
+
|
|
14
|
+
export const name = 'snyk';
|
|
15
|
+
export const tier = 'optional';
|
|
16
|
+
export const description = 'Snyk Vulnerability Database (requires SNYK_TOKEN)';
|
|
17
|
+
export const envKey = 'SNYK_TOKEN';
|
|
18
|
+
|
|
19
|
+
export async function fetchAll() {
|
|
20
|
+
const token = process.env.SNYK_TOKEN;
|
|
21
|
+
const orgId = process.env.SNYK_ORG_ID;
|
|
22
|
+
if (!token) return { skipped: true, reason: 'SNYK_TOKEN not set' };
|
|
23
|
+
if (!orgId) return { skipped: true, reason: 'SNYK_ORG_ID not set' };
|
|
24
|
+
|
|
25
|
+
const headers = { Authorization: `token ${token}` };
|
|
26
|
+
const url = `https://api.snyk.io/rest/orgs/${orgId}/issues?version=2024-10-15&limit=100`;
|
|
27
|
+
const data = await fetchJson(url, { headers });
|
|
28
|
+
|
|
29
|
+
const issues = (data.data || []).map(item => ({
|
|
30
|
+
id: item.id,
|
|
31
|
+
severity: item.attributes?.effective_severity_level,
|
|
32
|
+
title: item.attributes?.title,
|
|
33
|
+
package: item.relationships?.scan_item?.data?.id,
|
|
34
|
+
cves: item.attributes?.problems?.filter(p => p.source === 'CVE').map(p => p.id) || [],
|
|
35
|
+
cvss: item.attributes?.cvss_v3,
|
|
36
|
+
published: item.attributes?.created_at,
|
|
37
|
+
}));
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
orgId,
|
|
41
|
+
count: issues.length,
|
|
42
|
+
issues,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const TTL = TTL_MS;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Socket.dev — paid.
|
|
3
|
+
* https://docs.socket.dev/reference
|
|
4
|
+
*
|
|
5
|
+
* Requires SOCKET_API_KEY. Best-in-class supply-chain risk scoring for npm
|
|
6
|
+
* (typosquats, install-script risk, dependency drift, malware). Skipped
|
|
7
|
+
* silently when not set.
|
|
8
|
+
*
|
|
9
|
+
* For the merged feed we pull the recent malware advisories list. Live
|
|
10
|
+
* package lookup is exposed via lookupLive() and called by the supply-chain
|
|
11
|
+
* agent during a scan.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { fetchJson } from '../http.js';
|
|
15
|
+
|
|
16
|
+
const TTL_MS = 6 * 60 * 60 * 1000; // 6h — Socket updates often
|
|
17
|
+
|
|
18
|
+
export const name = 'socket';
|
|
19
|
+
export const tier = 'optional';
|
|
20
|
+
export const description = 'Socket.dev supply-chain risk feed (requires SOCKET_API_KEY)';
|
|
21
|
+
export const envKey = 'SOCKET_API_KEY';
|
|
22
|
+
|
|
23
|
+
function authHeader() {
|
|
24
|
+
const key = process.env.SOCKET_API_KEY;
|
|
25
|
+
if (!key) return null;
|
|
26
|
+
// Socket uses HTTP Basic with the API key as username, blank password
|
|
27
|
+
const b64 = Buffer.from(`${key}:`).toString('base64');
|
|
28
|
+
return { Authorization: `Basic ${b64}` };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function fetchAll() {
|
|
32
|
+
const headers = authHeader();
|
|
33
|
+
if (!headers) return { skipped: true, reason: 'SOCKET_API_KEY not set' };
|
|
34
|
+
|
|
35
|
+
const data = await fetchJson('https://api.socket.dev/v0/threat-feed?limit=200', {
|
|
36
|
+
headers,
|
|
37
|
+
timeout: 20000,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
count: (data.results || []).length,
|
|
42
|
+
advisories: (data.results || []).map(t => ({
|
|
43
|
+
id: t.id,
|
|
44
|
+
package: t.purl || t.name,
|
|
45
|
+
ecosystem: t.ecosystem,
|
|
46
|
+
severity: t.severity,
|
|
47
|
+
summary: t.summary || t.description,
|
|
48
|
+
published: t.created_at,
|
|
49
|
+
threatType: t.threat_type,
|
|
50
|
+
})),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Live single-package lookup. Used by supply-chain agent. */
|
|
55
|
+
export async function lookupLive(pkg, ecosystem = 'npm') {
|
|
56
|
+
const headers = authHeader();
|
|
57
|
+
if (!headers) return null;
|
|
58
|
+
try {
|
|
59
|
+
const data = await fetchJson(
|
|
60
|
+
`https://api.socket.dev/v0/${ecosystem}/${encodeURIComponent(pkg)}/score`,
|
|
61
|
+
{ headers, timeout: 8000, retries: 1 },
|
|
62
|
+
);
|
|
63
|
+
return data;
|
|
64
|
+
} catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const TTL = TTL_MS;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sonatype OSS Index — free with low rate limits, paid for higher volume.
|
|
3
|
+
* https://ossindex.sonatype.org/rest
|
|
4
|
+
*
|
|
5
|
+
* Optional auth via SONATYPE_USER + SONATYPE_TOKEN raises the limit.
|
|
6
|
+
* Operates per-batch component lookup (POST /api/v3/component-report).
|
|
7
|
+
* For the merged feed we fetch a small recent-vulns sample; primary use
|
|
8
|
+
* is the lookupLive() helper at scan time.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { fetchJson } from '../http.js';
|
|
12
|
+
|
|
13
|
+
const TTL_MS = 12 * 60 * 60 * 1000;
|
|
14
|
+
|
|
15
|
+
export const name = 'sonatype';
|
|
16
|
+
export const tier = 'optional';
|
|
17
|
+
export const description = 'Sonatype OSS Index (anonymous works; SONATYPE_USER/TOKEN raises limits)';
|
|
18
|
+
export const envKey = 'SONATYPE_USER';
|
|
19
|
+
|
|
20
|
+
function authHeader() {
|
|
21
|
+
const user = process.env.SONATYPE_USER;
|
|
22
|
+
const token = process.env.SONATYPE_TOKEN;
|
|
23
|
+
if (!user || !token) return null;
|
|
24
|
+
const b64 = Buffer.from(`${user}:${token}`).toString('base64');
|
|
25
|
+
return { Authorization: `Basic ${b64}` };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function fetchAll() {
|
|
29
|
+
// Default seed: query a known-noisy set so users get sample data without
|
|
30
|
+
// configuring credentials. This is the only source where a no-key call
|
|
31
|
+
// still yields useful seed records.
|
|
32
|
+
const headers = { 'content-type': 'application/json', ...(authHeader() || {}) };
|
|
33
|
+
const seed = ['pkg:npm/lodash@4.17.20', 'pkg:npm/axios@0.21.0', 'pkg:pypi/requests@2.19.0'];
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
const data = await fetchJson('https://ossindex.sonatype.org/api/v3/component-report', {
|
|
37
|
+
method: 'POST',
|
|
38
|
+
headers,
|
|
39
|
+
body: JSON.stringify({ coordinates: seed }),
|
|
40
|
+
timeout: 15000,
|
|
41
|
+
});
|
|
42
|
+
const advisories = (Array.isArray(data) ? data : []).flatMap(report =>
|
|
43
|
+
(report.vulnerabilities || []).map(v => ({
|
|
44
|
+
purl: report.coordinates,
|
|
45
|
+
cve: (v.cve || v.id),
|
|
46
|
+
cvssScore: v.cvssScore,
|
|
47
|
+
cvssVector: v.cvssVector,
|
|
48
|
+
title: v.title,
|
|
49
|
+
description: v.description?.slice(0, 200),
|
|
50
|
+
reference: v.reference,
|
|
51
|
+
}))
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
authed: !!authHeader(),
|
|
56
|
+
count: advisories.length,
|
|
57
|
+
advisories,
|
|
58
|
+
};
|
|
59
|
+
} catch (err) {
|
|
60
|
+
if (!authHeader()) {
|
|
61
|
+
return { skipped: true, reason: `Anonymous query failed (${err.message}). Set SONATYPE_USER/TOKEN.` };
|
|
62
|
+
}
|
|
63
|
+
throw err;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Live PURL lookup for use in scan-time enrichment. */
|
|
68
|
+
export async function lookupLive(purls = []) {
|
|
69
|
+
const headers = { 'content-type': 'application/json', ...(authHeader() || {}) };
|
|
70
|
+
if (!purls.length) return [];
|
|
71
|
+
try {
|
|
72
|
+
return await fetchJson('https://ossindex.sonatype.org/api/v3/component-report', {
|
|
73
|
+
method: 'POST',
|
|
74
|
+
headers,
|
|
75
|
+
body: JSON.stringify({ coordinates: purls.slice(0, 128) }),
|
|
76
|
+
timeout: 12000,
|
|
77
|
+
retries: 1,
|
|
78
|
+
});
|
|
79
|
+
} catch {
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export const TTL = TTL_MS;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Praxis Threat-Pack — AI attack-vector signature pack (core feed)
|
|
3
|
+
* ===============================================================
|
|
4
|
+
*
|
|
5
|
+
* A versioned, fetchable pack of AI-security detection knowledge:
|
|
6
|
+
* - probes: prompt-injection signatures (same schema as the bundled
|
|
7
|
+
* corpus — new jailbreak/obfuscation families arrive as data)
|
|
8
|
+
* - eaa: Endpoint-AI-Agent-Abuse technique additions
|
|
9
|
+
* - gateways: known model-gateway domains (allowlist updates)
|
|
10
|
+
*
|
|
11
|
+
* The bundled seed lives at cli/data/threatpacks/latest.json. The feed
|
|
12
|
+
* URL can be overridden with PRAXIS_THREATPACK_URL (defaults to the seed
|
|
13
|
+
* version on GitHub). Graceful degradation: a fetch failure marks the
|
|
14
|
+
* source failed and the scan continues with the bundled corpus.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import fs from 'fs';
|
|
18
|
+
import path from 'path';
|
|
19
|
+
import { fileURLToPath } from 'url';
|
|
20
|
+
import { fetchText } from '../http.js';
|
|
21
|
+
|
|
22
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
23
|
+
const SEED_PATH = path.join(__dirname, '..', '..', '..', 'data', 'threatpacks', 'latest.json');
|
|
24
|
+
|
|
25
|
+
const DEFAULT_URL = 'https://raw.githubusercontent.com/Ganron007/Praxis/main/cli/data/threatpacks/latest.json';
|
|
26
|
+
const TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7d
|
|
27
|
+
|
|
28
|
+
export const name = 'threatpack';
|
|
29
|
+
export const tier = 'core';
|
|
30
|
+
export const description = 'Praxis AI attack-vector signature pack (probes, EAA additions, gateway allowlists)';
|
|
31
|
+
|
|
32
|
+
function loadSeed() {
|
|
33
|
+
try {
|
|
34
|
+
return JSON.parse(fs.readFileSync(SEED_PATH, 'utf-8'));
|
|
35
|
+
} catch {
|
|
36
|
+
return { version: '0.0.0', probes: [], eaa: [], gateways: [] };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function fetchAll() {
|
|
41
|
+
const seed = loadSeed();
|
|
42
|
+
const url = process.env.PRAXIS_THREATPACK_URL || DEFAULT_URL;
|
|
43
|
+
|
|
44
|
+
// Try the remote pack; fall back to the bundled seed on any failure.
|
|
45
|
+
try {
|
|
46
|
+
const raw = await fetchText(url);
|
|
47
|
+
const pack = JSON.parse(raw);
|
|
48
|
+
if (!pack.version || !Array.isArray(pack.probes)) {
|
|
49
|
+
throw new Error('malformed threat pack');
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
sourceUrl: url,
|
|
53
|
+
version: pack.version,
|
|
54
|
+
probes: pack.probes,
|
|
55
|
+
eaa: pack.eaa || [],
|
|
56
|
+
gateways: pack.gateways || [],
|
|
57
|
+
fetchedRemote: true,
|
|
58
|
+
};
|
|
59
|
+
} catch {
|
|
60
|
+
return {
|
|
61
|
+
sourceUrl: SEED_PATH,
|
|
62
|
+
version: seed.version,
|
|
63
|
+
probes: seed.probes || [],
|
|
64
|
+
eaa: seed.eaa || [],
|
|
65
|
+
gateways: seed.gateways || [],
|
|
66
|
+
fetchedRemote: false,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}
|