meteor-security

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Meteor security

Meteor安全

Meteor's security model is opinionated: the server holds authority, the client cannot be trusted, and the only places that filter data before it reaches users are methods (write paths) and publications (read paths).
Meteor的安全模型具有明确的设计理念:服务器拥有权限,客户端不可信,只有方法(写入路径)和发布(读取路径)是数据到达用户前的过滤节点。

Decision flow

决策流程

  1. Audit every method: does it
    check()
    every argument and guard on
    this.userId
    or
    Meteor.userId()
    when authentication matters?
  2. Audit every publication: does it filter by
    this.userId
    (when user-specific) and project columns with
    fields
    ?
  3. Add
    audit-argument-checks
    in dev to catch missing
    check()
    .
  4. Add
    browser-policy
    and configure CSP.
  5. Add
    DDPRateLimiter
    rules for sensitive methods (login, password reset, resource creation).
  6. If the app uses OAuth, set
    oauthSecretKey
    to encrypt provider secrets at rest.
  7. Remove
    allow
    /
    deny
    rules. They are legacy and easy to misuse; use methods instead.
  1. 审计每个方法:当涉及身份验证时,是否对每个参数执行
    check()
    ,并通过
    this.userId
    Meteor.userId()
    进行防护?
  2. 审计每个发布:如果是用户特定数据,是否通过
    this.userId
    过滤,并使用
    fields
    指定返回字段?
  3. 在开发环境中添加
    audit-argument-checks
    以捕获缺失的
    check()
  4. 添加
    browser-policy
    并配置CSP。
  5. 为敏感方法(登录、密码重置、资源创建)添加
    DDPRateLimiter
    规则。
  6. 如果应用使用OAuth,设置
    oauthSecretKey
    以加密存储的提供商密钥。
  7. 移除
    allow
    /
    deny
    规则。它们属于遗留特性,容易误用;建议使用方法替代。

Method guard checklist

方法防护检查清单

javascript
import { Meteor } from "meteor/meteor";
import { check, Match } from "meteor/check";

Meteor.methods({
  async updateProfile(payload) {
    check(payload, { displayName: String, bio: Match.Optional(String) });
    if (!this.userId) {
      throw new Meteor.Error("not-authorized");
    }
    await Meteor.users.updateAsync(this.userId, { $set: { profile: payload } });
  },
  async updateAddress(payload) {
    check(payload, String);
    if (!Meteor.userId()) {
      throw new Meteor.Error("not-authorized");
    }
    await Meteor.users.updateAsync(Meteor.userId(), { $set: { address: payload } });
  },
});
Reject any method that does not match:
check
on every argument, userId gate when needed,
Meteor.Error(code, reason)
for failures,
*Async
Mongo on the server.
javascript
import { Meteor } from "meteor/meteor";
import { check, Match } from "meteor/check";

Meteor.methods({
  async updateProfile(payload) {
    check(payload, { displayName: String, bio: Match.Optional(String) });
    if (!this.userId) {
      throw new Meteor.Error("not-authorized");
    }
    await Meteor.users.updateAsync(this.userId, { $set: { profile: payload } });
  },
  async updateAddress(payload) {
    check(payload, String);
    if (!Meteor.userId()) {
      throw new Meteor.Error("not-authorized");
    }
    await Meteor.users.updateAsync(Meteor.userId(), { $set: { address: payload } });
  },
});
拒绝任何不符合以下要求的方法:对每个参数执行
check()
、必要时添加userId防护、使用
Meteor.Error(code, reason)
处理失败、在服务器端使用
*Async
风格的Mongo操作。

Publication guard checklist

发布防护检查清单

javascript
Meteor.publish("items.mine", function () {
  if (!this.userId) return this.ready();
  return Items.find(
    { ownerId: this.userId },
    { fields: { title: 1, qty: 1 }, limit: 200 },
  );
});
Reject any publication that returns an unbounded cursor, omits the field projection, or skips a userId filter on user-specific data.
javascript
Meteor.publish("items.mine", function () {
  if (!this.userId) return this.ready();
  return Items.find(
    { ownerId: this.userId },
    { fields: { title: 1, qty: 1 }, limit: 200 },
  );
});
拒绝任何返回无限制游标、省略字段投影或对用户特定数据跳过userId过滤的发布。

