meteor-security
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseMeteor 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
决策流程
- Audit every method: does it every argument and guard on
check()orthis.userIdwhen authentication matters?Meteor.userId() - Audit every publication: does it filter by (when user-specific) and project columns with
this.userId?fields - Add in dev to catch missing
audit-argument-checks.check() - Add and configure CSP.
browser-policy - Add rules for sensitive methods (login, password reset, resource creation).
DDPRateLimiter - If the app uses OAuth, set to encrypt provider secrets at rest.
oauthSecretKey - Remove /
allowrules. They are legacy and easy to misuse; use methods instead.deny
- 审计每个方法:当涉及身份验证时,是否对每个参数执行,并通过
check()或this.userId进行防护?Meteor.userId() - 审计每个发布:如果是用户特定数据,是否通过过滤,并使用
this.userId指定返回字段?fields - 在开发环境中添加以捕获缺失的
audit-argument-checks。check() - 添加并配置CSP。
browser-policy - 为敏感方法(登录、密码重置、资源创建)添加规则。
DDPRateLimiter - 如果应用使用OAuth,设置以加密存储的提供商密钥。
oauthSecretKey - 移除/
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: on every argument, userId
gate when needed, for failures,
Mongo on the server.
checkMeteor.Error(code, reason)*Asyncjavascript
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 } });
},
});拒绝任何不符合以下要求的方法:对每个参数执行、必要时添加userId防护、使用处理失败、在服务器端使用风格的Mongo操作。
check()Meteor.Error(code, reason)*AsyncPublication 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通过browser-policy
配置CSP
browser-policybash
meteor add browser-policyjavascript
// 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();
});BrowserPolicyreferences/browser-policy-csp.mdbash
meteor add browser-policyjavascript
// 服务器顶层或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();
});BrowserPolicyreferences/browser-policy-csp.mdDDPRateLimiter 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
, , or , 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.
clientAddressconnectionIduserIdThe default rule (5 in 10s for login / signup / password reset) ships
with . Remove with
only if you replace it.
accounts-baseAccounts.removeDefaultRateLimit()javascript
import { DDPRateLimiter } from "meteor/ddp-rate-limiter";
DDPRateLimiter.addRule(
{
type: "method",
name: "login",
clientAddress: () => true,
},
5,
60000, // 每个IP每分钟最多5次尝试
);只有匹配器字段会计入速率限制桶的键。如果没有、或,所有匹配的调用者将共享一个全局桶。Meteor 3.5+允许使用异步匹配器函数来基于数据库做决策;请确保查询速度快,因为连接会等待其执行结果。在Meteor 3.0至3.4版本中,匹配器必须保持同步。建议使用固定规则、预计算的同步状态,或者升级版本,而非在这些版本中等待Mongo操作。
clientAddressconnectionIduserIdaccounts-baseAccounts.removeDefaultRateLimit()OAuth secret encryption
OAuth密钥加密
Add and pass a 16-byte base64 key (NOT 32 bytes) to
at module top level (not inside ):
oauth-encryptionAccounts.configMeteor.startupbash
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, seals an unsealed provider application secret at
. Provider packages also seal
supported per-user token fields, such as or
Twitter's . There is no generic
field. Inspect the provider schema
before asserting which user credential is encrypted.
accounts-oauthServiceConfiguration.configurations.secretservices.github.accessTokenaccessTokenSecretMeteor.users.services.<provider>.secret添加包,并在模块顶层(而非内部)将一个16字节的base64密钥(注意不是32字节)传入:
oauth-encryptionMeteor.startupAccounts.configbash
meteor node -e "console.log(require('crypto').randomBytes(16).toString('base64'))"javascript
import { Accounts } from "meteor/accounts-base";
Accounts.config({
oauthSecretKey: Meteor.settings.oauthSecretKey,
});启动时,会加密中未加密的提供商应用密钥。提供商包还会加密支持的每个用户令牌字段,例如或Twitter的。不存在通用的字段。在断言哪些用户凭证已加密之前,请检查提供商的架构。
accounts-oauthServiceConfiguration.configurations.secretservices.github.accessTokenaccessTokenSecretMeteor.users.services.<provider>.secretaudit-argument-checks
audit-argument-checksaudit-argument-checks
audit-argument-checksbash
meteor add audit-argument-checksThrows if any method or publication runs without covering
every argument. Methods that legitimately accept arbitrary input declare
this explicitly:
check()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.allowrules. Legacy; easy to combine into a soft-fail. Replace with methods.Collection.deny - . The client sees
Meteor.settings.public.<secret>. Move secrets to the top level ofpublic.settings.json - Publish the entire collection. Always project (e.g.
Meteor.users) and filter. Publish email only to the owning user or another explicitly authorized audience.fields: { username: 1, profile: 1 } - Use 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.
BrowserPolicy.content.allowOriginForAll - Methods that accept callback-shaped arguments. Functions cannot travel over DDP.
- Call inside
Accounts.config({ oauthSecretKey }). Must be at module top level so it loads before the OAuth packages read it.Meteor.startup
- /
Collection.allow规则:属于遗留特性,容易组合导致失效。建议使用方法替代。Collection.deny - :客户端可以看到
Meteor.settings.public.<secret>下的内容。请将密钥移至public的顶层。settings.json - 发布整个集合:始终要进行字段投影(例如
Meteor.users)和过滤。仅向用户本人或明确授权的受众发布邮箱信息。fields: { username: 1, profile: 1 } - 为第三方脚本使用:这会将该源授权给所有当前内容指令。仅允许集成所需的脚本、框架、连接、图片、样式或字体指令。
BrowserPolicy.content.allowOriginForAll - 接受回调形参的方法:函数无法通过DDP传输。
- 在内部调用
Meteor.startup:必须在模块顶层调用,确保在OAuth包读取之前加载。Accounts.config({ oauthSecretKey })
See also
另请参阅
references/method-and-publish-guards.mdreferences/browser-policy-csp.mdreferences/eval-cases.md- Related skills: ,
meteor-methods,meteor-pubsub.meteor-accounts
references/method-and-publish-guards.mdreferences/browser-policy-csp.mdreferences/eval-cases.md- 相关技能:,
meteor-methods,meteor-pubsub.meteor-accounts