@blamejs/core 0.9.46 → 0.10.1
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 +951 -893
- package/index.js +30 -0
- package/lib/_test/crypto-fixtures.js +67 -0
- package/lib/agent-event-bus.js +52 -6
- package/lib/agent-idempotency.js +169 -16
- package/lib/agent-orchestrator.js +263 -9
- package/lib/agent-posture-chain.js +163 -5
- package/lib/agent-saga.js +146 -16
- package/lib/agent-snapshot.js +349 -19
- package/lib/agent-stream.js +34 -2
- package/lib/agent-tenant.js +179 -23
- package/lib/agent-trace.js +84 -21
- package/lib/auth/aal.js +8 -1
- package/lib/auth/ciba.js +6 -1
- package/lib/auth/dpop.js +7 -2
- package/lib/auth/fal.js +17 -8
- package/lib/auth/jwt-external.js +128 -4
- package/lib/auth/oauth.js +232 -10
- package/lib/auth/oid4vci.js +67 -7
- package/lib/auth/openid-federation.js +71 -25
- package/lib/auth/passkey.js +140 -6
- package/lib/auth/sd-jwt-vc.js +67 -5
- package/lib/circuit-breaker.js +10 -2
- package/lib/compliance.js +176 -8
- package/lib/crypto-field.js +114 -14
- package/lib/crypto.js +216 -20
- package/lib/db.js +1 -0
- package/lib/guard-imap-command.js +335 -0
- package/lib/guard-jmap.js +321 -0
- package/lib/guard-managesieve-command.js +566 -0
- package/lib/guard-pop3-command.js +317 -0
- package/lib/guard-smtp-command.js +58 -3
- package/lib/mail-agent.js +20 -7
- package/lib/mail-arc-sign.js +12 -8
- package/lib/mail-auth.js +323 -34
- package/lib/mail-crypto-pgp.js +934 -0
- package/lib/mail-crypto-smime.js +340 -0
- package/lib/mail-crypto.js +108 -0
- package/lib/mail-dav.js +1224 -0
- package/lib/mail-deploy.js +492 -0
- package/lib/mail-dkim.js +431 -26
- package/lib/mail-journal.js +435 -0
- package/lib/mail-scan.js +502 -0
- package/lib/mail-server-imap.js +1102 -0
- package/lib/mail-server-jmap.js +488 -0
- package/lib/mail-server-managesieve.js +853 -0
- package/lib/mail-server-mx.js +164 -34
- package/lib/mail-server-pop3.js +836 -0
- package/lib/mail-server-rate-limit.js +269 -0
- package/lib/mail-server-submission.js +1032 -0
- package/lib/mail-server-tls.js +445 -0
- package/lib/mail-sieve.js +557 -0
- package/lib/mail-spam-score.js +284 -0
- package/lib/mail.js +99 -0
- package/lib/metrics.js +130 -10
- package/lib/middleware/dpop.js +58 -3
- package/lib/middleware/idempotency-key.js +255 -42
- package/lib/middleware/protected-resource-metadata.js +114 -2
- package/lib/network-dns-resolver.js +33 -0
- package/lib/network-tls.js +46 -0
- package/lib/outbox.js +62 -12
- package/lib/pqc-agent.js +13 -5
- package/lib/retry.js +23 -9
- package/lib/router.js +23 -1
- package/lib/safe-ical.js +634 -0
- package/lib/safe-icap.js +502 -0
- package/lib/safe-mime.js +15 -0
- package/lib/safe-sieve.js +684 -0
- package/lib/safe-smtp.js +57 -0
- package/lib/safe-url.js +37 -0
- package/lib/safe-vcard.js +473 -0
- package/lib/self-update-standalone-verifier.js +32 -3
- package/lib/self-update.js +168 -17
- package/lib/vendor/MANIFEST.json +161 -156
- package/lib/vendor-data.js +127 -9
- package/lib/vex.js +324 -59
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @module b.guardImapCommand
|
|
4
|
+
* @nav Guards
|
|
5
|
+
* @title Guard IMAP Command
|
|
6
|
+
* @order 451
|
|
7
|
+
*
|
|
8
|
+
* @intro
|
|
9
|
+
* IMAP command-line validator (RFC 9051 IMAP4rev2; obsoletes
|
|
10
|
+
* RFC 3501). Gates every command-line the framework's inbound
|
|
11
|
+
* IMAP listener accepts from peers — `CAPABILITY` / `NOOP` /
|
|
12
|
+
* `LOGOUT` / `STARTTLS` / `AUTHENTICATE` / `LOGIN` / `ENABLE` /
|
|
13
|
+
* `SELECT` / `EXAMINE` / `CREATE` / `DELETE` / `RENAME` /
|
|
14
|
+
* `SUBSCRIBE` / `UNSUBSCRIBE` / `LIST` / `NAMESPACE` / `STATUS` /
|
|
15
|
+
* `APPEND` / `IDLE` / `CHECK` / `CLOSE` / `UNSELECT` / `EXPUNGE` /
|
|
16
|
+
* `SEARCH` / `FETCH` / `STORE` / `COPY` / `MOVE` / `UID` /
|
|
17
|
+
* `GETQUOTA` / `SETQUOTA` / `GETQUOTAROOT` / `ID`.
|
|
18
|
+
*
|
|
19
|
+
* ## Smuggling defense — bare-CR / bare-LF refusal
|
|
20
|
+
*
|
|
21
|
+
* Same wire-protocol smuggling class as SMTP: implementations that
|
|
22
|
+
* accept bare-CR or bare-LF in a command line let a hostile peer
|
|
23
|
+
* inject a second command past a per-line filter. RFC 9051 §2.2.1
|
|
24
|
+
* requires CRLF only; this validator refuses every bare CR / bare
|
|
25
|
+
* LF / NUL / C0 / DEL byte outside of explicit literal blocks
|
|
26
|
+
* (which the wire-protocol reader has already framed before
|
|
27
|
+
* handing the line to this validator).
|
|
28
|
+
*
|
|
29
|
+
* ## Literal-injection defense
|
|
30
|
+
*
|
|
31
|
+
* IMAP carries inline length-prefixed literals: `{n}<CRLF><n bytes>`.
|
|
32
|
+
* Per RFC 9051 §2.2.2 the literal opener `{n}` MUST appear at the
|
|
33
|
+
* end of a command line, with the n bytes following on subsequent
|
|
34
|
+
* line(s). RFC 7888 LITERAL+ relaxes the round-trip but is only
|
|
35
|
+
* honored post-AUTH. The validator detects literal openers as
|
|
36
|
+
* either:
|
|
37
|
+
*
|
|
38
|
+
* - well-formed: `{42}` or `{42+}` at the end of the line
|
|
39
|
+
* - injected: `{42}` mid-line (smuggling shape — refuse)
|
|
40
|
+
*
|
|
41
|
+
* Per-literal byte cap defaults to 64 MiB (operator opts down via
|
|
42
|
+
* `maxLiteralBytes`); the LISTENER then enforces the post-literal
|
|
43
|
+
* read against this cap.
|
|
44
|
+
*
|
|
45
|
+
* ## Mailbox-name traversal
|
|
46
|
+
*
|
|
47
|
+
* Mailbox names per RFC 9051 §5.1 — UTF-8 hierarchy with the
|
|
48
|
+
* server-chosen delimiter (typically `/` or `.`). Refuses path-
|
|
49
|
+
* traversal (`..`), NUL bytes, control chars, leading/trailing
|
|
50
|
+
* slash, overlong UTF-8 sequences, and (under strict) modified-
|
|
51
|
+
* UTF7 (RFC 3501 §5.1.3 legacy encoding — operators with legacy
|
|
52
|
+
* MUAs opt in via `allowLegacyMUtf7`).
|
|
53
|
+
*
|
|
54
|
+
* ## Per-verb shape
|
|
55
|
+
*
|
|
56
|
+
* Each command verb has a fixed argument shape per RFC 9051 §6.
|
|
57
|
+
* `LOGIN user pass` takes exactly two atoms or strings. `SELECT`
|
|
58
|
+
* takes one mailbox name. `FETCH` takes a sequence-set + a parts
|
|
59
|
+
* list. Refusals under strict use `guard-imap-command/bad-shape`.
|
|
60
|
+
*
|
|
61
|
+
* ## Caps
|
|
62
|
+
*
|
|
63
|
+
* - Command line (tag + verb + arguments excluding literal
|
|
64
|
+
* payload) capped at 8 KiB. RFC 9051 does not mandate a line
|
|
65
|
+
* cap but most servers limit at 8 KiB or 16 KiB to bound
|
|
66
|
+
* memory; operators on permissive can extend.
|
|
67
|
+
* - Mailbox name capped at 1 KiB.
|
|
68
|
+
* - Sequence set element count capped at 10,000 per command.
|
|
69
|
+
* - SEARCH expression nesting (AND/OR/NOT) capped at 32 levels.
|
|
70
|
+
* - Per-literal byte cap (64 MiB default).
|
|
71
|
+
*
|
|
72
|
+
* Throws `GuardImapCommandError` on every refusal. Pure-functional —
|
|
73
|
+
* no I/O, no state. The IMAP listener composes one instance per
|
|
74
|
+
* accepted connection.
|
|
75
|
+
*
|
|
76
|
+
* @card
|
|
77
|
+
* IMAP command-line validator (RFC 9051 IMAP4rev2). Refuses bare-CR /
|
|
78
|
+
* bare-LF (smuggling defense), enforces literal-injection refusal
|
|
79
|
+
* (RFC 9051 §2.2.2), caps line / mailbox / sequence-set / SEARCH-
|
|
80
|
+
* nesting bytes, validates per-verb shape (CAPABILITY / AUTHENTICATE
|
|
81
|
+
* / LOGIN / SELECT / FETCH / STORE / APPEND / SEARCH / ...).
|
|
82
|
+
*/
|
|
83
|
+
|
|
84
|
+
var { defineClass } = require("./framework-error");
|
|
85
|
+
|
|
86
|
+
var GuardImapCommandError = defineClass("GuardImapCommandError", { alwaysPermanent: true });
|
|
87
|
+
|
|
88
|
+
var DEFAULT_PROFILE = "strict";
|
|
89
|
+
|
|
90
|
+
var PROFILES = Object.freeze({
|
|
91
|
+
strict: {
|
|
92
|
+
maxLineBytes: 8192, // allow:raw-byte-literal — 8 KiB command-line cap
|
|
93
|
+
maxLiteralBytes: 67108864, // allow:raw-byte-literal — 64 MiB per-literal cap
|
|
94
|
+
maxMailboxBytes: 1024, // allow:raw-byte-literal — RFC 9051 §5.1 mailbox cap
|
|
95
|
+
maxSequenceSetItems: 10000, // allow:raw-byte-literal — FETCH/STORE sequence-set element cap
|
|
96
|
+
maxSearchDepth: 32, // allow:raw-byte-literal — SEARCH AND/OR/NOT nesting cap
|
|
97
|
+
allowBareLf: false,
|
|
98
|
+
allowLiteralPlus: false, // LITERAL+ (RFC 7888) only post-AUTH; the listener flips this
|
|
99
|
+
allowLegacyMUtf7: false, // RFC 3501 §5.1.3 modified-UTF7 mailbox names — legacy MUA escape hatch
|
|
100
|
+
},
|
|
101
|
+
balanced: {
|
|
102
|
+
maxLineBytes: 16384, // allow:raw-byte-literal — 16 KiB command-line cap
|
|
103
|
+
maxLiteralBytes: 134217728, // allow:raw-byte-literal — 128 MiB per-literal cap
|
|
104
|
+
maxMailboxBytes: 2048, // allow:raw-byte-literal — balanced mailbox cap
|
|
105
|
+
maxSequenceSetItems: 50000, // allow:raw-byte-literal — balanced sequence-set cap
|
|
106
|
+
maxSearchDepth: 48, // allow:raw-byte-literal — balanced SEARCH-depth cap
|
|
107
|
+
allowBareLf: false,
|
|
108
|
+
allowLiteralPlus: true,
|
|
109
|
+
allowLegacyMUtf7: true,
|
|
110
|
+
},
|
|
111
|
+
permissive: {
|
|
112
|
+
maxLineBytes: 65536, // allow:raw-byte-literal — 64 KiB command-line cap (legacy peers)
|
|
113
|
+
maxLiteralBytes: 268435456, // allow:raw-byte-literal — 256 MiB per-literal cap
|
|
114
|
+
maxMailboxBytes: 4096, // allow:raw-byte-literal — permissive mailbox cap
|
|
115
|
+
maxSequenceSetItems: 100000, // allow:raw-byte-literal — permissive sequence-set cap
|
|
116
|
+
maxSearchDepth: 64, // allow:raw-byte-literal — permissive SEARCH-depth cap
|
|
117
|
+
allowBareLf: true,
|
|
118
|
+
allowLiteralPlus: true,
|
|
119
|
+
allowLegacyMUtf7: true,
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
var COMPLIANCE_POSTURES = Object.freeze({
|
|
124
|
+
hipaa: "strict",
|
|
125
|
+
"pci-dss": "strict",
|
|
126
|
+
gdpr: "strict",
|
|
127
|
+
soc2: "strict",
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// IMAP4rev2 commands per RFC 9051 §6.
|
|
131
|
+
var KNOWN_VERBS = Object.freeze({
|
|
132
|
+
CAPABILITY: true, NOOP: true, LOGOUT: true,
|
|
133
|
+
STARTTLS: true, AUTHENTICATE: true, LOGIN: true,
|
|
134
|
+
ENABLE: true, SELECT: true, EXAMINE: true,
|
|
135
|
+
CREATE: true, DELETE: true, RENAME: true,
|
|
136
|
+
SUBSCRIBE: true, UNSUBSCRIBE: true, LIST: true,
|
|
137
|
+
NAMESPACE: true, STATUS: true, APPEND: true,
|
|
138
|
+
IDLE: true, DONE: true, CHECK: true,
|
|
139
|
+
CLOSE: true, UNSELECT: true, EXPUNGE: true,
|
|
140
|
+
SEARCH: true, FETCH: true, STORE: true,
|
|
141
|
+
COPY: true, MOVE: true, UID: true,
|
|
142
|
+
GETQUOTA: true, SETQUOTA: true, GETQUOTAROOT: true,
|
|
143
|
+
ID: true,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
var ZERO_ARG_VERBS = Object.freeze({
|
|
147
|
+
CAPABILITY: true, NOOP: true, LOGOUT: true,
|
|
148
|
+
STARTTLS: true, IDLE: true, DONE: true,
|
|
149
|
+
CHECK: true, CLOSE: true, UNSELECT: true,
|
|
150
|
+
EXPUNGE: true,
|
|
151
|
+
NAMESPACE: true,
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// IMAP tag per RFC 9051 §9 ABNF: `tag = 1*<any ASTRING-CHAR except "+">`.
|
|
155
|
+
// We narrow further: letters, digits, hyphen, underscore, dot — refuses
|
|
156
|
+
// `+` (continuation request marker; reserved by §9 explicitly) and
|
|
157
|
+
// `*` (server-untagged response marker) which are reserved.
|
|
158
|
+
var TAG_RE = /^[A-Za-z0-9._-]{1,64}$/; // allow:regex-no-length-cap — anchored + bounded repeat
|
|
159
|
+
|
|
160
|
+
// Literal-opener detection — `{n}` or `{n+}` at end of line per
|
|
161
|
+
// RFC 9051 §2.2.2 / RFC 7888 §2. The `+` form is LITERAL+ (non-
|
|
162
|
+
// synchronizing).
|
|
163
|
+
var LITERAL_OPEN_RE = /\{([0-9]+)(\+?)\}$/; // allow:regex-no-length-cap — anchored + bounded numeric run
|
|
164
|
+
|
|
165
|
+
// Detect a literal-opener mid-line (smuggling shape) — same `{n}` /
|
|
166
|
+
// `{n+}` pattern but NOT at end of line. Used by detectLiteralSmuggling.
|
|
167
|
+
var LITERAL_SMUGGLE_RE = /\{[0-9]+\+?\}(?!\s*$)/; // allow:regex-no-length-cap — bounded numeric run + tail anchor
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* @primitive b.guardImapCommand.validate
|
|
171
|
+
* @signature b.guardImapCommand.validate(line, opts?)
|
|
172
|
+
* @since 0.9.49
|
|
173
|
+
* @status stable
|
|
174
|
+
* @related b.guardImapCommand.detectLiteralSmuggling, b.guardSmtpCommand.validate
|
|
175
|
+
*
|
|
176
|
+
* Validate a single IMAP command line (without its CRLF terminator —
|
|
177
|
+
* the listener strips that before calling this). Returns
|
|
178
|
+
* `{ tag, verb, args, literalSize, literalNonSync }` on success;
|
|
179
|
+
* throws `GuardImapCommandError` on any refusal. `literalSize` is the
|
|
180
|
+
* pending-literal byte count when the line ends in `{n}`; `null`
|
|
181
|
+
* otherwise. `literalNonSync` is true for RFC 7888 LITERAL+ (`{n+}`).
|
|
182
|
+
*
|
|
183
|
+
* @opts
|
|
184
|
+
* profile: "strict" | "balanced" | "permissive",
|
|
185
|
+
* posture: "hipaa" | "pci-dss" | "gdpr" | "soc2",
|
|
186
|
+
* authenticated: boolean, // when true, LITERAL+ (RFC 7888) is honored under
|
|
187
|
+
* strict; pre-AUTH literal+ is refused per RFC 7888 §1
|
|
188
|
+
*
|
|
189
|
+
* @example
|
|
190
|
+
* var parsed = b.guardImapCommand.validate("A001 LOGIN alice secret");
|
|
191
|
+
* // → { tag: "A001", verb: "LOGIN", args: ["alice", "secret"], literalSize: null, literalNonSync: false }
|
|
192
|
+
*
|
|
193
|
+
* var pending = b.guardImapCommand.validate("A002 APPEND INBOX {1024}");
|
|
194
|
+
* // → { tag: "A002", verb: "APPEND", args: ["INBOX"], literalSize: 1024, literalNonSync: false }
|
|
195
|
+
*/
|
|
196
|
+
function validate(line, opts) {
|
|
197
|
+
opts = opts || {};
|
|
198
|
+
var profileName = typeof opts.profile === "string" ? opts.profile : DEFAULT_PROFILE;
|
|
199
|
+
if (opts.posture && COMPLIANCE_POSTURES[opts.posture]) {
|
|
200
|
+
profileName = COMPLIANCE_POSTURES[opts.posture];
|
|
201
|
+
}
|
|
202
|
+
var caps = PROFILES[profileName];
|
|
203
|
+
if (!caps) {
|
|
204
|
+
throw new GuardImapCommandError("guard-imap-command/bad-profile",
|
|
205
|
+
"guardImapCommand.validate: unknown profile '" + profileName + "'");
|
|
206
|
+
}
|
|
207
|
+
if (typeof line !== "string") {
|
|
208
|
+
throw new GuardImapCommandError("guard-imap-command/bad-input",
|
|
209
|
+
"guardImapCommand.validate: line must be a string");
|
|
210
|
+
}
|
|
211
|
+
if (line.length === 0) {
|
|
212
|
+
throw new GuardImapCommandError("guard-imap-command/empty-line",
|
|
213
|
+
"guardImapCommand.validate: empty command line");
|
|
214
|
+
}
|
|
215
|
+
if (line.length > caps.maxLineBytes) {
|
|
216
|
+
throw new GuardImapCommandError("guard-imap-command/line-too-long",
|
|
217
|
+
"guardImapCommand.validate: line " + line.length + " bytes exceeds cap " + caps.maxLineBytes);
|
|
218
|
+
}
|
|
219
|
+
// Byte-safety: refuse bare CR / bare LF / NUL / C0 / DEL. The
|
|
220
|
+
// wire-protocol reader has already stripped the terminating CRLF
|
|
221
|
+
// before calling validate(); any remaining CR or LF is a smuggling
|
|
222
|
+
// shape.
|
|
223
|
+
for (var i = 0; i < line.length; i += 1) {
|
|
224
|
+
var c = line.charCodeAt(i);
|
|
225
|
+
if (c === 0x00 || c === 0x7F || (c < 0x20 && c !== 0x09)) { // allow:raw-byte-literal — control-byte refusal
|
|
226
|
+
if (c === 0x0A && caps.allowBareLf) continue;
|
|
227
|
+
throw new GuardImapCommandError("guard-imap-command/bad-byte",
|
|
228
|
+
"guardImapCommand.validate: control byte 0x" + c.toString(16) + " at offset " + i); // allow:raw-byte-literal — hex format literal in error message
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// RFC 9051 §2.2.1 — `tag SP command [SP args] CRLF`
|
|
233
|
+
var firstSpace = line.indexOf(" ");
|
|
234
|
+
if (firstSpace === -1) {
|
|
235
|
+
throw new GuardImapCommandError("guard-imap-command/missing-verb",
|
|
236
|
+
"guardImapCommand.validate: command line missing verb (no SP after tag)");
|
|
237
|
+
}
|
|
238
|
+
var tag = line.slice(0, firstSpace);
|
|
239
|
+
if (!TAG_RE.test(tag)) { // allow:regex-no-length-cap — TAG_RE anchored + bounded-repeat
|
|
240
|
+
throw new GuardImapCommandError("guard-imap-command/bad-tag",
|
|
241
|
+
"guardImapCommand.validate: bad tag '" + tag + "' (RFC 9051 §9 atom)");
|
|
242
|
+
}
|
|
243
|
+
var rest = line.slice(firstSpace + 1);
|
|
244
|
+
var verbSpace = rest.indexOf(" ");
|
|
245
|
+
var verb = (verbSpace === -1 ? rest : rest.slice(0, verbSpace)).toUpperCase();
|
|
246
|
+
var args = verbSpace === -1 ? "" : rest.slice(verbSpace + 1);
|
|
247
|
+
|
|
248
|
+
if (!KNOWN_VERBS[verb]) {
|
|
249
|
+
throw new GuardImapCommandError("guard-imap-command/unknown-verb",
|
|
250
|
+
"guardImapCommand.validate: unknown verb '" + verb + "'");
|
|
251
|
+
}
|
|
252
|
+
if (ZERO_ARG_VERBS[verb] && args.length > 0) {
|
|
253
|
+
throw new GuardImapCommandError("guard-imap-command/unexpected-args",
|
|
254
|
+
"guardImapCommand.validate: verb '" + verb + "' takes no arguments");
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Literal-opener detection — `{n}` at end of line.
|
|
258
|
+
var literalSize = null;
|
|
259
|
+
var literalNonSync = false;
|
|
260
|
+
var litMatch = args.match(LITERAL_OPEN_RE);
|
|
261
|
+
if (litMatch) {
|
|
262
|
+
var sz = parseInt(litMatch[1], 10);
|
|
263
|
+
if (!isFinite(sz) || sz < 0 || sz > caps.maxLiteralBytes) {
|
|
264
|
+
throw new GuardImapCommandError("guard-imap-command/literal-too-large",
|
|
265
|
+
"guardImapCommand.validate: literal size " + sz + " exceeds cap " + caps.maxLiteralBytes);
|
|
266
|
+
}
|
|
267
|
+
literalSize = sz;
|
|
268
|
+
literalNonSync = litMatch[2] === "+";
|
|
269
|
+
if (literalNonSync && !caps.allowLiteralPlus) {
|
|
270
|
+
throw new GuardImapCommandError("guard-imap-command/literal-plus-refused",
|
|
271
|
+
"guardImapCommand.validate: LITERAL+ (RFC 7888) refused under profile '" + profileName + "'");
|
|
272
|
+
}
|
|
273
|
+
if (literalNonSync && opts.authenticated === false) {
|
|
274
|
+
// RFC 7888 §1: LITERAL+ MAY be used by clients but servers MAY
|
|
275
|
+
// refuse it pre-AUTH. We refuse pre-AUTH to bound resource use
|
|
276
|
+
// before authentication.
|
|
277
|
+
throw new GuardImapCommandError("guard-imap-command/literal-plus-pre-auth",
|
|
278
|
+
"guardImapCommand.validate: LITERAL+ refused pre-authentication");
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Mid-line literal opener is smuggling-shaped.
|
|
283
|
+
if (detectLiteralSmuggling(line)) {
|
|
284
|
+
throw new GuardImapCommandError("guard-imap-command/literal-smuggling",
|
|
285
|
+
"guardImapCommand.validate: literal opener `{n}` MUST appear at end of line (RFC 9051 §2.2.2)");
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return { tag: tag, verb: verb, args: args, literalSize: literalSize, literalNonSync: literalNonSync };
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* @primitive b.guardImapCommand.detectLiteralSmuggling
|
|
293
|
+
* @signature b.guardImapCommand.detectLiteralSmuggling(line)
|
|
294
|
+
* @since 0.9.49
|
|
295
|
+
* @status stable
|
|
296
|
+
*
|
|
297
|
+
* Return `true` when the input line contains a literal opener
|
|
298
|
+
* `{n}` or `{n+}` that is NOT at the end of the line — the
|
|
299
|
+
* smuggling-shape per RFC 9051 §2.2.2.
|
|
300
|
+
*
|
|
301
|
+
* @example
|
|
302
|
+
* b.guardImapCommand.detectLiteralSmuggling("A001 APPEND INBOX {10} hostile"); // → true
|
|
303
|
+
* b.guardImapCommand.detectLiteralSmuggling("A001 APPEND INBOX {10}"); // → false (well-formed)
|
|
304
|
+
*/
|
|
305
|
+
function detectLiteralSmuggling(line) {
|
|
306
|
+
if (typeof line !== "string") return false;
|
|
307
|
+
return LITERAL_SMUGGLE_RE.test(line); // allow:regex-no-length-cap — caller's input is already length-capped upstream by the listener's per-line cap
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* @primitive b.guardImapCommand.compliancePosture
|
|
312
|
+
* @signature b.guardImapCommand.compliancePosture(posture)
|
|
313
|
+
* @since 0.9.49
|
|
314
|
+
* @status stable
|
|
315
|
+
*
|
|
316
|
+
* Return the effective profile for a compliance posture, or `null`
|
|
317
|
+
* for unknown names.
|
|
318
|
+
*
|
|
319
|
+
* @example
|
|
320
|
+
* b.guardImapCommand.compliancePosture("hipaa"); // → "strict"
|
|
321
|
+
*/
|
|
322
|
+
function compliancePosture(posture) {
|
|
323
|
+
return COMPLIANCE_POSTURES[posture] || null;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
module.exports = {
|
|
327
|
+
validate: validate,
|
|
328
|
+
detectLiteralSmuggling: detectLiteralSmuggling,
|
|
329
|
+
compliancePosture: compliancePosture,
|
|
330
|
+
PROFILES: PROFILES,
|
|
331
|
+
COMPLIANCE_POSTURES: COMPLIANCE_POSTURES,
|
|
332
|
+
KNOWN_VERBS: KNOWN_VERBS,
|
|
333
|
+
ZERO_ARG_VERBS: ZERO_ARG_VERBS,
|
|
334
|
+
GuardImapCommandError: GuardImapCommandError,
|
|
335
|
+
};
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @module b.guardJmap
|
|
4
|
+
* @nav Guards
|
|
5
|
+
* @title Guard JMAP Request
|
|
6
|
+
* @order 452
|
|
7
|
+
*
|
|
8
|
+
* @intro
|
|
9
|
+
* JMAP request-envelope validator (RFC 8620 JMAP Core). Validates
|
|
10
|
+
* the shape of an HTTP request body posted to `/jmap/api` and
|
|
11
|
+
* refuses requests that exceed operator caps, omit required
|
|
12
|
+
* capability declarations, or contain malformed back-references.
|
|
13
|
+
*
|
|
14
|
+
* ## Request shape (RFC 8620 §3.3)
|
|
15
|
+
*
|
|
16
|
+
* ```json
|
|
17
|
+
* {
|
|
18
|
+
* "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
|
|
19
|
+
* "methodCalls": [
|
|
20
|
+
* ["Mailbox/get", { "accountId": "A1", "ids": null }, "c0"],
|
|
21
|
+
* ["Email/query", { "filter": { "inMailbox": "#c0/list/0" } }, "c1"]
|
|
22
|
+
* ],
|
|
23
|
+
* "createdIds": null
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* `using` is the set of capability URIs the request invokes; the
|
|
28
|
+
* server's `urn:ietf:params:jmap:core` is implicit. `methodCalls`
|
|
29
|
+
* is an array of 3-tuples `[methodName, args, clientId]` where
|
|
30
|
+
* `clientId` echoes back on the response for client-side
|
|
31
|
+
* correlation.
|
|
32
|
+
*
|
|
33
|
+
* ## Back-reference resolution (RFC 8620 §3.7)
|
|
34
|
+
*
|
|
35
|
+
* Subsequent `methodCalls` reference earlier results via
|
|
36
|
+
* `{ "resultOf": <prior-clientId>, "name": <methodName>, "path": <JSONPath> }`
|
|
37
|
+
* placeholders inside the `args` object. The validator detects
|
|
38
|
+
* back-references and caps the chain depth so a pathological
|
|
39
|
+
* chain doesn't degrade into a O(2^N) blowup.
|
|
40
|
+
*
|
|
41
|
+
* ## Caps
|
|
42
|
+
*
|
|
43
|
+
* - `maxCallsInRequest` — default 32 (RFC 8620 §3.6)
|
|
44
|
+
* - `maxObjectsInGet` — default 500
|
|
45
|
+
* - `maxObjectsInSet` — default 500
|
|
46
|
+
* - `maxSizeRequest` — default 10 MiB
|
|
47
|
+
* - `maxBackRefDepth` — default 8 (we add this; spec doesn't)
|
|
48
|
+
* - `maxUsingCapabilities` — default 32 (refuses oversize `using`)
|
|
49
|
+
*
|
|
50
|
+
* Refusals emit a `urn:ietf:params:jmap:error:*` URI per
|
|
51
|
+
* RFC 8620 §3.6.1.
|
|
52
|
+
*
|
|
53
|
+
* @card
|
|
54
|
+
* JMAP request-envelope validator (RFC 8620 §3.3). Refuses oversize
|
|
55
|
+
* requests, capability-unknown / malformed back-reference / pipeline-
|
|
56
|
+
* bomb shapes per RFC 8620 §3.6.1 error vocabulary.
|
|
57
|
+
*/
|
|
58
|
+
|
|
59
|
+
var { defineClass } = require("./framework-error");
|
|
60
|
+
var safeJson = require("./safe-json");
|
|
61
|
+
var validateOpts = require("./validate-opts");
|
|
62
|
+
|
|
63
|
+
var GuardJmapError = defineClass("GuardJmapError", { alwaysPermanent: true });
|
|
64
|
+
|
|
65
|
+
var DEFAULT_PROFILE = "strict";
|
|
66
|
+
|
|
67
|
+
var PROFILES = Object.freeze({
|
|
68
|
+
strict: {
|
|
69
|
+
maxCallsInRequest: 32, // allow:raw-byte-literal — RFC 8620 §3.6 default
|
|
70
|
+
maxObjectsInGet: 500, // allow:raw-byte-literal — RFC 8620 §3.6 default
|
|
71
|
+
maxObjectsInSet: 500, // allow:raw-byte-literal — RFC 8620 §3.6 default
|
|
72
|
+
maxSizeRequest: 10485760, // allow:raw-byte-literal — 10 MiB request body cap
|
|
73
|
+
maxBackRefDepth: 8,
|
|
74
|
+
maxUsingCapabilities: 32, // allow:raw-byte-literal — `using` array length cap
|
|
75
|
+
},
|
|
76
|
+
balanced: {
|
|
77
|
+
maxCallsInRequest: 128, // allow:raw-byte-literal — balanced call cap
|
|
78
|
+
maxObjectsInGet: 1000, // allow:raw-byte-literal — balanced object cap
|
|
79
|
+
maxObjectsInSet: 1000, // allow:raw-byte-literal — balanced object cap
|
|
80
|
+
maxSizeRequest: 52428800, // allow:raw-byte-literal — 50 MiB balanced
|
|
81
|
+
maxBackRefDepth: 16, // allow:raw-byte-literal — balanced depth
|
|
82
|
+
maxUsingCapabilities: 64, // allow:raw-byte-literal — balanced using cap
|
|
83
|
+
},
|
|
84
|
+
permissive: {
|
|
85
|
+
maxCallsInRequest: 512, // allow:raw-byte-literal — permissive call cap
|
|
86
|
+
maxObjectsInGet: 5000, // allow:raw-byte-literal — permissive object cap
|
|
87
|
+
maxObjectsInSet: 5000, // allow:raw-byte-literal — permissive object cap
|
|
88
|
+
maxSizeRequest: 104857600, // allow:raw-byte-literal — 100 MiB permissive
|
|
89
|
+
maxBackRefDepth: 32, // allow:raw-byte-literal — permissive depth
|
|
90
|
+
maxUsingCapabilities: 128, // allow:raw-byte-literal — permissive using cap
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
var COMPLIANCE_POSTURES = Object.freeze({
|
|
95
|
+
hipaa: "strict",
|
|
96
|
+
"pci-dss": "strict",
|
|
97
|
+
gdpr: "strict",
|
|
98
|
+
soc2: "strict",
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Capability URIs the server's core JMAP implementation always supports.
|
|
102
|
+
// Additional capabilities (mail / contacts / calendars / submission)
|
|
103
|
+
// the operator opts into via opts.serverCapabilities.
|
|
104
|
+
var CORE_CAPABILITIES = Object.freeze({
|
|
105
|
+
"urn:ietf:params:jmap:core": true,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* @primitive b.guardJmap.validate
|
|
110
|
+
* @signature b.guardJmap.validate(rawBody, opts?)
|
|
111
|
+
* @since 0.9.50
|
|
112
|
+
* @status stable
|
|
113
|
+
* @related b.guardImapCommand.validate, b.safeJson.parse
|
|
114
|
+
*
|
|
115
|
+
* Validate a JMAP request envelope. Accepts either a raw JSON string
|
|
116
|
+
* (bytes) or a pre-parsed object. Returns
|
|
117
|
+
* `{ using, methodCalls, createdIds }` on success; throws
|
|
118
|
+
* `GuardJmapError` with the matching `urn:ietf:params:jmap:error:*`
|
|
119
|
+
* URI on refusal.
|
|
120
|
+
*
|
|
121
|
+
* @opts
|
|
122
|
+
* profile: "strict" | "balanced" | "permissive",
|
|
123
|
+
* posture: "hipaa" | "pci-dss" | "gdpr" | "soc2",
|
|
124
|
+
* serverCapabilities: { "urn:ietf:params:jmap:mail": true, ... },
|
|
125
|
+
* // capability URIs the server has wired; `using`
|
|
126
|
+
* // entries not in this set are refused with
|
|
127
|
+
* // urn:ietf:params:jmap:error:unknownCapability
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* var parsed = b.guardJmap.validate(rawBody, {
|
|
131
|
+
* serverCapabilities: { "urn:ietf:params:jmap:mail": true },
|
|
132
|
+
* });
|
|
133
|
+
* // → { using: [...], methodCalls: [[methodName, args, clientId], ...] }
|
|
134
|
+
*/
|
|
135
|
+
function validate(rawBody, opts) {
|
|
136
|
+
opts = opts || {};
|
|
137
|
+
var profileName = typeof opts.profile === "string" ? opts.profile : DEFAULT_PROFILE;
|
|
138
|
+
if (opts.posture && COMPLIANCE_POSTURES[opts.posture]) {
|
|
139
|
+
profileName = COMPLIANCE_POSTURES[opts.posture];
|
|
140
|
+
}
|
|
141
|
+
var caps = PROFILES[profileName];
|
|
142
|
+
if (!caps) {
|
|
143
|
+
throw new GuardJmapError("guard-jmap/bad-profile",
|
|
144
|
+
"guardJmap.validate: unknown profile '" + profileName + "'");
|
|
145
|
+
}
|
|
146
|
+
// Clone serverCapabilities before injecting the core capability so we
|
|
147
|
+
// never mutate the operator-supplied object. mail.server.jmap.create
|
|
148
|
+
// passes its shared `serverCapabilities` into every validate() call;
|
|
149
|
+
// pre-fix this rewrote the operator's `urn:ietf:params:jmap:core`
|
|
150
|
+
// entry to `true` after the first request, breaking the Session
|
|
151
|
+
// resource's RFC 8620 §2 capability-object shape.
|
|
152
|
+
var serverCaps = Object.assign({}, opts.serverCapabilities || {});
|
|
153
|
+
// Always allow the core capability — the server provides it inherently.
|
|
154
|
+
serverCaps["urn:ietf:params:jmap:core"] = true;
|
|
155
|
+
|
|
156
|
+
// Parse if rawBody is a string. Cap the byte size before parsing.
|
|
157
|
+
var body;
|
|
158
|
+
if (typeof rawBody === "string" || Buffer.isBuffer(rawBody)) {
|
|
159
|
+
var s = typeof rawBody === "string" ? rawBody : rawBody.toString("utf8");
|
|
160
|
+
// Wire-protocol size cap MUST be measured in UTF-8 bytes, not
|
|
161
|
+
// JavaScript UTF-16 code units. A 1 MiB cap interpreted as code
|
|
162
|
+
// units lets non-ASCII payloads (emoji, CJK) past the gate at
|
|
163
|
+
// 2-4× the actual byte budget — directly weakens the DoS cap.
|
|
164
|
+
var byteLen = typeof rawBody === "string" ? Buffer.byteLength(s, "utf8") : rawBody.length;
|
|
165
|
+
if (byteLen > caps.maxSizeRequest) {
|
|
166
|
+
throw new GuardJmapError("urn:ietf:params:jmap:error:requestTooLarge",
|
|
167
|
+
"guardJmap.validate: request body " + byteLen +
|
|
168
|
+
" bytes exceeds cap " + caps.maxSizeRequest);
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
body = safeJson.parse(s);
|
|
172
|
+
} catch (e) {
|
|
173
|
+
throw new GuardJmapError("guard-jmap/bad-json",
|
|
174
|
+
"guardJmap.validate: body is not valid JSON: " + (e && e.message ? e.message : String(e)));
|
|
175
|
+
}
|
|
176
|
+
} else if (rawBody && typeof rawBody === "object") {
|
|
177
|
+
body = rawBody;
|
|
178
|
+
} else {
|
|
179
|
+
throw new GuardJmapError("guard-jmap/bad-input",
|
|
180
|
+
"guardJmap.validate: rawBody must be a JSON string, Buffer, or pre-parsed object");
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (typeof body !== "object" || body === null || Array.isArray(body)) {
|
|
184
|
+
throw new GuardJmapError("urn:ietf:params:jmap:error:invalidArguments",
|
|
185
|
+
"guardJmap.validate: request body must be a JSON object");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (!Array.isArray(body.using)) {
|
|
189
|
+
throw new GuardJmapError("urn:ietf:params:jmap:error:invalidArguments",
|
|
190
|
+
"guardJmap.validate: `using` must be an array of capability URIs");
|
|
191
|
+
}
|
|
192
|
+
if (body.using.length > caps.maxUsingCapabilities) {
|
|
193
|
+
throw new GuardJmapError("urn:ietf:params:jmap:error:invalidArguments",
|
|
194
|
+
"guardJmap.validate: `using` length " + body.using.length +
|
|
195
|
+
" exceeds cap " + caps.maxUsingCapabilities);
|
|
196
|
+
}
|
|
197
|
+
for (var ui = 0; ui < body.using.length; ui += 1) {
|
|
198
|
+
var cap = body.using[ui];
|
|
199
|
+
if (typeof cap !== "string") {
|
|
200
|
+
throw new GuardJmapError("urn:ietf:params:jmap:error:invalidArguments",
|
|
201
|
+
"guardJmap.validate: `using[" + ui + "]` must be a string capability URI");
|
|
202
|
+
}
|
|
203
|
+
if (!CORE_CAPABILITIES[cap] && !serverCaps[cap]) {
|
|
204
|
+
throw new GuardJmapError("urn:ietf:params:jmap:error:unknownCapability",
|
|
205
|
+
"guardJmap.validate: capability '" + cap + "' not advertised by this server");
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (!Array.isArray(body.methodCalls)) {
|
|
210
|
+
throw new GuardJmapError("urn:ietf:params:jmap:error:invalidArguments",
|
|
211
|
+
"guardJmap.validate: `methodCalls` must be an array");
|
|
212
|
+
}
|
|
213
|
+
if (body.methodCalls.length === 0) {
|
|
214
|
+
throw new GuardJmapError("urn:ietf:params:jmap:error:invalidArguments",
|
|
215
|
+
"guardJmap.validate: `methodCalls` must contain at least one call");
|
|
216
|
+
}
|
|
217
|
+
if (body.methodCalls.length > caps.maxCallsInRequest) {
|
|
218
|
+
throw new GuardJmapError("urn:ietf:params:jmap:error:limit/maxCallsInRequest",
|
|
219
|
+
"guardJmap.validate: " + body.methodCalls.length +
|
|
220
|
+
" methodCalls exceeds cap " + caps.maxCallsInRequest);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
var seenClientIds = Object.create(null);
|
|
224
|
+
for (var ci = 0; ci < body.methodCalls.length; ci += 1) {
|
|
225
|
+
var call = body.methodCalls[ci];
|
|
226
|
+
if (!Array.isArray(call) || call.length !== 3) {
|
|
227
|
+
throw new GuardJmapError("urn:ietf:params:jmap:error:invalidArguments",
|
|
228
|
+
"guardJmap.validate: methodCalls[" + ci + "] must be a 3-tuple [name, args, clientId]");
|
|
229
|
+
}
|
|
230
|
+
if (typeof call[0] !== "string") {
|
|
231
|
+
throw new GuardJmapError("urn:ietf:params:jmap:error:invalidArguments",
|
|
232
|
+
"guardJmap.validate: methodCalls[" + ci + "][0] (method name) must be a string");
|
|
233
|
+
}
|
|
234
|
+
if (typeof call[1] !== "object" || call[1] === null || Array.isArray(call[1])) {
|
|
235
|
+
throw new GuardJmapError("urn:ietf:params:jmap:error:invalidArguments",
|
|
236
|
+
"guardJmap.validate: methodCalls[" + ci + "][1] (args) must be an object");
|
|
237
|
+
}
|
|
238
|
+
if (typeof call[2] !== "string") {
|
|
239
|
+
throw new GuardJmapError("urn:ietf:params:jmap:error:invalidArguments",
|
|
240
|
+
"guardJmap.validate: methodCalls[" + ci + "][2] (clientId) must be a string");
|
|
241
|
+
}
|
|
242
|
+
if (call[2].length === 0 || call[2].length > 256) { // allow:raw-byte-literal — clientId length cap
|
|
243
|
+
throw new GuardJmapError("urn:ietf:params:jmap:error:invalidArguments",
|
|
244
|
+
"guardJmap.validate: methodCalls[" + ci + "][2] (clientId) length must be 1..256");
|
|
245
|
+
}
|
|
246
|
+
// Back-reference depth cap: count `resultOf` occurrences in the
|
|
247
|
+
// args subtree. Pathological depth would let a client chain
|
|
248
|
+
// hundreds of resolutions per call.
|
|
249
|
+
var refCount = _countBackRefs(call[1], 0, caps.maxBackRefDepth);
|
|
250
|
+
if (refCount === -1) {
|
|
251
|
+
throw new GuardJmapError("urn:ietf:params:jmap:error:limit/maxBackRefDepth",
|
|
252
|
+
"guardJmap.validate: methodCalls[" + ci + "] back-reference depth exceeds cap " +
|
|
253
|
+
caps.maxBackRefDepth);
|
|
254
|
+
}
|
|
255
|
+
seenClientIds[call[2]] = true;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
validateOpts.optionalPlainObject(body.createdIds,
|
|
259
|
+
"guardJmap.validate: `createdIds`",
|
|
260
|
+
GuardJmapError, "urn:ietf:params:jmap:error:invalidArguments",
|
|
261
|
+
"null or an object");
|
|
262
|
+
|
|
263
|
+
return {
|
|
264
|
+
using: body.using,
|
|
265
|
+
methodCalls: body.methodCalls,
|
|
266
|
+
createdIds: body.createdIds || null,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Walk args looking for back-reference markers
|
|
271
|
+
// (`#name`-prefixed keys per RFC 8620 §3.7 or a `resultOf` shape).
|
|
272
|
+
// Returns the maximum depth seen, or -1 if it exceeds maxDepth.
|
|
273
|
+
function _countBackRefs(node, depth, maxDepth) {
|
|
274
|
+
if (depth > maxDepth) return -1;
|
|
275
|
+
if (node === null || typeof node !== "object") return depth;
|
|
276
|
+
if (Array.isArray(node)) {
|
|
277
|
+
var maxA = depth;
|
|
278
|
+
for (var i = 0; i < node.length; i += 1) {
|
|
279
|
+
var d = _countBackRefs(node[i], depth + 1, maxDepth);
|
|
280
|
+
if (d === -1) return -1;
|
|
281
|
+
if (d > maxA) maxA = d;
|
|
282
|
+
}
|
|
283
|
+
return maxA;
|
|
284
|
+
}
|
|
285
|
+
var keys = Object.keys(node);
|
|
286
|
+
if (keys.length > 1000) return -1; // allow:raw-byte-literal — per-object key cap
|
|
287
|
+
var maxO = depth;
|
|
288
|
+
for (var k = 0; k < keys.length; k += 1) {
|
|
289
|
+
var key = keys[k];
|
|
290
|
+
var inc = (key === "resultOf" || key.charCodeAt(0) === 0x23) ? 1 : 0; // allow:raw-byte-literal — `#` (0x23) is the JMAP back-ref prefix
|
|
291
|
+
var d2 = _countBackRefs(node[key], depth + inc, maxDepth);
|
|
292
|
+
if (d2 === -1) return -1;
|
|
293
|
+
if (d2 > maxO) maxO = d2;
|
|
294
|
+
}
|
|
295
|
+
return maxO;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* @primitive b.guardJmap.compliancePosture
|
|
300
|
+
* @signature b.guardJmap.compliancePosture(posture)
|
|
301
|
+
* @since 0.9.50
|
|
302
|
+
* @status stable
|
|
303
|
+
*
|
|
304
|
+
* Return the effective profile for a compliance posture, or `null`
|
|
305
|
+
* for unknown names.
|
|
306
|
+
*
|
|
307
|
+
* @example
|
|
308
|
+
* b.guardJmap.compliancePosture("hipaa"); // → "strict"
|
|
309
|
+
*/
|
|
310
|
+
function compliancePosture(posture) {
|
|
311
|
+
return COMPLIANCE_POSTURES[posture] || null;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
module.exports = {
|
|
315
|
+
validate: validate,
|
|
316
|
+
compliancePosture: compliancePosture,
|
|
317
|
+
PROFILES: PROFILES,
|
|
318
|
+
COMPLIANCE_POSTURES: COMPLIANCE_POSTURES,
|
|
319
|
+
CORE_CAPABILITIES: CORE_CAPABILITIES,
|
|
320
|
+
GuardJmapError: GuardJmapError,
|
|
321
|
+
};
|