CSP via
browser-policy

通过
browser-policy
配置CSP

bash
meteor add browser-policy
javascript
// server top-level or inside Meteor.startup
import { BrowserPolicy } from "meteor/browser-policy-common";
import { Meteor } from "meteor/meteor";

Meteor.startup(async () => {
  await BrowserPolicy.content.disallowInlineScripts();
  BrowserPolicy.content.disallowEval();
  BrowserPolicy.framing.disallow();
});
BrowserPolicy
is server-only. Configure it during module initialization or startup so every request receives one deterministic process-wide policy. The current implementation invalidates its cached CSP after a mutation, but do not mutate this global policy per request or per user. See
references/browser-policy-csp.md
for recipes (Stripe, Google Maps, fonts, inline-style allowance).
bash
meteor add browser-policy
javascript
// 服务器顶层或Meteor.startup内部
import { BrowserPolicy } from "meteor/browser-policy-common";
import { Meteor } from "meteor/meteor";

Meteor.startup(async () => {
  await BrowserPolicy.content.disallowInlineScripts();
  BrowserPolicy.content.disallowEval();
  BrowserPolicy.framing.disallow();
});
BrowserPolicy
仅在服务器端生效。在模块初始化或启动阶段进行配置,确保每个请求都接收统一的全局策略。当前实现会在策略变更后失效缓存的CSP,但请勿针对单个请求或用户修改此全局策略。有关配置示例(Stripe、Google Maps、字体、允许内联样式),请参阅
references/browser-policy-csp.md

DDPRateLimiter for sensitive methods

为敏感方法配置DDPRateLimiter

javascript
import { DDPRateLimiter } from "meteor/ddp-rate-limiter";

DDPRateLimiter.addRule(
  {
    type: "method",
    name: "login",
    clientAddress: () => true,
  },
  5,
  60000,                  // 5 attempts per 60s, per IP
);
Only matcher fields contribute to the rate-limit bucket key. Without
clientAddress
,
connectionId
, or
userId
, every matching caller shares one global bucket. Meteor 3.5+ permits async matcher functions for database-backed decisions; keep their queries fast because the connection waits for them. On Meteor 3.0 through 3.4, matchers must stay synchronous. Use a fixed rule, precomputed synchronous state, or upgrade rather than awaiting Mongo there.
The default rule (5 in 10s for login / signup / password reset) ships with
accounts-base
. Remove with
Accounts.removeDefaultRateLimit()
only if you replace it.
javascript
import { DDPRateLimiter } from "meteor/ddp-rate-limiter";

DDPRateLimiter.addRule(
  {
    type: "method",
    name: "login",
    clientAddress: () => true,
  },
  5,
  60000,                  // 每个IP每分钟最多5次尝试
);
只有匹配器字段会计入速率限制桶的键。如果没有
clientAddress
connectionId
userId
,所有匹配的调用者将共享一个全局桶。Meteor 3.5+允许使用异步匹配器函数来基于数据库做决策;请确保查询速度快,因为连接会等待其执行结果。在Meteor 3.0至3.4版本中,匹配器必须保持同步。建议使用固定规则、预计算的同步状态,或者升级版本,而非在这些版本中等待Mongo操作。
accounts-base
内置了默认规则(登录/注册/密码重置操作10秒内最多5次尝试)。只有在替换该规则时,才使用
Accounts.removeDefaultRateLimit()
移除它。

OAuth secret encryption

OAuth密钥加密

Add
oauth-encryption
and pass a 16-byte base64 key (NOT 32 bytes) to
Accounts.config
at module top level (not inside
Meteor.startup
):
bash
meteor node -e "console.log(require('crypto').randomBytes(16).toString('base64'))"
javascript
import { Accounts } from "meteor/accounts-base";

