fullshield 0.4.0 → 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/CHANGELOG.md +62 -0
- package/README.md +15 -3
- package/dist/agent.js +50 -10582
- package/dist/bench/score.js +2 -118
- package/dist/cli/index.js +134 -15538
- package/dist/compliance.js +2 -164
- package/dist/confirm.js +2 -146
- package/dist/dashboard.js +2 -179
- package/dist/iac.js +2 -132
- package/dist/incremental.js +2 -13
- package/dist/index.d.ts +191 -5
- package/dist/index.js +94 -11869
- package/dist/llm.js +2 -105
- package/dist/mcp.js +48 -10692
- package/dist/runtime/auto.js +5 -1260
- package/dist/runtime/dlp.js +1 -313
- package/dist/runtime/express.js +1 -579
- package/dist/runtime/fastify.js +1 -579
- package/dist/runtime/hono.js +1 -570
- package/dist/runtime/index.js +1 -229
- package/dist/runtime/koa.js +1 -573
- package/dist/runtime/rasp/loader.js +1 -14
- package/dist/runtime/rasp.js +14 -1436
- package/dist/runtime/register.js +6 -1268
- package/dist/supply.js +8 -324
- package/dist/threat.js +3 -63
- package/dist/vex.js +2 -180
- package/dist/workspaces.js +2 -23
- package/models/index.json +356 -0
- package/package.json +22 -7
- package/rules/DEPS-LOCKMISMATCH-001.md +7 -0
- package/rules/DEPS-PHANTOM-001.md +7 -0
- package/rules/README.md +1 -0
- package/rules/SECRET-AWS-001.md +6 -0
- package/rules/SECRET-GITHUB-001.md +6 -0
- package/rules/SUPPLY-CAP-001.md +17 -0
- package/rules/SUPPLY-PROV-001.md +15 -0
package/dist/runtime/index.js
CHANGED
|
@@ -1,229 +1 @@
|
|
|
1
|
-
|
|
2
|
-
var SQLI = /('\s*or\s+'?1'?\s*='?1)|union\s+select|;\s*drop\s+table|;\s*delete\s+from/i;
|
|
3
|
-
var XSS = /<\s*script\b|javascript:\s*|onerror\s*=/i;
|
|
4
|
-
var PATH = /(\.\.\/|\.\.\\|%2e%2e%2f|%2e%2e\\)/i;
|
|
5
|
-
function flattenValues(value, out = [], depth = 0) {
|
|
6
|
-
if (depth > 8 || out.length > 200) return out;
|
|
7
|
-
if (typeof value === "string") {
|
|
8
|
-
out.push(value);
|
|
9
|
-
return out;
|
|
10
|
-
}
|
|
11
|
-
if (Array.isArray(value)) {
|
|
12
|
-
for (const item of value) flattenValues(item, out, depth + 1);
|
|
13
|
-
return out;
|
|
14
|
-
}
|
|
15
|
-
if (value !== null && typeof value === "object") {
|
|
16
|
-
for (const item of Object.values(value)) {
|
|
17
|
-
flattenValues(item, out, depth + 1);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
return out;
|
|
21
|
-
}
|
|
22
|
-
function detectAttack(text) {
|
|
23
|
-
if (PATH.test(text)) return "path";
|
|
24
|
-
if (XSS.test(text)) return "xss";
|
|
25
|
-
if (SQLI.test(text)) return "sqli";
|
|
26
|
-
return void 0;
|
|
27
|
-
}
|
|
28
|
-
function inspectValues(values) {
|
|
29
|
-
for (const value of values) {
|
|
30
|
-
const hit = detectAttack(value);
|
|
31
|
-
if (hit !== void 0) return hit;
|
|
32
|
-
}
|
|
33
|
-
return void 0;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// src/runtime/lru.ts
|
|
37
|
-
var LruMap = class {
|
|
38
|
-
constructor(max) {
|
|
39
|
-
this.max = max;
|
|
40
|
-
if (max < 1) throw new Error("LruMap max must be >= 1");
|
|
41
|
-
}
|
|
42
|
-
max;
|
|
43
|
-
map = /* @__PURE__ */ new Map();
|
|
44
|
-
get size() {
|
|
45
|
-
return this.map.size;
|
|
46
|
-
}
|
|
47
|
-
has(key) {
|
|
48
|
-
return this.map.has(key);
|
|
49
|
-
}
|
|
50
|
-
get(key) {
|
|
51
|
-
if (!this.map.has(key)) return void 0;
|
|
52
|
-
const value = this.map.get(key);
|
|
53
|
-
this.map.delete(key);
|
|
54
|
-
this.map.set(key, value);
|
|
55
|
-
return value;
|
|
56
|
-
}
|
|
57
|
-
set(key, value) {
|
|
58
|
-
if (this.map.has(key)) this.map.delete(key);
|
|
59
|
-
this.map.set(key, value);
|
|
60
|
-
while (this.map.size > this.max) {
|
|
61
|
-
const oldest = this.map.keys().next().value;
|
|
62
|
-
if (oldest === void 0) break;
|
|
63
|
-
this.map.delete(oldest);
|
|
64
|
-
}
|
|
65
|
-
return this;
|
|
66
|
-
}
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
// src/runtime/rateLimit.ts
|
|
70
|
-
var DEFAULT_MAX_KEYS = 1e4;
|
|
71
|
-
function createRateLimiter(options) {
|
|
72
|
-
const maxKeys = options.maxKeys ?? DEFAULT_MAX_KEYS;
|
|
73
|
-
const hits = new LruMap(maxKeys);
|
|
74
|
-
return {
|
|
75
|
-
allow(key, now = Date.now()) {
|
|
76
|
-
const windowStart = now - options.windowMs;
|
|
77
|
-
const prev = hits.get(key);
|
|
78
|
-
if (prev === void 0) {
|
|
79
|
-
hits.set(key, [now]);
|
|
80
|
-
return true;
|
|
81
|
-
}
|
|
82
|
-
let keep = 0;
|
|
83
|
-
for (let i = 0; i < prev.length; i += 1) {
|
|
84
|
-
const t = prev[i];
|
|
85
|
-
if (t !== void 0 && t > windowStart) {
|
|
86
|
-
prev[keep] = t;
|
|
87
|
-
keep += 1;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
prev.length = keep;
|
|
91
|
-
if (prev.length >= options.max) {
|
|
92
|
-
hits.set(key, prev);
|
|
93
|
-
return false;
|
|
94
|
-
}
|
|
95
|
-
prev.push(now);
|
|
96
|
-
hits.set(key, prev);
|
|
97
|
-
return true;
|
|
98
|
-
},
|
|
99
|
-
get size() {
|
|
100
|
-
return hits.size;
|
|
101
|
-
},
|
|
102
|
-
get maxKeys() {
|
|
103
|
-
return maxKeys;
|
|
104
|
-
}
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// src/runtime/inspect.ts
|
|
109
|
-
var RULE = {
|
|
110
|
-
sqli: "RUNTIME-SQLI-001",
|
|
111
|
-
xss: "RUNTIME-XSS-001",
|
|
112
|
-
path: "RUNTIME-PATH-001"
|
|
113
|
-
};
|
|
114
|
-
function bodyStats(value, depth = 0) {
|
|
115
|
-
if (value === null || value === void 0) return { bytes: 0, depth, keys: 0 };
|
|
116
|
-
if (typeof value === "string") return { bytes: value.length, depth, keys: 0 };
|
|
117
|
-
if (typeof value === "number" || typeof value === "boolean") {
|
|
118
|
-
return { bytes: String(value).length, depth, keys: 0 };
|
|
119
|
-
}
|
|
120
|
-
if (typeof value !== "object") return { bytes: 0, depth, keys: 0 };
|
|
121
|
-
const entries = Object.entries(value);
|
|
122
|
-
let bytes = 2;
|
|
123
|
-
let keys = entries.length;
|
|
124
|
-
let maxDepth = depth;
|
|
125
|
-
for (const [k, v] of entries) {
|
|
126
|
-
bytes += k.length;
|
|
127
|
-
const inner = bodyStats(v, depth + 1);
|
|
128
|
-
bytes += inner.bytes;
|
|
129
|
-
keys += inner.keys;
|
|
130
|
-
if (inner.depth > maxDepth) maxDepth = inner.depth;
|
|
131
|
-
}
|
|
132
|
-
return { bytes, depth: maxDepth, keys };
|
|
133
|
-
}
|
|
134
|
-
var DEFAULT_MAX_ROUTES = 1e3;
|
|
135
|
-
var DEFAULT_MAX_PARAM_NAMES = 200;
|
|
136
|
-
function keysOf(value) {
|
|
137
|
-
if (value === null || value === void 0 || typeof value !== "object") return [];
|
|
138
|
-
return Object.keys(value);
|
|
139
|
-
}
|
|
140
|
-
function createInspector(options = {}) {
|
|
141
|
-
const mode = options.mode ?? "monitor";
|
|
142
|
-
const authRoutes = new Set(options.authRoutes ?? ["/login"]);
|
|
143
|
-
const limiter = createRateLimiter(options.rateLimit ?? { windowMs: 6e4, max: 20 });
|
|
144
|
-
const maxBodyBytes = options.maxBodyBytes ?? 1e6;
|
|
145
|
-
const maxBodyDepth = options.maxBodyDepth ?? 8;
|
|
146
|
-
const maxBodyKeys = options.maxBodyKeys ?? 200;
|
|
147
|
-
const anomaly = options.anomaly;
|
|
148
|
-
const maxRoutes = anomaly?.maxRoutes ?? DEFAULT_MAX_ROUTES;
|
|
149
|
-
const maxParamNames = anomaly?.maxParamNames ?? DEFAULT_MAX_PARAM_NAMES;
|
|
150
|
-
const routes = new LruMap(maxRoutes);
|
|
151
|
-
let windowStart;
|
|
152
|
-
const inspect = ((req) => {
|
|
153
|
-
const path = req.path ?? req.url?.split("?")[0] ?? "/";
|
|
154
|
-
const ip = req.ip ?? "unknown";
|
|
155
|
-
const emit = (ruleId, sample, shouldBlock) => {
|
|
156
|
-
const event = { ruleId, path, ip, sample: sample.slice(0, 40) };
|
|
157
|
-
options.onEvent?.(event);
|
|
158
|
-
if (shouldBlock) options.onBlock?.(event);
|
|
159
|
-
return { block: shouldBlock, event };
|
|
160
|
-
};
|
|
161
|
-
if (authRoutes.has(path) && !limiter.allow(`${ip}:${path}`)) {
|
|
162
|
-
return emit("RUNTIME-RATELIMIT-001", path, mode === "block");
|
|
163
|
-
}
|
|
164
|
-
const stats = bodyStats(req.body);
|
|
165
|
-
if (stats.bytes > maxBodyBytes || stats.depth > maxBodyDepth || stats.keys > maxBodyKeys) {
|
|
166
|
-
return emit("RUNTIME-BODY-001", "oversized-body", mode === "block");
|
|
167
|
-
}
|
|
168
|
-
const values = [
|
|
169
|
-
...flattenValues(req.query),
|
|
170
|
-
...flattenValues(req.body),
|
|
171
|
-
...flattenValues(req.params)
|
|
172
|
-
];
|
|
173
|
-
const kind = inspectValues(values);
|
|
174
|
-
if (kind !== void 0) {
|
|
175
|
-
return emit(RULE[kind], kind, mode === "block");
|
|
176
|
-
}
|
|
177
|
-
if (anomaly !== void 0) {
|
|
178
|
-
const now = anomaly.now ?? Date.now;
|
|
179
|
-
const t = now();
|
|
180
|
-
if (windowStart === void 0) windowStart = t;
|
|
181
|
-
const learning = t - windowStart < anomaly.learnMinutes * 6e4;
|
|
182
|
-
let baseline = routes.get(path);
|
|
183
|
-
if (baseline === void 0) {
|
|
184
|
-
baseline = {
|
|
185
|
-
count: 0,
|
|
186
|
-
bodySum: 0,
|
|
187
|
-
paramNames: new LruMap(maxParamNames),
|
|
188
|
-
status401: 0,
|
|
189
|
-
status404: 0,
|
|
190
|
-
recent404: 0
|
|
191
|
-
};
|
|
192
|
-
routes.set(path, baseline);
|
|
193
|
-
}
|
|
194
|
-
const names = [...keysOf(req.query), ...keysOf(req.body), ...keysOf(req.params)];
|
|
195
|
-
const status = req.statusCode;
|
|
196
|
-
if (learning) {
|
|
197
|
-
baseline.count += 1;
|
|
198
|
-
baseline.bodySum += stats.bytes;
|
|
199
|
-
for (const n of names) baseline.paramNames.set(n, true);
|
|
200
|
-
if (status === 401) baseline.status401 += 1;
|
|
201
|
-
if (status === 404) baseline.status404 += 1;
|
|
202
|
-
} else {
|
|
203
|
-
const newParam = names.some((n) => !baseline.paramNames.has(n));
|
|
204
|
-
const spike401 = status === 401 && baseline.status401 === 0 && baseline.count > 0;
|
|
205
|
-
if (status === 404) baseline.recent404 += 1;
|
|
206
|
-
else baseline.recent404 = 0;
|
|
207
|
-
const burst404 = baseline.recent404 >= 3 && baseline.status404 === 0 && baseline.count > 0;
|
|
208
|
-
if (newParam || spike401 || burst404) {
|
|
209
|
-
const event = {
|
|
210
|
-
ruleId: "RUNTIME-ANOMALY-001",
|
|
211
|
-
path,
|
|
212
|
-
ip,
|
|
213
|
-
sample: newParam ? "new-param" : spike401 ? "401-spike" : "404-burst"
|
|
214
|
-
};
|
|
215
|
-
anomaly.onAnomaly?.(event);
|
|
216
|
-
options.onEvent?.(event);
|
|
217
|
-
return { block: false, event };
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
return { block: false };
|
|
222
|
-
});
|
|
223
|
-
Object.defineProperty(inspect, "limiterSize", { get: () => limiter.size });
|
|
224
|
-
Object.defineProperty(inspect, "routeCount", { get: () => routes.size });
|
|
225
|
-
inspect.paramCount = (path) => routes.get(path)?.paramNames.size ?? 0;
|
|
226
|
-
return inspect;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
export { createInspector, createRateLimiter, detectAttack, flattenValues, inspectValues };
|
|
1
|
+
var j=/('\s*or\s+'?1'?\s*='?1)|union\s+select|;\s*drop\s+table|;\s*delete\s+from/i,z=/<\s*script\b|javascript:\s*|onerror\s*=/i,U=/(\.\.\/|\.\.\\|%2e%2e%2f|%2e%2e\\)/i;function p(e,t=[],n=0){if(n>8||t.length>200)return t;if(typeof e=="string")return t.push(e),t;if(Array.isArray(e)){for(let r of e)p(r,t,n+1);return t}if(e!==null&&typeof e=="object")for(let r of Object.values(e))p(r,t,n+1);return t}function G(e){if(U.test(e))return "path";if(z.test(e))return "xss";if(j.test(e))return "sqli"}function L(e){for(let t of e){let n=G(t);if(n!==void 0)return n}}var f=class{constructor(t){this.max=t;if(t<1)throw new Error("LruMap max must be >= 1")}max;map=new Map;get size(){return this.map.size}has(t){return this.map.has(t)}get(t){if(!this.map.has(t))return;let n=this.map.get(t);return this.map.delete(t),this.map.set(t,n),n}set(t,n){for(this.map.has(t)&&this.map.delete(t),this.map.set(t,n);this.map.size>this.max;){let r=this.map.keys().next().value;if(r===void 0)break;this.map.delete(r);}return this}};var V=1e4;function K(e){let t=e.maxKeys??V,n=new f(t);return {allow(r,m=Date.now()){let c=m-e.windowMs,o=n.get(r);if(o===void 0)return n.set(r,[m]),true;let a=0;for(let u=0;u<o.length;u+=1){let l=o[u];l!==void 0&&l>c&&(o[a]=l,a+=1);}return o.length=a,o.length>=e.max?(n.set(r,o),false):(o.push(m),n.set(r,o),true)},get size(){return n.size},get maxKeys(){return t}}}var D={sqli:"RUNTIME-SQLI-001",xss:"RUNTIME-XSS-001",path:"RUNTIME-PATH-001"};function T(e,t=0){if(e==null)return {bytes:0,depth:t,keys:0};if(typeof e=="string")return {bytes:e.length,depth:t,keys:0};if(typeof e=="number"||typeof e=="boolean")return {bytes:String(e).length,depth:t,keys:0};if(typeof e!="object")return {bytes:0,depth:t,keys:0};let n=Object.entries(e),r=2,m=n.length,c=t;for(let[o,a]of n){r+=o.length;let u=T(a,t+1);r+=u.bytes,m+=u.keys,u.depth>c&&(c=u.depth);}return {bytes:r,depth:c,keys:m}}var P=1e3,C=200;function S(e){return e==null||typeof e!="object"?[]:Object.keys(e)}function X(e={}){let t=e.mode??"monitor",n=new Set(e.authRoutes??["/login"]),r=K(e.rateLimit??{windowMs:6e4,max:20}),m=e.maxBodyBytes??1e6,c=e.maxBodyDepth??8,o=e.maxBodyKeys??200,a=e.anomaly,u=a?.maxRoutes??P,l=a?.maxParamNames??C,y=new f(u),I,g=(i=>{let d=i.path??i.url?.split("?")[0]??"/",A=i.ip??"unknown",E=(B,x,k)=>{let s={ruleId:B,path:d,ip:A,sample:x.slice(0,40)};return e.onEvent?.(s),k&&e.onBlock?.(s),{block:k,event:s}};if(n.has(d)&&!r.allow(`${A}:${d}`))return E("RUNTIME-RATELIMIT-001",d,t==="block");let h=T(i.body);if(h.bytes>m||h.depth>c||h.keys>o)return E("RUNTIME-BODY-001","oversized-body",t==="block");let v=[...p(i.query),...p(i.body),...p(i.params)],M=L(v);if(M!==void 0)return E(D[M],M,t==="block");if(a!==void 0){let x=(a.now??Date.now)();I===void 0&&(I=x);let k=x-I<a.learnMinutes*6e4,s=y.get(d);s===void 0&&(s={count:0,bodySum:0,paramNames:new f(l),status401:0,status404:0,recent404:0},y.set(d,s));let N=[...S(i.query),...S(i.body),...S(i.params)],R=i.statusCode;if(k){s.count+=1,s.bodySum+=h.bytes;for(let w of N)s.paramNames.set(w,true);R===401&&(s.status401+=1),R===404&&(s.status404+=1);}else {let w=N.some(b=>!s.paramNames.has(b)),O=R===401&&s.status401===0&&s.count>0;R===404?s.recent404+=1:s.recent404=0;let _=s.recent404>=3&&s.status404===0&&s.count>0;if(w||O||_){let b={ruleId:"RUNTIME-ANOMALY-001",path:d,ip:A,sample:w?"new-param":O?"401-spike":"404-burst"};return a.onAnomaly?.(b),e.onEvent?.(b),{block:false,event:b}}}}return {block:false}});return Object.defineProperty(g,"limiterSize",{get:()=>r.size}),Object.defineProperty(g,"routeCount",{get:()=>y.size}),g.paramCount=i=>y.get(i)?.paramNames.size??0,g}export{X as createInspector,K as createRateLimiter,G as detectAttack,p as flattenValues,L as inspectValues};
|