tina4-nodejs 3.13.92 → 3.13.94

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 (134) hide show
  1. package/CLAUDE.md +16 -3
  2. package/README.md +1 -1
  3. package/package.json +12 -9
  4. package/packages/cli/dist/bin.js +1260 -969
  5. package/packages/core/dist/index.js +1260 -969
  6. package/packages/core/src/devMailbox.ts +20 -44
  7. package/packages/core/src/index.ts +2 -2
  8. package/packages/core/src/messenger.ts +72 -0
  9. package/packages/core/src/queueBackends/kafkaBackend.ts +108 -12
  10. package/packages/core/src/sessionHandlers/childError.ts +72 -0
  11. package/packages/core/src/sessionHandlers/mongoClient.ts +9 -3
  12. package/packages/core/src/sessionHandlers/redisHandler.ts +18 -5
  13. package/packages/core/src/sessionHandlers/respClient.ts +5 -1
  14. package/packages/frond/dist/index.js +74 -31
  15. package/packages/frond/src/engine.ts +99 -33
  16. package/packages/orm/dist/index.js +3055 -2764
  17. package/packages/orm/src/adapters/sqlite.ts +4 -1
  18. package/packages/orm/src/database.ts +108 -8
  19. package/types/cli/src/bin.d.ts +92 -0
  20. package/types/cli/src/commands/build.d.ts +2 -0
  21. package/types/cli/src/commands/generate.d.ts +47 -0
  22. package/types/cli/src/commands/init.d.ts +1 -0
  23. package/types/cli/src/commands/metrics.d.ts +6 -0
  24. package/types/cli/src/commands/migrate.d.ts +1 -0
  25. package/types/cli/src/commands/migrateCreate.d.ts +1 -0
  26. package/types/cli/src/commands/migrateRollback.d.ts +1 -0
  27. package/types/cli/src/commands/migrateStatus.d.ts +1 -0
  28. package/types/cli/src/commands/queue.d.ts +20 -0
  29. package/types/cli/src/commands/routes.d.ts +1 -0
  30. package/types/cli/src/commands/seed.d.ts +1 -0
  31. package/types/cli/src/commands/serve.d.ts +6 -0
  32. package/types/cli/src/commands/test.d.ts +1 -0
  33. package/types/core/src/ai.d.ts +64 -0
  34. package/types/core/src/api.d.ts +262 -0
  35. package/types/core/src/auth.d.ts +154 -0
  36. package/types/core/src/authGate.d.ts +20 -0
  37. package/types/core/src/background.d.ts +34 -0
  38. package/types/core/src/cache.d.ts +160 -0
  39. package/types/core/src/constants.d.ts +38 -0
  40. package/types/core/src/container.d.ts +44 -0
  41. package/types/core/src/context/chunker.d.ts +31 -0
  42. package/types/core/src/context/index.d.ts +93 -0
  43. package/types/core/src/devAdmin.d.ts +179 -0
  44. package/types/core/src/devMailbox.d.ts +54 -0
  45. package/types/core/src/docs.d.ts +141 -0
  46. package/types/core/src/docsAutoDiscovery.d.ts +6 -0
  47. package/types/core/src/dotenv.d.ts +65 -0
  48. package/types/core/src/env.d.ts +28 -0
  49. package/types/core/src/errorOverlay.d.ts +36 -0
  50. package/types/core/src/events.d.ts +75 -0
  51. package/types/core/src/fakeData.d.ts +55 -0
  52. package/types/core/src/feedback.d.ts +90 -0
  53. package/types/core/src/graphql.d.ts +207 -0
  54. package/types/core/src/health.d.ts +22 -0
  55. package/types/core/src/htmlElement.d.ts +75 -0
  56. package/types/core/src/i18n.d.ts +37 -0
  57. package/types/core/src/index.d.ts +93 -0
  58. package/types/core/src/job.d.ts +39 -0
  59. package/types/core/src/logger.d.ts +123 -0
  60. package/types/core/src/mcp.d.ts +248 -0
  61. package/types/core/src/messenger.d.ts +191 -0
  62. package/types/core/src/metrics.d.ts +77 -0
  63. package/types/core/src/middleware.d.ts +207 -0
  64. package/types/core/src/mqtt.d.ts +257 -0
  65. package/types/core/src/mqttMessage.d.ts +67 -0
  66. package/types/core/src/plan.d.ts +96 -0
  67. package/types/core/src/projectIndex.d.ts +56 -0
  68. package/types/core/src/queue.d.ts +219 -0
  69. package/types/core/src/queueBackends/kafkaBackend.d.ts +117 -0
  70. package/types/core/src/queueBackends/liteBackend.d.ts +119 -0
  71. package/types/core/src/queueBackends/mongoBackend.d.ts +97 -0
  72. package/types/core/src/queueBackends/rabbitmqBackend.d.ts +55 -0
  73. package/types/core/src/rateLimiter.d.ts +49 -0
  74. package/types/core/src/request.d.ts +25 -0
  75. package/types/core/src/response.d.ts +28 -0
  76. package/types/core/src/routeDiscovery.d.ts +12 -0
  77. package/types/core/src/router.d.ts +355 -0
  78. package/types/core/src/scss.d.ts +19 -0
  79. package/types/core/src/server.d.ts +131 -0
  80. package/types/core/src/service.d.ts +115 -0
  81. package/types/core/src/session.d.ts +256 -0
  82. package/types/core/src/sessionHandlers/childError.d.ts +34 -0
  83. package/types/core/src/sessionHandlers/databaseHandler.d.ts +42 -0
  84. package/types/core/src/sessionHandlers/mongoClient.d.ts +24 -0
  85. package/types/core/src/sessionHandlers/mongoHandler.d.ts +61 -0
  86. package/types/core/src/sessionHandlers/redisHandler.d.ts +60 -0
  87. package/types/core/src/sessionHandlers/respClient.d.ts +22 -0
  88. package/types/core/src/sessionHandlers/valkeyHandler.d.ts +65 -0
  89. package/types/core/src/static.d.ts +2 -0
  90. package/types/core/src/test.d.ts +94 -0
  91. package/types/core/src/testClient.d.ts +36 -0
  92. package/types/core/src/testing.d.ts +58 -0
  93. package/types/core/src/types.d.ts +219 -0
  94. package/types/core/src/validator.d.ts +52 -0
  95. package/types/core/src/websocket.d.ts +376 -0
  96. package/types/core/src/websocketBackplane.d.ts +166 -0
  97. package/types/core/src/websocketConnection.d.ts +54 -0
  98. package/types/core/src/wsdl.d.ts +101 -0
  99. package/types/frond/src/engine.d.ts +263 -0
  100. package/types/frond/src/index.d.ts +2 -0
  101. package/types/orm/src/adapters/firebird.d.ts +138 -0
  102. package/types/orm/src/adapters/mongodb.d.ts +81 -0
  103. package/types/orm/src/adapters/mssql.d.ts +70 -0
  104. package/types/orm/src/adapters/mysql.d.ts +66 -0
  105. package/types/orm/src/adapters/odbc.d.ts +97 -0
  106. package/types/orm/src/adapters/postgres.d.ts +85 -0
  107. package/types/orm/src/adapters/sqlite.d.ts +56 -0
  108. package/types/orm/src/autoCrud.d.ts +73 -0
  109. package/types/orm/src/baseModel.d.ts +391 -0
  110. package/types/orm/src/cachedDatabase.d.ts +177 -0
  111. package/types/orm/src/database.d.ts +609 -0
  112. package/types/orm/src/databaseResult.d.ts +85 -0
  113. package/types/orm/src/docstore.d.ts +182 -0
  114. package/types/orm/src/fakeData.d.ts +22 -0
  115. package/types/orm/src/index.d.ts +40 -0
  116. package/types/orm/src/migration.d.ts +275 -0
  117. package/types/orm/src/model.d.ts +7 -0
  118. package/types/orm/src/query.d.ts +14 -0
  119. package/types/orm/src/queryBuilder.d.ts +173 -0
  120. package/types/orm/src/realtime/index.d.ts +7 -0
  121. package/types/orm/src/realtime/models/attachment.d.ts +43 -0
  122. package/types/orm/src/realtime/models/channel.d.ts +32 -0
  123. package/types/orm/src/realtime/models/channelMember.d.ts +32 -0
  124. package/types/orm/src/realtime/models/message.d.ts +36 -0
  125. package/types/orm/src/realtime/models/workspace.d.ts +26 -0
  126. package/types/orm/src/realtime/realtime.d.ts +24 -0
  127. package/types/orm/src/realtime/storage.d.ts +61 -0
  128. package/types/orm/src/seeder.d.ts +118 -0
  129. package/types/orm/src/sqlTranslator.d.ts +134 -0
  130. package/types/orm/src/types.d.ts +138 -0
  131. package/types/orm/src/validation.d.ts +6 -0
  132. package/types/swagger/src/generator.d.ts +46 -0
  133. package/types/swagger/src/index.d.ts +2 -0
  134. package/types/swagger/src/ui.d.ts +11 -0