Accounts.config({
  oauthSecretKey: Meteor.settings.oauthSecretKey,
});
At startup,
accounts-oauth
seals an unsealed provider application secret at
ServiceConfiguration.configurations.secret
. Provider packages also seal supported per-user token fields, such as
services.github.accessToken
or Twitter's
accessTokenSecret
. There is no generic
Meteor.users.services.<provider>.secret
field. Inspect the provider schema before asserting which user credential is encrypted.
添加
oauth-encryption
包,并在模块顶层(而非
Meteor.startup
内部)将一个16字节的base64密钥(注意不是32字节)传入
Accounts.config
bash
meteor node -e "console.log(require('crypto').randomBytes(16).toString('base64'))"
javascript
import { Accounts } from "meteor/accounts-base";

Accounts.config({
  oauthSecretKey: Meteor.settings.oauthSecretKey,
});
启动时,
accounts-oauth
会加密
ServiceConfiguration.configurations.secret
中未加密的提供商应用密钥。提供商包还会加密支持的每个用户令牌字段,例如
services.github.accessToken
或Twitter的
accessTokenSecret
。不存在通用的
Meteor.users.services.<provider>.secret
字段。在断言哪些用户凭证已加密之前,请检查提供商的架构。

audit-argument-checks

audit-argument-checks

bash
meteor add audit-argument-checks
Throws if any method or publication runs without
check()
covering every argument. Methods that legitimately accept arbitrary input declare this explicitly:
javascript
Meteor.methods({
  rawLog(...args) {
    check(args, [Match.Any]);
    // ...
  },
});
bash
meteor add audit-argument-checks
如果任何方法或发布未对所有参数执行
check()
,该包会抛出错误。确实需要接受任意输入的方法需显式声明:
javascript
Meteor.methods({
  rawLog(...args) {
    check(args, [Match.Any]);
    // ...
  },
});

Anti-patterns

反模式

  • Collection.allow
    /
    Collection.deny
    rules. Legacy; easy to combine into a soft-fail. Replace with methods.
  • Meteor.settings.public.<secret>
    . The client sees
    public
    . Move secrets to the top level of
    settings.json
    .
  • Publish the entire
    Meteor.users
    collection. Always project (e.g.
    fields: { username: 1, profile: 1 }
    ) and filter. Publish email only to the owning user or another explicitly authorized audience.
  • Use
    BrowserPolicy.content.allowOriginForAll
    for a third-party script. It grants the origin to every current content directive. Allow only the script, frame, connect, image, style, or font directives the integration needs.
  • Methods that accept callback-shaped arguments. Functions cannot travel over DDP.
  • Call
    Accounts.config({ oauthSecretKey })
    inside
    Meteor.startup
    . Must be at module top level so it loads before the OAuth packages read it.
  • Collection.allow
    /
    Collection.deny
    规则:属于遗留特性,容易组合导致失效。建议使用方法替代。
  • Meteor.settings.public.<secret>
    :客户端可以看到
    public
    下的内容。请将密钥移至
    settings.json
    的顶层。
  • 发布整个
    Meteor.users
    集合:始终要进行字段投影(例如
    fields: { username: 1, profile: 1 }
    )和过滤。仅向用户本人或明确授权的受众发布邮箱信息。
  • 为第三方脚本使用
    BrowserPolicy.content.allowOriginForAll
    :这会将该源授权给所有当前内容指令。仅允许集成所需的脚本、框架、连接、图片、样式或字体指令。
  • 接受回调形参的方法:函数无法通过DDP传输。
  • Meteor.startup
    内部调用
    Accounts.config({ oauthSecretKey })
    :必须在模块顶层调用,确保在OAuth包读取之前加载。

See also

另请参阅

  • references/method-and-publish-guards.md
  • references/browser-policy-csp.md
  • references/eval-cases.md
  • Related skills:
    meteor-methods
    ,
    meteor-pubsub
    ,
    meteor-accounts
    .
  • references/method-and-publish-guards.md
  • references/browser-policy-csp.md
  • references/eval-cases.md
  • 相关技能:
    meteor-methods
    ,
    meteor-pubsub
    ,
    meteor-accounts
    .