fullshield 0.0.1 → 0.1.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.
Files changed (67) hide show
  1. package/CHANGELOG.md +77 -0
  2. package/LICENSE +21 -0
  3. package/README.md +155 -1
  4. package/dist/agent.js +5737 -0
  5. package/dist/cli/index.js +8540 -0
  6. package/dist/compliance.js +126 -0
  7. package/dist/confirm.js +146 -0
  8. package/dist/dashboard.js +121 -0
  9. package/dist/iac.js +84 -0
  10. package/dist/incremental.js +13 -0
  11. package/dist/index.d.ts +334 -0
  12. package/dist/index.js +6180 -0
  13. package/dist/llm.js +105 -0
  14. package/dist/mcp.js +5831 -0
  15. package/dist/runtime/auto.js +1163 -0
  16. package/dist/runtime/express.js +229 -0
  17. package/dist/runtime/fastify.js +241 -0
  18. package/dist/runtime/index.js +216 -0
  19. package/dist/runtime/rasp/loader.js +14 -0
  20. package/dist/runtime/rasp.js +1339 -0
  21. package/dist/runtime/register.js +1171 -0
  22. package/dist/supply.js +228 -0
  23. package/dist/threat.js +63 -0
  24. package/dist/workspaces.js +23 -0
  25. package/package.json +85 -8
  26. package/rules/CONFIG-BODYLIMIT-001.md +9 -0
  27. package/rules/CONFIG-CORS-001.md +9 -0
  28. package/rules/CONFIG-DOCKER-ENV-001.md +9 -0
  29. package/rules/CONFIG-DOCKER-LATEST-001.md +9 -0
  30. package/rules/CONFIG-DOCKER-ROOT-001.md +9 -0
  31. package/rules/CONFIG-ERRSTACK-001.md +9 -0
  32. package/rules/CONFIG-HELMET-001.md +9 -0
  33. package/rules/CONFIG-INSTALLSCRIPT-001.md +9 -0
  34. package/rules/CONFIG-RATELIMIT-001.md +9 -0
  35. package/rules/DEPS-DEPRECATED-001.md +9 -0
  36. package/rules/DEPS-NOLOCK-001.md +9 -0
  37. package/rules/DEPS-TYPOSQUAT-001.md +9 -0
  38. package/rules/DEPS-VULN-001.md +9 -0
  39. package/rules/README.md +60 -0
  40. package/rules/SAST-AUTH-001.md +9 -0
  41. package/rules/SAST-CMDI-001.md +9 -0
  42. package/rules/SAST-CRYPTO-001.md +9 -0
  43. package/rules/SAST-EVAL-001.md +9 -0
  44. package/rules/SAST-JWT-001.md +9 -0
  45. package/rules/SAST-NOSQLI-001.md +9 -0
  46. package/rules/SAST-PATH-001.md +9 -0
  47. package/rules/SAST-PROTO-001.md +9 -0
  48. package/rules/SAST-REDIRECT-001.md +9 -0
  49. package/rules/SAST-SQLI-001.md +9 -0
  50. package/rules/SAST-SSRF-001.md +9 -0
  51. package/rules/SAST-XSS-001.md +9 -0
  52. package/rules/SECRET-AIKEY-001.md +9 -0
  53. package/rules/SECRET-AWS-001.md +9 -0
  54. package/rules/SECRET-DBURL-001.md +9 -0
  55. package/rules/SECRET-ENTROPY-001.md +9 -0
  56. package/rules/SECRET-ENVFILE-001.md +9 -0
  57. package/rules/SECRET-GITHUB-001.md +9 -0
  58. package/rules/SECRET-GOOGLE-001.md +9 -0
  59. package/rules/SECRET-JWT-001.md +9 -0
  60. package/rules/SECRET-PRIVKEY-001.md +9 -0
  61. package/rules/SECRET-SLACK-001.md +9 -0
  62. package/rules/SECRET-STRIPE-001.md +9 -0
  63. package/templates/fullshield.config.json +7 -0
  64. package/templates/github-watch.yml +15 -0
  65. package/templates/github-workflow.yml +19 -0
  66. package/templates/gitlab-ci.yml +11 -0
  67. package/templates/pre-commit.sh +11 -0