@@ -41,6 +41,29 @@ var TERMINATOR_TAGS = /* @__PURE__ */ new Set([
41
41
  "endset",
42
42
  "endspaceless"
43
43
  ]);
44
+ var GATEABLE_TAGS = /* @__PURE__ */ new Set([
45
+ "autoescape",
46
+ "cache",
47
+ "for",
48
+ "from",
49
+ "if",
50
+ "import",
51
+ "include",
52
+ "live",
53
+ "macro",
54
+ "set",
55
+ "spaceless"
56
+ ]);
57
+ var BLOCK_TAG_ENDS = {
58
+ autoescape: "endautoescape",
59
+ cache: "endcache",
60
+ for: "endfor",
61
+ if: "endif",
62
+ live: "endlive",
63
+ macro: "endmacro",
64
+ set: "endset",
65
+ spaceless: "endspaceless"
66
+ };
44
67
  function jsonText(value) {
45
68
  try {
46
69
  const text = JSON.stringify(value);
@@ -1680,42 +1703,27 @@ var Frond = class _Frond {
1680
1703
  if (stripA && i + 1 < tokens.length && tokens[i + 1][0] === "TEXT") {
1681
1704
  tokens[i + 1] = ["TEXT", tokens[i + 1][1].replace(LEADING_WS_RE, "")];
1682
1705
  }
1683
- if (tag === "if") {
1684
- if (this._sandbox && this._allowedTags !== null && !this._allowedTags.has("if")) {
1685
- const skip = this.skipBlock(tokens, i, "if", "endif");
1686
- i = skip;
1687
- } else {
1688
- const [result, skip] = this.handleIf(tokens, i, context);
1689
- output.push(result);
1690
- i = skip;
1691
- }
1706
+ if (!this.tagPermitted(tag)) {
1707
+ i = this.skipDeniedTag(tokens, i, tag, content);
1708
+ } else if (tag === "if") {
1709
+ const [result, skip] = this.handleIf(tokens, i, context);
1710
+ output.push(result);
1711
+ i = skip;
1692
1712
  } else if (tag === "for") {
1693
- if (this._sandbox && this._allowedTags !== null && !this._allowedTags.has("for")) {
1694
- const skip = this.skipBlock(tokens, i, "for", "endfor");
1695
- i = skip;
1696
- } else {
1697
- const [result, skip] = this.handleFor(tokens, i, context);
1698
- output.push(result);
1699
- i = skip;
1700
- }
1713
+ const [result, skip] = this.handleFor(tokens, i, context);
1714
+ output.push(result);
1715
+ i = skip;
1701
1716
  } else if (tag === "set") {
1702
- const isBlockSet = !content.includes("=");
1703
- if (this._sandbox && this._allowedTags !== null && !this._allowedTags.has("set")) {
1704
- i = isBlockSet ? this.skipBlock(tokens, i, "set", "endset") : i + 1;
1705
- } else if (isBlockSet) {
1717
+ if (!content.includes("=")) {
1706
1718
  i = this.handleSetBlock(tokens, i, context);
1707
1719
  } else {
1708
1720
  this.handleSet(content, context);
1709
1721
  i++;
1710
1722
  }
1711
1723
  } else if (tag === "include") {
1712
- if (this._sandbox && this._allowedTags !== null && !this._allowedTags.has("include")) {
1713
- i++;
1714
- } else {
1715
- const result = this.handleInclude(content, context);
1716
- output.push(result);
1717
- i++;
1718
- }
1724
+ const result = this.handleInclude(content, context);
1725
+ output.push(result);
1726
+ i++;
1719
1727
  } else if (tag === "macro") {
1720
1728
  const skip = this.handleMacro(tokens, i, context);
1721
1729
  i = skip;
@@ -1760,6 +1768,41 @@ var Frond = class _Frond {
1760
1768
  }
1761
1769
  return output.join("");
1762
1770
  }
1771
+ /**
1772
+ * May this filter RUN under the current sandbox?
1773
+ *
1774
+ * The escaping decision has to ask this rather than read the filter name out of
1775
+ * the source. Node carries safety as a FLAG rather than as a value-level marker
1776
+ * (Python and Ruby return a SafeString, PHP prepends a RAW_MARKER -- all three
1777
+ * produced only by actually running the filter), so here the name alone was
1778
+ * enough to suppress auto-escaping even when the filter was denied and skipped.
1779
+ */
1780
+ filterPermitted(name) {
1781
+ if (!this._sandbox || this._allowedFilters === null) return true;
1782
+ return this._allowedFilters.has(name);
1783
+ }
1784
+ /**
1785
+ * May this tag run under the current sandbox?
1786
+ *
1787
+ * One gate for every tag, so the allow-list governs the whole tag vocabulary
1788
+ * instead of the four names that happened to be checked individually.
1789
+ */
1790
+ tagPermitted(tag) {
1791
+ if (!this._sandbox || this._allowedTags === null) return true;
1792
+ if (!GATEABLE_TAGS.has(tag)) return true;
1793
+ return this._allowedTags.has(tag);
1794
+ }
1795
+ /**
1796
+ * Consume a denied tag WITHOUT running it, returning the index past its body.
1797
+ *
1798
+ * Advancing a single token past a body-owning tag would leave the body's tokens
1799
+ * to render at the TOP level, leaking exactly the content the sandbox denied.
1800
+ */
1801
+ skipDeniedTag(tokens, start, tag, content) {
1802
+ const closeTag = BLOCK_TAG_ENDS[tag];
1803
+ if (closeTag === void 0 || tag === "set" && content.includes("=")) return start + 1;
1804
+ return this.skipBlock(tokens, start, tag, closeTag);
1805
+ }
1763
1806
  skipBlock(tokens, start, openTag, closeTag) {
1764
1807
  let depth = 0;
1765
1808
  let i = start + 1;
@@ -1767,7 +1810,7 @@ var Frond = class _Frond {
1767
1810
  if (tokens[i][0] === "BLOCK") {
1768
1811
  const [content] = stripTag(tokens[i][1]);
1769
1812
  const tag = content.split(/\s+/)[0] || "";
1770
- if (tag === openTag) depth++;
1813
+ if (tag === openTag && !(openTag === "set" && content.includes("="))) depth++;
1771
1814
  else if (tag === closeTag) {
1772
1815
  if (depth === 0) return i + 1;
1773
1816
  depth--;
@@ -1952,11 +1995,11 @@ var Frond = class _Frond {
1952
1995
  for (const [fname, rawArgs] of filters) {
1953
1996
  const args = rawArgs.map((a) => a instanceof VarRef ? evalExpr(a.name, context) : a);
1954
1997
  if (fname === "raw" || fname === "safe") {
1955
- isSafe = true;
1998
+ if (this.filterPermitted(fname)) isSafe = true;
1956
1999
  continue;
1957
2000
  }
1958
2001
  if (fname === "escape" || fname === "e") {
1959
- isSafe = true;
2002
+ if (this.filterPermitted(fname)) isSafe = true;
1960
2003
  }
1961
2004
  if (this._sandbox && this._allowedFilters !== null) {
1962
2005
  if (!this._allowedFilters.has(fname)) {
@@ -59,6 +59,35 @@ const TERMINATOR_TAGS = new Set([
59
59
  "endif", "endlive", "endmacro", "endraw", "endset", "endspaceless",
60
60
  ]);
61
61
 
62
+ /**
63
+ * Author-written tags the sandbox allow-list governs. Mirrors Python's
64
+ * _GATEABLE_TAGS, PHP's GATEABLE_TAGS and Ruby's GATEABLE_TAGS. A tag absent from
65
+ * this set is structural, not an author capability, and is never gated -- `block`
66
+ * and `extends` are template inheritance, and `raw` is consumed by the tokenizer.
67
+ * Both spellings of set ({% set x = 1 %} and {% set x %}...{% endset %}) dispatch
68
+ * under "set", so one entry covers the pair.
69
+ */
70
+ const GATEABLE_TAGS = new Set([
71
+ "autoescape", "cache", "for", "from", "if", "import", "include", "live",
72
+ "macro", "set", "spaceless",
73
+ ]);
74
+
75
+ /**
76
+ * Gateable tags that OWN A BODY, mapped to the terminator closing it. A denied tag
77
+ * has to consume its body or the body's tokens render at the top level, leaking
78
+ * exactly the content the sandbox denied.
79
+ */
80
+ const BLOCK_TAG_ENDS: Record<string, string> = {
81
+ autoescape: "endautoescape",
82
+ cache: "endcache",
83
+ for: "endfor",
84
+ if: "endif",
85
+ live: "endlive",
86
+ macro: "endmacro",
87
+ set: "endset",
88
+ spaceless: "endspaceless",
89
+ };
90
+
62
91
  /**
63
92
  * Serialize a value to compact JSON text that is always valid JSON.
64
93
  *
@@ -2082,48 +2111,36 @@ export class Frond {
2082
2111
  tokens[i + 1] = ["TEXT", tokens[i + 1][1].replace(LEADING_WS_RE, "")];
2083
2112
  }
2084
2113
 
2085
- if (tag === "if") {
2086
- // Sandbox check
2087
- if (this._sandbox && this._allowedTags !== null && !this._allowedTags.has("if")) {
2088
- const skip = this.skipBlock(tokens, i, "if", "endif");
2089
- i = skip;
2090
- } else {
2091
- const [result, skip] = this.handleIf(tokens, i, context);
2092
- output.push(result);
2093
- i = skip;
2094
- }
2114
+ if (!this.tagPermitted(tag)) {
2115
+ // ONE sandbox gate for the whole tag vocabulary. Previously only if, for,
2116
+ // set and include were checked, so every other tag ignored the allow-list
2117
+ // -- {% autoescape false %} could switch escaping off from inside a
2118
+ // sandbox whose tags were restricted to something else entirely.
2119
+ i = this.skipDeniedTag(tokens, i, tag, content);
2120
+ } else if (tag === "if") {
2121
+ const [result, skip] = this.handleIf(tokens, i, context);
2122
+ output.push(result);
2123
+ i = skip;
2095
2124
  } else if (tag === "for") {
2096
- if (this._sandbox && this._allowedTags !== null && !this._allowedTags.has("for")) {
2097
- const skip = this.skipBlock(tokens, i, "for", "endfor");
2098
- i = skip;
2099
- } else {
2100
- const [result, skip] = this.handleFor(tokens, i, context);
2101
- output.push(result);
2102
- i = skip;
2103
- }
2125
+ const [result, skip] = this.handleFor(tokens, i, context);
2126
+ output.push(result);
2127
+ i = skip;
2104
2128
  } else if (tag === "set") {
2105
2129
  // An assignment has an "="; without one this is the BLOCK form,
2106
2130
  // {% set name %}...{% endset %}, which captures its rendered body. A
2107
2131
  // bare includes() is exact here, not a shortcut: the block form's tag
2108
2132
  // content is only ever "set <name>", so an "=" anywhere -- even inside
2109
2133
  // a quoted value like {% set m = "a = b" %} -- means assignment.
2110
- const isBlockSet = !content.includes("=");
2111
- if (this._sandbox && this._allowedTags !== null && !this._allowedTags.has("set")) {
2112
- i = isBlockSet ? this.skipBlock(tokens, i, "set", "endset") : i + 1;
2113
- } else if (isBlockSet) {
2134
+ if (!content.includes("=")) {
2114
2135
  i = this.handleSetBlock(tokens, i, context);
2115
2136
  } else {
2116
2137
  this.handleSet(content, context);
2117
2138
  i++;
2118
2139
  }
2119
2140
  } else if (tag === "include") {
2120
- if (this._sandbox && this._allowedTags !== null && !this._allowedTags.has("include")) {
2121
- i++;
2122
- } else {
2123
- const result = this.handleInclude(content, context);
2124
- output.push(result);
2125
- i++;
2126
- }
2141
+ const result = this.handleInclude(content, context);
2142
+ output.push(result);
2143
+ i++;
2127
2144
  } else if (tag === "macro") {
2128
2145
  const skip = this.handleMacro(tokens, i, context);
2129
2146
  i = skip;
@@ -2176,6 +2193,46 @@ export class Frond {
2176
2193
  return output.join("");
2177
2194
  }
2178
2195
 
2196
+ /**
2197
+ * May this filter RUN under the current sandbox?
2198
+ *
2199
+ * The escaping decision has to ask this rather than read the filter name out of
2200
+ * the source. Node carries safety as a FLAG rather than as a value-level marker
2201
+ * (Python and Ruby return a SafeString, PHP prepends a RAW_MARKER -- all three
2202
+ * produced only by actually running the filter), so here the name alone was
2203
+ * enough to suppress auto-escaping even when the filter was denied and skipped.
2204
+ */
2205
+ private filterPermitted(name: string): boolean {
2206
+ if (!this._sandbox || this._allowedFilters === null) return true;
2207
+ return this._allowedFilters.has(name);
2208
+ }
2209
+
2210
+ /**
2211
+ * May this tag run under the current sandbox?
2212
+ *
2213
+ * One gate for every tag, so the allow-list governs the whole tag vocabulary
2214
+ * instead of the four names that happened to be checked individually.
2215
+ */
2216
+ private tagPermitted(tag: string): boolean {
2217
+ if (!this._sandbox || this._allowedTags === null) return true;
2218
+ if (!GATEABLE_TAGS.has(tag)) return true; // structural, not an author capability
2219
+ return this._allowedTags.has(tag);
2220
+ }
2221
+
2222
+ /**
2223
+ * Consume a denied tag WITHOUT running it, returning the index past its body.
2224
+ *
2225
+ * Advancing a single token past a body-owning tag would leave the body's tokens
2226
+ * to render at the TOP level, leaking exactly the content the sandbox denied.
2227
+ */
2228
+ private skipDeniedTag(tokens: Token[], start: number, tag: string, content: string): number {
2229
+ const closeTag = BLOCK_TAG_ENDS[tag];
2230
+ // {% set x = 1 %} is an assignment and owns no body; {% set x %}...{% endset %}
2231
+ // captures one. Same exact-"=" test the dispatch uses.
2232
+ if (closeTag === undefined || (tag === "set" && content.includes("="))) return start + 1;
2233
+ return this.skipBlock(tokens, start, tag, closeTag);
2234
+ }
2235
+
2179
2236
  private skipBlock(tokens: Token[], start: number, openTag: string, closeTag: string): number {
2180
2237
  let depth = 0;
2181
2238
  let i = start + 1;
@@ -2183,7 +2240,9 @@ export class Frond {
2183
2240
  if (tokens[i][0] === "BLOCK") {
2184
2241
  const [content] = stripTag(tokens[i][1]);
2185
2242
  const tag = content.split(/\s+/)[0] || "";
2186
- if (tag === openTag) depth++;
2243
+ // A nested assignment-form set opens no body, so it must not nest -- counting
2244
+ // it would consume past the real {% endset %} and swallow trailing content.
2245
+ if (tag === openTag && !(openTag === "set" && content.includes("="))) depth++;
2187
2246
  else if (tag === closeTag) {
2188
2247
  if (depth === 0) return i + 1;
2189
2248
  depth--;
@@ -2388,12 +2447,19 @@ export class Frond {
2388
2447
  for (const [fname, rawArgs] of filters) {
2389
2448
  const args = rawArgs.map((a) => (a instanceof VarRef ? evalExpr(a.name, context) : a));
2390
2449
  if (fname === "raw" || fname === "safe") {
2391
- isSafe = true;
2450
+ // Decide from what was permitted to RUN, not from what the source asked
2451
+ // for. Marking the value safe here regardless meant a DENIED raw produced
2452
+ // byte-identical output to an allowed one -- the allow-list entry that
2453
+ // governs XSS escaping did nothing at all.
2454
+ if (this.filterPermitted(fname)) isSafe = true;
2392
2455
  continue;
2393
2456
  }
2394
- // escape/e filter marks output as safe (already escaped)
2457
+ // escape/e filter marks output as safe (already escaped) -- but ONLY when it
2458
+ // is permitted to run. Node's escape returns a plain string, so this flag is
2459
+ // what suppresses auto-escaping; setting it for a DENIED escape emitted the
2460
+ // value unescaped, having never escaped it.
2395
2461
  if (fname === "escape" || fname === "e") {
2396
- isSafe = true;
2462
+ if (this.filterPermitted(fname)) isSafe = true;
2397
2463
  }
2398
2464
 
2399
2465
  // Sandbox: check filter access