← 中文资源

简体中文版本

安全默认值

安全默认值让安全配置成为自动选择,即使使用者从不修改设置,也能获得加密、最小权限和关闭的访问。

阅读英文原文

不作为也应当安全

大多数配置从未被审查。产品的默认值因此比任何加固指南都更能决定安装基数的安全性。安全默认值反转了通常的负担:安全路径就是阻力最小的路径,削弱它需要明确且可见的选择。

工作机制

对权限、网络暴露和 API 访问采用默认拒绝。静态和传输中加密无需额外开启。调试控制台、目录列表和安装脚本等高风险功能默认关闭,直到运营者显式启用。生成强随机密钥而不是占位符,Cookie 使用 HttpOnly、Secure、SameSite,并设置保守的超时和请求体上限。

好的默认值还应可见。生成的配置要说明哪些已开启、为什么,让运营者可以审查而不是猜测。默认值在版本间变化时,应视为潜在的破坏性变更并记录迁移方式。

何时使用与取舍

每个新产品、模板、SDK 和内部平台都应从这里开始。基础设施即代码模块尤其重要,一个模板可能创建数千个暴露资源。对成熟系统,修改默认值是迁移项目,不是补丁。

安全默认值可能破坏现有工作流和集成,升级需要弃用路径。过严的默认值会让用户整体关闭控制,而不是逐步调整。有些默认值取决于场景:公开网站和内部管理工具的暴露要求不同,因此安全默认值应配合显式配置档,而非一个通用设置。

示例与常见误解

某云模板创建带公网端点和默认口令的数据库,快速入门让用户稍后修改,但大多数人不会。安全模板创建私有端点,把随机口令写入密钥管理服务,并要求显式开关才能暴露数据库。

常见误解是默认值是中立的。它编码了一种策略选择;当选择不安全时,厂商实际上把这种不安全交付给了每个客户。相关概念:最小权限、攻击面、纵深防御。

代码示例

export function createFlagSet(names) {
  if (!Array.isArray(names) || names.length === 0) {
    throw new TypeError("names must be a non-empty array");
  }
  if (names.length > 30) throw new RangeError("at most 30 flags are supported");
  const bits = new Map();
  const all = [];
  for (const name of names) {
    if (typeof name !== "string" || name.trim() === "") {
      throw new TypeError("flag names must be non-empty strings");
    }
    const normalized = name.trim();
    if (bits.has(normalized)) throw new Error(`duplicate flag "${normalized}"`);
    bits.set(normalized, 1 << bits.size);
    all.push(normalized);
  }
  const resolve = (name) => {
    const bit = bits.get(name);
    if (bit === undefined) throw new Error(`unknown flag "${name}"`);
    return bit;
  };
  return {
    names: () => [...all],
    mask(...selected) {
      return selected.reduce((mask, name) => mask | resolve(name), 0);
    },
    has(mask, name) {
      return (mask & resolve(name)) !== 0;
    },
    add(mask, name) {
      return mask | resolve(name);
    },
    remove(mask, name) {
      return mask & ~resolve(name);
    },
    toggle(mask, name) {
      return mask ^ resolve(name);
    },
    toNames(mask) {
      return all.filter((name) => (mask & resolve(name)) !== 0);
    },
  };
}

const permissions = createFlagSet(["read", "write", "admin"]);
let mask = permissions.mask("read", "write");
console.log(mask, permissions.toNames(mask));
mask = permissions.remove(mask, "write");
mask = permissions.toggle(mask, "admin");
console.log(permissions.has(mask, "write"), permissions.toNames(mask));
from pathlib import Path


class UnsafePathError(ValueError):
    pass


def safe_join(root: str | Path, user_path: str) -> Path:
    if not isinstance(user_path, str) or not user_path.strip():
        raise UnsafePathError("path must be a non-empty string")
    if "\x00" in user_path:
        raise UnsafePathError("path must not contain NUL bytes")

    candidate = Path(user_path)
    if candidate.is_absolute() or candidate.drive:
        raise UnsafePathError("absolute paths are not allowed")

    base = Path(root).resolve()
    target = (base / candidate).resolve()
    try:
        target.relative_to(base)
    except ValueError as error:
        raise UnsafePathError(f"path escapes root: {user_path!r}") from error
    return target


def main() -> None:
    root = Path.cwd() / "public"
    for value in ["reports/2026/q1.csv", "../secrets.env", "/etc/passwd", "a/../../b"]:
        try:
            print(value, "->", safe_join(root, value))
        except UnsafePathError as error:
            print(value, "-> rejected:", error)


if __name__ == "__main__":
    main()