@@ -0,0 +1,126 @@
1
+ // src/compliance/index.ts
2
+ var COMPLIANCE_DISCLAIMER = "This is not a certification or audit. FullShield produces an evidence-style mapping only; it does not certify PCI DSS, SOC 2, ISO, or any other standard.";
3
+ var ROW = (ruleId, owasp, cwe, asvs, pci) => ({
4
+ ruleId,
5
+ owasp,
6
+ cwe,
7
+ asvs,
8
+ pci
9
+ });
10
+ var RULE_MAP = [
11
+ ROW("SAST-SQLI-001", "A03:2021 Injection", "CWE-89", "V5.3.4", "PCI DSS 6.2.4"),
12
+ ROW("SAST-NOSQLI-001", "A03:2021 Injection", "CWE-943", "V5.3.4", "PCI DSS 6.2.4"),
13
+ ROW("SAST-CMDI-001", "A03:2021 Injection", "CWE-78", "V5.3.3", "PCI DSS 6.2.4"),
14
+ ROW("SAST-PATH-001", "A01:2021 Broken Access Control", "CWE-22", "V12.3.1", "PCI DSS 6.2.4"),
15
+ ROW("SAST-EVAL-001", "A03:2021 Injection", "CWE-95", "V5.3.3", "PCI DSS 6.2.4"),
16
+ ROW("SAST-SSRF-001", "A10:2021 SSRF", "CWE-918", "V12.6.1", "PCI DSS 6.2.4"),
17
+ ROW("SAST-REDIRECT-001", "A01:2021 Broken Access Control", "CWE-601", "V5.1.1", "PCI DSS 6.4.2"),
18
+ ROW("SAST-PROTO-001", "A03:2021 Injection", "CWE-1321", "V5.3.3", "PCI DSS 6.2.4"),
19
+ ROW("SAST-XSS-001", "A03:2021 Injection", "CWE-79", "V5.3.3", "PCI DSS 6.2.4"),
20
+ ROW("SAST-JWT-001", "A07:2021 Identification Failures", "CWE-347", "V3.5.3", "PCI DSS 6.5.10"),
21
+ ROW("SAST-CRYPTO-001", "A02:2021 Cryptographic Failures", "CWE-338", "V6.2.1", "PCI DSS 3.6 / 4.2"),
22
+ ROW("SAST-AUTH-001", "A07:2021 Identification Failures", "CWE-306", "V4.1.1", "PCI DSS 8.2"),
23
+ ROW("SECRET-AWS-001", "A02:2021 Cryptographic Failures", "CWE-798", "V6.4.1", "PCI DSS 3.2 / 8.3"),
24
+ ROW("SECRET-STRIPE-001", "A02:2021 Cryptographic Failures", "CWE-798", "V6.4.1", "PCI DSS 3.2"),
25
+ ROW("SECRET-GITHUB-001", "A02:2021 Cryptographic Failures", "CWE-798", "V6.4.1", "PCI DSS 8.3"),
26
+ ROW("SECRET-SLACK-001", "A02:2021 Cryptographic Failures", "CWE-798", "V6.4.1", "PCI DSS 8.3"),
27
+ ROW("SECRET-GOOGLE-001", "A02:2021 Cryptographic Failures", "CWE-798", "V6.4.1", "PCI DSS 8.3"),
28
+ ROW("SECRET-AIKEY-001", "A02:2021 Cryptographic Failures", "CWE-798", "V6.4.1", "PCI DSS 8.3"),
29
+ ROW("SECRET-PRIVKEY-001", "A02:2021 Cryptographic Failures", "CWE-321", "V6.4.1", "PCI DSS 3.5 / 4.2"),
30
+ ROW("SECRET-DBURL-001", "A02:2021 Cryptographic Failures", "CWE-798", "V6.4.1", "PCI DSS 3.2 / 8.3"),
31
+ ROW("SECRET-JWT-001", "A02:2021 Cryptographic Failures", "CWE-798", "V6.4.1", "PCI DSS 3.5"),
32
+ ROW("SECRET-ENVFILE-001", "A02:2021 Cryptographic Failures", "CWE-312", "V8.3.4", "PCI DSS 3.2"),
33
+ ROW("SECRET-ENTROPY-001", "A02:2021 Cryptographic Failures", "CWE-798", "V6.4.1", "PCI DSS 8.3"),
34
+ ROW("DEPS-VULN-001", "A06:2021 Vulnerable Components", "CWE-1104", "V14.2.1", "PCI DSS 6.3.3"),
35
+ ROW("DEPS-MALICIOUS-001", "A06:2021 Vulnerable Components", "CWE-506", "V14.2.1", "PCI DSS 6.3.3 / 12.10"),
36
+ ROW("DEPS-TYPOSQUAT-001", "A06:2021 Vulnerable Components", "CWE-494", "V14.2.1", "PCI DSS 6.3.3"),
37
+ ROW("DEPS-NOLOCK-001", "A06:2021 Vulnerable Components", "CWE-1104", "V14.2.1", "PCI DSS 6.3.3"),
38
+ ROW("DEPS-DEPRECATED-001", "A06:2021 Vulnerable Components", "CWE-1104", "V14.2.1", "PCI DSS 6.3.3"),
39
+ ROW("CONFIG-HELMET-001", "A05:2021 Security Misconfiguration", "CWE-693", "V14.4.3", "PCI DSS 6.4.3"),
40
+ ROW("CONFIG-BODYLIMIT-001", "A05:2021 Security Misconfiguration", "CWE-400", "V13.1.1", "PCI DSS 6.2.4"),
41
+ ROW("CONFIG-CORS-001", "A05:2021 Security Misconfiguration", "CWE-942", "V14.4.5", "PCI DSS 6.4.3"),
42
+ ROW("CONFIG-RATELIMIT-001", "A07:2021 Identification Failures", "CWE-307", "V2.2.1", "PCI DSS 8.3.4"),
43
+ ROW("CONFIG-ERRSTACK-001", "A04:2021 Insecure Design", "CWE-209", "V7.4.1", "PCI DSS 6.2.4"),
44
+ ROW("CONFIG-INSTALLSCRIPT-001", "A06:2021 Vulnerable Components", "CWE-829", "V14.2.1", "PCI DSS 6.3.3"),
45
+ ROW("CONFIG-DOCKER-LATEST-001", "A05:2021 Security Misconfiguration", "CWE-1188", "V14.1.3", "PCI DSS 2.2"),
46
+ ROW("CONFIG-DOCKER-ROOT-001", "A05:2021 Security Misconfiguration", "CWE-250", "V14.1.3", "PCI DSS 2.2"),
47
+ ROW("CONFIG-DOCKER-ENV-001", "A02:2021 Cryptographic Failures", "CWE-798", "V6.4.1", "PCI DSS 3.2"),
48
+ ROW("EDGE-CDN-001", "A05:2021 Security Misconfiguration", "CWE-693", "V14.4.3", "PCI DSS 1.2 / 6.4"),
49
+ ROW("EDGE-CSP-001", "A05:2021 Security Misconfiguration", "CWE-693", "V14.4.3", "PCI DSS 6.4.3"),
50
+ ROW("EDGE-EXPOSE-001", "A05:2021 Security Misconfiguration", "CWE-538", "V14.4.3", "PCI DSS 2.2 / 6.4"),
51
+ ROW("EDGE-HSTS-001", "A05:2021 Security Misconfiguration", "CWE-319", "V9.1.1", "PCI DSS 4.2"),
52
+ ROW("EDGE-REFERRER-001", "A05:2021 Security Misconfiguration", "CWE-201", "V14.4.3", "PCI DSS 6.4.3"),
53
+ ROW("EDGE-TLS-001", "A02:2021 Cryptographic Failures", "CWE-319", "V9.1.1", "PCI DSS 4.2"),
54
+ ROW("EDGE-XCTO-001", "A05:2021 Security Misconfiguration", "CWE-693", "V14.4.3", "PCI DSS 6.4.3"),
55
+ ROW("EDGE-XFO-001", "A05:2021 Security Misconfiguration", "CWE-1021", "V14.4.3", "PCI DSS 6.4.3"),
56
+ ROW("RASP-SQL-001", "A03:2021 Injection", "CWE-89", "V5.3.4", "PCI DSS 6.4.2"),
57
+ ROW("RASP-SHELL-001", "A03:2021 Injection", "CWE-78", "V5.3.3", "PCI DSS 6.4.2"),
58
+ ROW("RASP-PATH-001", "A01:2021 Broken Access Control", "CWE-22", "V12.3.1", "PCI DSS 6.4.2"),
59
+ ROW("RASP-NOSQL-001", "A03:2021 Injection", "CWE-943", "V5.3.4", "PCI DSS 6.4.2"),
60
+ ROW("RASP-SSRF-001", "A10:2021 SSRF", "CWE-918", "V12.6.1", "PCI DSS 1.3"),
61
+ ROW("RASP-PROTO-001", "A03:2021 Injection", "CWE-1321", "V5.3.3", "PCI DSS 6.4.2"),
62
+ ROW("AI-INJECT-001", "A03:2021 Injection", "CWE-1427", "V5.3.3", "PCI DSS 6.2.4"),
63
+ ROW("LLM-001", "A03:2021 Injection", "CWE-1426", "V5.3.3", "PCI DSS 6.2.4"),
64
+ ROW("LLM-002", "A03:2021 Injection", "CWE-94", "V5.3.3", "PCI DSS 6.2.4"),
65
+ ROW("LLM-003", "A01:2021 Broken Access Control", "CWE-250", "V4.1.1", "PCI DSS 7.2"),
66
+ ROW("LLM-004", "A02:2021 Cryptographic Failures", "CWE-798", "V6.4.1", "PCI DSS 3.2"),
67
+ ROW("LLM-005", "A04:2021 Insecure Design", "CWE-770", "V13.1.1", "PCI DSS 6.2.4"),
68
+ ROW("SUPPLY-INSTALLSCRIPT-001", "A06:2021 Vulnerable Components", "CWE-829", "V14.2.1", "PCI DSS 6.3.3"),
69
+ ROW("SUPPLY-NPMRC-NET-001", "A06:2021 Vulnerable Components", "CWE-506", "V14.2.1", "PCI DSS 12.10"),
70
+ ROW("SUPPLY-OBFUSCATE-001", "A06:2021 Vulnerable Components", "CWE-506", "V14.2.1", "PCI DSS 6.3.3"),
71
+ ROW("SUPPLY-BEHAVIOR-001", "A06:2021 Vulnerable Components", "CWE-829", "V14.2.1", "PCI DSS 6.3.3"),
72
+ ROW("SUPPLY-COOLDOWN-001", "A06:2021 Vulnerable Components", "CWE-1104", "V14.2.1", "PCI DSS 6.3.3"),
73
+ ROW("SUPPLY-LOCKURL-001", "A08:2021 Integrity Failures", "CWE-494", "V14.2.3", "PCI DSS 6.3.3"),
74
+ ROW("SUPPLY-CONFUSION-001", "A08:2021 Integrity Failures", "CWE-494", "V14.2.3", "PCI DSS 6.3.3"),
75
+ ROW("SUPPLY-PROVENANCE-001", "A08:2021 Integrity Failures", "CWE-494", "V14.2.3", "PCI DSS 6.3.3"),
76
+ ROW("IAC-GHA-UNPINNED-001", "A08:2021 Integrity Failures", "CWE-494", "V14.2.3", "PCI DSS 6.3.3"),
77
+ ROW("IAC-GHA-PRTARGET-001", "A01:2021 Broken Access Control", "CWE-269", "V4.1.1", "PCI DSS 7.2"),
78
+ ROW("IAC-GHA-PERMS-001", "A01:2021 Broken Access Control", "CWE-250", "V4.1.1", "PCI DSS 7.2"),
79
+ ROW("IAC-GHA-SECRETLOG-001", "A09:2021 Security Logging Failures", "CWE-532", "V7.1.1", "PCI DSS 3.2 / 10.2"),
80
+ ROW("IAC-TF-S3PUBLIC-001", "A01:2021 Broken Access Control", "CWE-284", "V12.1.1", "PCI DSS 1.3 / 3.2"),
81
+ ROW("IAC-TF-SGOPEN-001", "A05:2021 Security Misconfiguration", "CWE-284", "V1.4.1", "PCI DSS 1.2 / 1.3"),
82
+ ROW("IAC-TF-UNENCRYPTED-001", "A02:2021 Cryptographic Failures", "CWE-311", "V6.2.1", "PCI DSS 3.4 / 4.2"),
83
+ ROW("IAC-K8S-PRIV-001", "A05:2021 Security Misconfiguration", "CWE-250", "V14.1.3", "PCI DSS 2.2"),
84
+ ROW("IAC-COMPOSE-PRIV-001", "A05:2021 Security Misconfiguration", "CWE-250", "V14.1.3", "PCI DSS 2.2"),
85
+ ROW("RASP-EGRESS-001", "A10:2021 SSRF", "CWE-918", "V12.6.1", "PCI DSS 1.3"),
86
+ ROW("RASP-HONEY-001", "A09:2021 Security Logging Failures", "CWE-778", "V7.1.1", "PCI DSS 10.2 / 11.5"),
87
+ ROW("RASP-FAIL-OPEN-001", "A05:2021 Security Misconfiguration", "CWE-755", "V14.4.3", "PCI DSS 6.4.3"),
88
+ ROW("RUNTIME-SQLI-001", "A03:2021 Injection", "CWE-89", "V5.3.4", "PCI DSS 6.4.2"),
89
+ ROW("RUNTIME-XSS-001", "A03:2021 Injection", "CWE-79", "V5.3.3", "PCI DSS 6.4.2"),
90
+ ROW("RUNTIME-PATH-001", "A01:2021 Broken Access Control", "CWE-22", "V12.3.1", "PCI DSS 6.4.2"),
91
+ ROW("RUNTIME-RATELIMIT-001", "A07:2021 Identification Failures", "CWE-307", "V2.2.1", "PCI DSS 8.3.4"),
92
+ ROW("RUNTIME-BODY-001", "A05:2021 Security Misconfiguration", "CWE-400", "V13.1.1", "PCI DSS 6.2.4"),
93
+ ROW("RUNTIME-ANOMALY-001", "A04:2021 Insecure Design", "CWE-754", "V14.4.3", "PCI DSS 10.2"),
94
+ ROW("AI-IDOR-001", "A01:2021 Broken Access Control", "CWE-639", "V4.1.1", "PCI DSS 7.2"),
95
+ ROW("AI-MASSASSIGN-001", "A01:2021 Broken Access Control", "CWE-915", "V5.1.1", "PCI DSS 6.2.4")
96
+ ];
97
+ var byId = new Map(RULE_MAP.map((r) => [r.ruleId, r]));
98
+ function mappingFor(ruleId) {
99
+ return byId.get(ruleId);
100
+ }
101
+ function renderCompliance(findings, standard = "pci") {
102
+ const rows = findings.map((f) => {
103
+ const m = mappingFor(f.ruleId);
104
+ return `${f.ruleId} ${f.severity ?? ""} ${f.file ?? ""} ${m?.pci ?? "unmapped"} ${m?.owasp ?? ""} ${m?.cwe ?? ""}`;
105
+ });
106
+ return [
107
+ `FullShield compliance mapping (${standard})`,
108
+ COMPLIANCE_DISCLAIMER,
109
+ "",
110
+ "ruleId severity file pci owasp cwe",
111
+ ...rows,
112
+ "",
113
+ COMPLIANCE_DISCLAIMER
114
+ ].join("\n");
115
+ }
116
+ function collectRuleIdsFromSources(texts) {
117
+ const ids = /* @__PURE__ */ new Set();
118
+ const re = /\b((?:SAST|SECRET|DEPS|CONFIG|EDGE|AI|LLM|SUPPLY|IAC|RASP|RUNTIME)-[A-Z0-9]+-\d{3})\b/g;
119
+ for (const text of texts) {
120
+ let m;
121
+ while ((m = re.exec(text)) !== null) ids.add(m[1] ?? "");
122
+ }
123
+ return [...ids].sort();
124
+ }
125
+
126
+ export { COMPLIANCE_DISCLAIMER, RULE_MAP, collectRuleIdsFromSources, mappingFor, renderCompliance };
@@ -0,0 +1,146 @@
1
+ // src/confirm/index.ts
2
+ var DESTRUCTIVE = /\b(drop|delete|update|insert|truncate|alter)\b|\brm\s|unlink|mkfs|\bdd\s|format\s|>\s*\/dev/i;
3
+ var containers = /* @__PURE__ */ new Map();
4
+ function registerConfirmContainer(entry) {
5
+ containers.set(entry.id, entry);
6
+ }
7
+ function resetConfirmContainers() {
8
+ containers.clear();
9
+ }
10
+ function hostnameOf(raw) {
11
+ const href = raw.includes("://") ? raw : `http://${raw}`;
12
+ const url = new URL(href);
13
+ const m = href.match(/^https?:\/\/([^/?#]+)/i);
14
+ let authority = m?.[1] ?? url.hostname;
15
+ if (authority.includes("@")) authority = authority.slice(authority.lastIndexOf("@") + 1);
16
+ let rawHost;
17
+ if (authority.startsWith("[")) {
18
+ const end = authority.indexOf("]");
19
+ rawHost = end >= 0 ? authority.slice(0, end + 1) : authority;
20
+ } else {
21
+ rawHost = authority.split(":")[0] ?? url.hostname;
22
+ }
23
+ return { host: url.hostname, href: url.href, rawHost };
24
+ }
25
+ function isLiteralLocal(host) {
26
+ return host === "localhost" || host === "127.0.0.1";
27
+ }
28
+ function isRegisteredContainer(host) {
29
+ for (const c of containers.values()) {
30
+ if (c.host === host || c.ip === host) return true;
31
+ }
32
+ return false;
33
+ }
34
+ function assertLocalTarget(raw, opts = {}) {
35
+ let host;
36
+ let rawHost;
37
+ try {
38
+ const parsed = hostnameOf(raw);
39
+ host = parsed.host;
40
+ rawHost = parsed.rawHost;
41
+ } catch {
42
+ throw new Error(`confirm: refused target ${raw}`);
43
+ }
44
+ if (isLiteralLocal(rawHost) && isLiteralLocal(host)) return;
45
+ if (isRegisteredContainer(host) || isRegisteredContainer(rawHost)) return;
46
+ if (opts.lookup) {
47
+ const ips = opts.lookup(host);
48
+ throw new Error(`confirm: refused target ${raw} (resolved ${ips.join(",")})`);
49
+ }
50
+ throw new Error(`confirm: refused target ${raw} (host ${host} is not localhost/127.0.0.1/FullShield container)`);
51
+ }
52
+ function buildCanary(kind, token) {
53
+ if (DESTRUCTIVE.test(token)) {
54
+ throw new Error("confirm: canary must be harmless (no DROP/DELETE/rm/dd)");
55
+ }
56
+ if (kind === "sql") return `1 UNION SELECT 0,'${token}'`;
57
+ if (kind === "shell") return `; echo ${token}`;
58
+ if (kind === "path") return "../canary.txt";
59
+ return token;
60
+ }
61
+ function curlProof(method, url, extra = "") {
62
+ return `curl -sS -X ${method} ${extra}${url}`.trim();
63
+ }
64
+ async function probe(fetchImpl, url, method, body, headers) {
65
+ try {
66
+ const res = await fetchImpl(url, {
67
+ method,
68
+ headers: {
69
+ ...body !== void 0 ? { "content-type": "application/json" } : {},
70
+ ...headers
71
+ },
72
+ body: body !== void 0 ? JSON.stringify(body) : void 0
73
+ });
74
+ const text = await res.text();
75
+ return { ok: res.ok, text };
76
+ } catch (err) {
77
+ return { ok: false, text: err instanceof Error ? err.message : String(err) };
78
+ }
79
+ }
80
+ async function confirmTarget(baseUrl, opts) {
81
+ if (!opts.experimental && process.env.FULLSHIELD_EXPERIMENTAL_CONFIRM !== "1") {
82
+ return { confirmations: [], skipped: true };
83
+ }
84
+ try {
85
+ assertLocalTarget(baseUrl);
86
+ } catch (err) {
87
+ return {
88
+ confirmations: [
89
+ {
90
+ kind: "sql",
91
+ status: "unconfirmed",
92
+ detail: err instanceof Error ? err.message : String(err)
93
+ }
94
+ ]
95
+ };
96
+ }
97
+ const token = opts.canaryToken ?? `FULLSHIELD_CANARY_${Date.now().toString(16)}`;
98
+ const fetchImpl = opts.fetchImpl ?? fetch;
99
+ const origin = baseUrl.replace(/\/$/, "");
100
+ const confirmations = [];
101
+ for (const route of opts.routes) {
102
+ try {
103
+ if (route.kind === "idor") {
104
+ const url2 = `${origin}${route.path}`;
105
+ const hit2 = await probe(fetchImpl, url2, route.method, void 0, {
106
+ "x-user": route.asUser ?? opts.users?.b ?? "bob"
107
+ });
108
+ const owner = route.expectOwner ?? opts.users?.a ?? "alice";
109
+ const confirmed2 = hit2.ok && hit2.text.includes(owner);
110
+ confirmations.push({
111
+ kind: "idor",
112
+ status: confirmed2 ? "confirmed" : "unconfirmed",
113
+ proof: curlProof(route.method, url2, `-H 'x-user: ${route.asUser ?? "bob"}' `),
114
+ detail: hit2.text.slice(0, 200)
115
+ });
116
+ continue;
117
+ }
118
+ const payload = buildCanary(route.kind, token);
119
+ const param = route.param ?? "q";
120
+ let url = `${origin}${route.path}`;
121
+ let body;
122
+ if (route.method === "GET") {
123
+ url += `?${encodeURIComponent(param)}=${encodeURIComponent(payload)}`;
124
+ } else {
125
+ body = { [param]: payload };
126
+ }
127
+ const hit = await probe(fetchImpl, url, route.method, body);
128
+ const confirmed = hit.text.includes(token) || route.kind === "path" && hit.text.includes("FULLSHIELD_CANARY");
129
+ confirmations.push({
130
+ kind: route.kind,
131
+ status: confirmed ? "confirmed" : "unconfirmed",
132
+ proof: curlProof(route.method, url),
133
+ detail: hit.text.slice(0, 200)
134
+ });
135
+ } catch (err) {
136
+ confirmations.push({
137
+ kind: route.kind,
138
+ status: "unconfirmed",
139
+ detail: err instanceof Error ? err.message : String(err)
140
+ });
141
+ }
142
+ }
143
+ return { confirmations };
144
+ }
145
+
146
+ export { assertLocalTarget, buildCanary, confirmTarget, registerConfirmContainer, resetConfirmContainers };
@@ -0,0 +1,121 @@
1
+ import { mkdirSync, existsSync } from 'fs';
2
+ import { createRequire } from 'module';
3
+ import { createServer } from 'http';
4
+ import { join } from 'path';
5
+
6
+ // src/dashboard/index.ts
7
+ var require2 = createRequire(import.meta.url);
8
+ function sqliteDb(file) {
9
+ const { DatabaseSync } = require2("node:sqlite");
10
+ const db = new DatabaseSync(file);
11
+ db.exec(
12
+ "CREATE TABLE IF NOT EXISTS scans (scannedAt TEXT NOT NULL, total INTEGER NOT NULL, highCrit INTEGER NOT NULL)"
13
+ );
14
+ return {
15
+ list() {
16
+ return db.prepare("SELECT scannedAt, total, highCrit FROM scans ORDER BY scannedAt").all();
17
+ },
18
+ insert(row) {
19
+ db.prepare("INSERT INTO scans (scannedAt, total, highCrit) VALUES (?, ?, ?)").run(
20
+ row.scannedAt,
21
+ row.total,
22
+ row.highCrit
23
+ );
24
+ },
25
+ close() {
26
+ db.close();
27
+ }
28
+ };
29
+ }
30
+ function openHistory(dir) {
31
+ const root = join(dir, ".fullshield");
32
+ mkdirSync(root, { recursive: true });
33
+ return sqliteDb(join(root, "history.db"));
34
+ }
35
+ function recordHistory(db, row) {
36
+ db.insert(row);
37
+ }
38
+ function assertDashboardHost(host) {
39
+ if (host === void 0 || host.length === 0) return false;
40
+ const h = host.split(":")[0]?.toLowerCase() ?? "";
41
+ return h === "localhost" || h === "127.0.0.1";
42
+ }
43
+ function createDashboardHandler(opts) {
44
+ if (!opts.experimental && process.env.FULLSHIELD_EXPERIMENTAL_PLATFORM !== "1") {
45
+ throw new Error("dashboard requires FULLSHIELD_EXPERIMENTAL_PLATFORM=1");
46
+ }
47
+ const db = opts.db ?? openHistory(opts.dbPath);
48
+ return (req, res) => {
49
+ if (!assertDashboardHost(typeof req.headers.host === "string" ? req.headers.host : void 0)) {
50
+ res.writeHead(400, { "content-type": "text/plain" });
51
+ res.end("invalid host");
52
+ return;
53
+ }
54
+ const url = new URL(req.url ?? "/", "http://127.0.0.1");
55
+ if (url.pathname === "/api/ingest" && req.method !== "POST") {
56
+ res.writeHead(405, { allow: "POST" });
57
+ res.end("method not allowed");
58
+ return;
59
+ }
60
+ if (req.method === "POST" && url.pathname === "/api/ingest") {
61
+ const chunks = [];
62
+ req.on("data", (c) => chunks.push(c));
63
+ req.on("end", () => {
64
+ try {
65
+ const body = JSON.parse(Buffer.concat(chunks).toString("utf8"));
66
+ db.insert(body);
67
+ res.writeHead(204);
68
+ res.end();
69
+ } catch {
70
+ res.writeHead(400);
71
+ res.end("bad json");
72
+ }
73
+ });
74
+ return;
75
+ }
76
+ if (req.method === "GET" && url.pathname === "/api/history") {
77
+ res.writeHead(200, { "content-type": "application/json" });
78
+ res.end(JSON.stringify({ scans: db.list() }));
79
+ return;
80
+ }
81
+ if (req.method === "GET" && (url.pathname === "/" || url.pathname === "/index.html")) {
82
+ const scans = db.list();
83
+ const rows = scans.map((s) => `<tr><td>${s.scannedAt}</td><td>${s.total}</td><td>${s.highCrit}</td></tr>`).join("");
84
+ res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
85
+ res.end(
86
+ `<!DOCTYPE html><html><body><h1>FullShield history</h1><table><tr><th>at</th><th>total</th><th>high+crit</th></tr>${rows}</table></body></html>`
87
+ );
88
+ return;
89
+ }
90
+ res.writeHead(404);
91
+ res.end("not found");
92
+ };
93
+ }
94
+ function startDashboard(opts) {
95
+ if (!existsSync(opts.dir)) mkdirSync(opts.dir, { recursive: true });
96
+ const db = openHistory(opts.dir);
97
+ const handler = createDashboardHandler({ dbPath: opts.dir, experimental: opts.experimental, db });
98
+ const server = createServer(handler);
99
+ return new Promise((resolve, reject) => {
100
+ server.listen(opts.port ?? 0, "127.0.0.1", () => {
101
+ const addr = server.address();
102
+ const port = typeof addr === "object" && addr !== null ? addr.port : 0;
103
+ resolve({
104
+ port,
105
+ close: () => new Promise((res, rej) => {
106
+ server.close((err) => {
107
+ try {
108
+ db.close();
109
+ } catch {
110
+ }
111
+ if (err) rej(err);
112
+ else res();
113
+ });
114
+ })
115
+ });
116
+ });
117
+ server.on("error", reject);
118
+ });
119
+ }
120
+
121
+ export { assertDashboardHost, createDashboardHandler, openHistory, recordHistory, startDashboard };
package/dist/iac.js ADDED
@@ -0,0 +1,84 @@
1
+ import { existsSync, statSync, readFileSync, readdirSync } from 'fs';
2
+ import { relative, join } from 'path';
3
+ import { createHash } from 'crypto';
4
+
5
+ // src/iac/index.ts
6
+ function makeFindingId(ruleId, file, snippet) {
7
+ const normalized = (snippet ?? "").replace(/\s+/g, " ").trim();
8
+ return createHash("sha256").update(`${ruleId}\0${file ?? ""}\0${normalized}`).digest("hex").slice(0, 16);
9
+ }
10
+
11
+ // src/iac/index.ts
12
+ function finding(ruleId, title, message, file, severity = "high") {
13
+ return {
14
+ id: makeFindingId(ruleId, file, message),
15
+ ruleId,
16
+ module: "config",
17
+ title,
18
+ severity,
19
+ confidence: "medium",
20
+ file,
21
+ message,
22
+ remediation: "Pin actions by SHA, drop pull_request_target, tighten permissions, close 0.0.0.0/0, encrypt storage, drop privileged."
23
+ };
24
+ }
25
+ function walk(dir, out = []) {
26
+ if (!existsSync(dir)) return out;
27
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
28
+ const full = join(dir, entry.name);
29
+ if (entry.isDirectory()) {
30
+ if (["node_modules", "dist", ".git"].includes(entry.name)) continue;
31
+ walk(full, out);
32
+ } else if (entry.isFile()) {
33
+ out.push(full);
34
+ }
35
+ }
36
+ return out;
37
+ }
38
+ function scanIac(dir, opts = {}) {
39
+ if (!opts.experimental && process.env.FULLSHIELD_EXPERIMENTAL_IAC !== "1") return [];
40
+ if (!existsSync(dir) || !statSync(dir).isDirectory()) return [];
41
+ const findings = [];
42
+ for (const file of walk(dir)) {
43
+ const rel = relative(dir, file).replaceAll("\\", "/");
44
+ const text = readFileSync(file, "utf8");
45
+ if (rel.includes(".github/workflows/") && /\.ya?ml$/.test(rel)) {
46
+ if (/uses:\s+\S+@v\d/.test(text) && !/uses:\s+\S+@[0-9a-f]{40}/.test(text)) {
47
+ findings.push(finding("IAC-GHA-UNPINNED-001", "GitHub Action not pinned to a commit SHA", "uses: tag instead of SHA", rel));
48
+ }
49
+ if (/pull_request_target/.test(text)) {
50
+ findings.push(finding("IAC-GHA-PRTARGET-001", "pull_request_target workflow", "untrusted PR can run with base secrets", rel, "critical"));
51
+ }
52
+ if (/permissions:\s*\n\s+contents:\s*write/.test(text) || /permissions:\s+write-all/.test(text)) {
53
+ findings.push(finding("IAC-GHA-PERMS-001", "Overly broad GitHub Actions permissions", "write-all or contents: write", rel));
54
+ }
55
+ if (/echo\s+\$\{\{\s*secrets\.|console\.(?:log|info|debug)\([^)]*secrets\./i.test(text)) {
56
+ findings.push(finding("IAC-GHA-SECRETLOG-001", "Secret written to workflow logs", "echo or console of secrets.*", rel, "critical"));
57
+ }
58
+ }
59
+ if (/\.tf$/.test(rel)) {
60
+ if (/acl\s*=\s*"public-read"|block_public_acls\s*=\s*false/.test(text)) {
61
+ findings.push(finding("IAC-TF-S3PUBLIC-001", "Public S3 bucket", "public-read or public ACLs enabled", rel, "critical"));
62
+ }
63
+ if (/0\.0\.0\.0\/0/.test(text) && /22|3306|5432/.test(text)) {
64
+ findings.push(finding("IAC-TF-SGOPEN-001", "Security group open to the world on a data/admin port", "0.0.0.0/0 on 22/3306/5432", rel, "critical"));
65
+ }
66
+ if (/encrypted\s*=\s*false/.test(text)) {
67
+ findings.push(finding("IAC-TF-UNENCRYPTED-001", "Unencrypted cloud storage", "encrypted = false", rel));
68
+ }
69
+ }
70
+ if (/deployment\.ya?ml$|pod\.ya?ml$/.test(rel) || rel.includes("k8s/")) {
71
+ if (/privileged:\s*true/.test(text) || /hostNetwork:\s*true/.test(text)) {
72
+ findings.push(finding("IAC-K8S-PRIV-001", "Privileged or hostNetwork Kubernetes pod", "privileged/hostNetwork true", rel, "critical"));
73
+ }
74
+ }
75
+ if (/compose\.ya?ml$|docker-compose\.ya?ml$/.test(rel)) {
76
+ if (/privileged:\s*true/.test(text)) {
77
+ findings.push(finding("IAC-COMPOSE-PRIV-001", "Privileged docker-compose service", "privileged: true", rel, "critical"));
78
+ }
79
+ }
80
+ }
81
+ return findings;
82
+ }
83
+
84
+ export { scanIac };
@@ -0,0 +1,13 @@
1
+ import { spawnSync } from 'child_process';
2
+
3
+ // src/incremental/index.ts
4
+ function resolveChangedFiles(cwd, opts = {}) {
5
+ if (!opts.experimental && process.env.FULLSHIELD_EXPERIMENTAL_PLATFORM !== "1") return [];
6
+ if (!opts.changed && !opts.since) return [];
7
+ const run = opts.git ?? ((args) => spawnSync("git", args, { cwd, encoding: "utf8" }).stdout ?? "");
8
+ const range = opts.since ?? "HEAD";
9
+ const out = opts.changed ? run(["diff", "--name-only", "--cached"]) : run(["diff", "--name-only", range]);
10
+ return out.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
11
+ }
12
+
13
+ export { resolveChangedFiles };