
Safeinstall
io.github.Mickdownunder由 GitHub 账号 Mickdownunder 发布v0.12.0更新于 Oct 24, 2026
Local-first supply-chain security gate for npm/pnpm/bun: typosquat, release age, provenance checks
概览
本地优先的供应链安全闸门,让 AI 助手在安装前按策略检查并执行 npm/pnpm 安装。
- 可以做什么
- 按策略执行 npm 与 pnpm 安装,拦截过新发布、含安装脚本、不可信来源与疑似仿冒包
- 审计项目直接依赖是否符合配置策略,可选输出机器可读的 JSON
- 校验产物的 SHA-512 与 tarball 绑定,并检查 Sigstore 来源证明与受信发布者
- 为代理信任面(配置、钩子、规则、MCP 文件)建立基线并核对漂移
- 生成初始配置、面向已检测编码代理的 shell 守卫钩子与信任锁
- 适用场景
- 当助手会安装或推荐软件包,而你希望在包管理器运行前用本地闸门拦截新发布、安装脚本、仿冒包和异常发布者时适用。也适合希望把编码代理的配置文件纳入提交基线并在 CI 中复核的团队。
- 运行要求
- 以本地进程运行;需全局安装该 npm 包,使 safeinstall 可执行文件位于代理的 PATH 中。需要 Node.js 20 或更高版本,支持 macOS、Linux、Windows。无需账号、云服务或 API 密钥;可选的 safeinstall.config.json 会从项目目录向上查找或显式指定。完整来源证明校验需要可选的 sigstore 校验器。
安装
在 SourceWeft 中
- 打开 控制台中的 Safeinstall,将其添加到工作区。
- 为需要使用其工具的对话启用该服务。
Desktop only,通过 STDIO。 STDIO 服务会启动本地进程,因此需要 SourceWeft 桌面宿主。
其他 MCP 客户端
参照 仓库 中的启动说明。
README
SafeInstall
Supply-chain security for the AI-agent era.
[npm version] [license] [stars] [TypeScript] [docs] [trust surface]
The artifact-bound install gate for npm and pnpm — for the humans who type install, the AI agents that run it for them, and the config that programs those agents.
Policy runs first. Then your package manager. Not the other way around.
Local-first · No cloud · No account · MIT · Free forever
What it does
SafeInstall runs your policy before your package manager — locally, blocking by default. One tool, three layers of defense:
- 🧑💻 For the humans who install — prefix supported install commands:
safeinstall pnpm add axios. Policy runs first; the resolved lockfile is then checked against the approved version and SHA-512 before the frozen install. Release age, install scripts, untrusted sources, typo-squats, and cryptographic provenance are policy checks. - 🤖 For the AI agents that install for you — an MCP tool agents can call, plus a shell guard that intercepts install commands in Claude Code, Codex, and Cursor before they run. Best-effort shell interception — one defense layer that fires even when the agent isn't cooperating, not a lossless guarantee.
- 🔒 For the files that program the agents — the Agent Trust Surface: a committed hash baseline of your config, hooks, rules, and MCP files, reconciled locally and re-verified in CI, so tampering with the rules surfaces as drift instead of silently owning every future session — with a fully consistent rewrite caught by human review of the diff, not the automated check alone.
Why SafeInstall
AI coding tools suggest packages in seconds. They don't check publish dates. They don't read install scripts. They don't verify the source. You type "yes" and move on.
SafeInstall is the gate between suggestion and execution.
No dashboard. No account. No cloud. One command prefix — policy runs locally, blocks by default, then invokes the real tool.
Catching a maintainer-compromise attack
A valid Sigstore signature is not enough. An attacker who compromises an npm maintainer account can publish a malicious version of a package you already trust, and the attestation on that malicious version will cryptographically verify — signed by a GitHub Actions workflow the attacker controls in a fork of the real repository.
SafeInstall catches this. Pin the expected source repository with provenance.trustedPublishers and any build that comes from anywhere else is blocked, even if the signature is valid:
This is the only check of its kind in an install-time policy gate. CVE scanners look for known vulnerabilities. Content analyzers look for suspicious code. SafeInstall enforces that the cryptographic chain of trust points at the repository you agreed to trust — and refuses anything else, no matter how legitimate it looks.
SafeInstall itself is published with a Sigstore attestation. You can eat your own dog food: enable provenance verification, pin safeinstall-cli to Mickdownunder/SafeInstall, and watch SafeInstall verify its own trust chain against the public Sigstore transparency log.
Install
Node.js >=20 · macOS, Linux, Windows · Command:
safeinstall
Quickstart
How it works
- Resolves the requested version and captures the registry's tarball URL and SHA-512
- Fetches registry metadata (publish time, declared scripts)
- Evaluates policy rules
- For explicit package additions, pins the evaluated version and runs a scripts-disabled, lockfile-only preparation
- Checks every direct registry dependency in the affected lockfile scope against its evaluated version, SHA-512, and tarball URL where recorded; applies configured transitive checks
- Runs
npm ciorpnpm install --frozen-lockfilewith scripts disabled and the configured registry enforced; rejects manifest/lockfile changes during evaluation or installation
No registry proxy. No tarball rewriting. No cloud dependency.
Policy defaults
All rules are configurable. Ambiguous or incomplete metadata blocks instead of allowing.
Provenance continuity — catching what npm defaults can't
This is SafeInstall's most distinctive check, and the one no other consumer-side tool does. npm verifies provenance at publish time and binds a package to a source repository — but it does not enforce continuity between versions. A compromised maintainer account can publish a new version with no attestation (from a stolen token), or from a different repository, and npm raises no alarm. This is the signature of the 2026 attack wave (Mastra, the dormant-account republishes).
Continuity learns a per-package trust baseline from the provenance identity of recent versions, then blocks deviations:
provenance-downgrade— recent versions were attested, this one isn't. The fingerprint of an account-compromise publish from a personal token. (This is the Mastra case.)identity-discontinuity— this version is attested from a different source repository than the established baseline.
Because the baseline is learned per package, there are no false positives on the large majority of packages that never adopted provenance — they simply have no baseline and the check stays silent. No global "require provenance" sledgehammer.
It reads npm's published attestation metadata, so it works without the optional sigstore package. Opt in with continuity.mode set to "warn" or "block".
Honest limit: continuity does not catch an attack that comes through a legitimately-compromised CI workflow with valid provenance from the real repository (e.g. the Shai-Hulud worm class). There is no identity discontinuity to detect there. SafeInstall raises the bar against the dominant 2026 attack pattern; it does not close every door.
Transitive dependencies
By default SafeInstall evaluates direct dependencies. Most supply-chain attacks, though, reach you through a transitive dependency — a package you never chose, pulled in several levels deep. Enable transitive mode to walk the full lockfile tree.
Two checks run transitively, both read directly from the lockfile with zero extra registry calls:
install-script— flags transitive packages that declare a lifecycle script (theua-parser-jsattack class: a deeply nested dependency running code at install time). npm records this in the lockfile; pnpm lockfiles do not, so this check is npm-only for now.untrusted-source— flags transitive packages resolving from git, url, or tarball sources instead of the registry. Works for both npm and pnpm.
Release-age, typo-squat, and provenance are deliberately not run transitively — they would either flood you with noise or require a registry round-trip per package. Transitive evaluation applies to safeinstall check, project installs, and the prepared lockfile for explicit npm/pnpm additions.
Typo-squat detection
Flags install requests whose package name is a close-but-not-exact match to a well-known popular package — the most common supply-chain attack pattern: lodsh for lodash, axois for axios, raect for react, and so on.
- Algorithm: Damerau-Levenshtein distance (transpositions count as a single edit)
- Target list: curated set of popular ecosystem packages, embedded at build time (no runtime network fetch)
- Default mode:
"off"— opt in withtypoSquat: { "mode": "warn" }or"block" - False-positive mitigation: exact matches to the list are never flagged, short names (< 4 chars) are skipped, per-project
ignorelist is supported
Provenance verification
Cryptographically verifies the npm provenance attestation for registry installs and optionally pins the source repository via per-package trusted publisher patterns.
- Fetches the attestation bundle from the npm registry's
/-/npm/v1/attestations/<pkg>@<version>endpoint - Verifies the Sigstore bundle via the official
sigstorepackage (signatures, Rekor transparency log, Sigstore public trust root) - Extracts source repository, commit ref, and workflow path from the SLSA v1 provenance statement
- Matches the source repository slug against per-package
trustedPublisherspatterns — catching not only tampered tarballs but also maintainer-compromise attacks where an attacker republishes a legitimate-looking package from a fork they control - Publisher mismatches always block, even in
"warn"mode, because a valid signature from the wrong repo is exactly what maintainer compromise looks like
Default mode is "off". Opt in by setting provenance.mode to "warn" or "require".
Usage
Project installs
For pnpm install and npm install / npm ci, dependency versions come from the lockfile — not loose ranges in package.json.
pnpm-lock.yamlfor pnpmpackage-lock.jsonornpm-shrinkwrap.jsonfor npm- Stale, missing, or mismatched lockfile entries fail closed
- If
packageManageris set inpackage.json, using a different CLI is blocked - Workspace-targeting flags (
--filter,--workspace) are blocked — use-Cor--prefix bun add/bun installfail closed until artifact-bound Bun installation is implemented;checkand MCP policy queries remain available
Artifact-bound installation contract
Installation requires one canonical SHA-512 digest in the checked registry manifest and the matching lockfile entry. SHA-1-only or missing-integrity entries block. A different command-line registry, script-enabling flags, global installs, and unknown installation options block. Registry selection in environment variables or scoped .npmrc settings cannot authorize a different artifact: the resulting lockfile must still match the checked binding.
Explicit additions resolve into a lockfile with the evaluated exact version before installation. Preparation can update package.json and the lockfile, and can populate the manager's cache, even if the later binding/policy check blocks. Those files are left for inspection, not automatically rolled back. npm's frozen phase uses ci, which replaces node_modules. Shared workspace lockfiles cause all recorded importers' direct dependencies to be reviewed under the selected policy, not just the selected package.
Lifecycle scripts are always disabled, including when legacy packageManagerDefaults.*.ignoreScripts is false. allowedScripts permits a policy exception, not script execution. pnpm's executable .pnpmfile.cjs hooks are disabled too. Explicit non-registry additions currently block; existing local/workspace dependencies remain governed by allowedSources and are not claimed to have registry-artifact binding.
MCP server / AI agents
The CLI reaches humans who type safeinstall. The MCP server reaches AI coding agents — Claude Code, Codex, Cursor, Windsurf, Cline — so the policy engine can be consulted before an agent suggests or runs an install, without anyone typing anything.
safeinstall mcp starts a Model Context Protocol server over stdio that exposes one tool, check_package, backed by the exact same engine as the CLI (release age, install scripts, untrusted sources, typo-squat, Sigstore provenance, and provenance continuity).
[!IMPORTANT] MCP tools are advisory. Installing the server makes
check_packageavailable; it does not force an agent to call it. "Install once, protected forever" = one MCP config block + one rule snippet (mcp/agent-rule.md) telling the agent to call the tool before every install.
Wire it into your agent
Claude Code / Claude Desktop — add to your MCP config (claude_desktop_config.json or .mcp.json):
Cursor — add the same block to .cursor/mcp.json.
Then paste the rule from mcp/agent-rule.md into your CLAUDE.md / Cursor Rules. Full setup details: mcp/README.md.
What the agent sees
check_package accepts name (required), version (optional, defaults to latest), and manager (optional, informational) and returns a JSON verdict:
verdict is "block" when any policy reason blocks the install, otherwise "allow".
When no safeinstall.config.json is found, the MCP server uses a recommended secure preset — the built-in defaults with typoSquat and continuity promoted to "block", because the agent use case wants maximum signal. When a config file is found, it is respected exactly (same resolution as the CLI).
The MCP SDK ships as an optional dependency, lazily loaded only when safeinstall mcp runs — CLI-only users never install it. If it is missing, the command prints an install hint and exits non-zero.
Agent guard — enforcement, not advice
The MCP tool is advisory: an agent can consult it. The guard is a stronger layer: it hooks into the agent's shell layer and screens commands before they run, firing even when the agent doesn't know SafeInstall exists. This is best-effort shell interception — one layer of defense in depth, not a complete boundary; its command parsing is not exhaustive (see Guard limitations).
This writes project-level hook configuration (merged non-destructively, idempotent on re-runs):
- Claude Code — a
PreToolUsehook on theBashtool in.claude/settings.json; raw installs are rewritten in-place through the SafeInstall CLI viaupdatedInputwith nopermissionDecision, so Claude's normal permission prompt still fires and shows the user the rewritten command to approve - Codex — a
PreToolUsehook onBashin.codex/hooks.json; raw installs are rewritten in-place through the SafeInstall CLI via CodexupdatedInput. Open/hooksin Codex after installation and trust the new project hook before expecting it to run. - Cursor — a
beforeShellExecutionhook in.cursor/hooks.json, registered withfailClosed: trueso a crashed or timed-out guard blocks instead of silently allowing
After registration, the CLI reports the Trust Surface step that matches the project state. A project without a baseline is directed to safeinstall trust lock. If a baseline already exists and the guard changed it, review the complete drift with safeinstall trust status and run safeinstall trust approve only when every change is intentional. Re-running an already-registered guard does not require re-approval unless status reports drift.
How the guard decides
The guard never evaluates policy itself — it detects package installs and routes them through the CLI, which owns the decision:
Routing through the CLI matters more than a simple allow/deny: a vetted-but-raw npm install would still execute lifecycle scripts. Through SafeInstall, the same install gets the full policy evaluation and runs with install scripts disabled.
Claude and Codex apply the rewrite in-place through updatedInput, so the user's normal permission prompt shows the SafeInstall-routed command to approve rather than the raw install. Cursor receives the exact rewritten command in the block message so a well-behaved agent self-corrects in one step. The guard needs no network access and answers in milliseconds.
Guard limitations
- The
safeinstallbinary must be on the agent'sPATH(npm install -g safeinstall-cli). - Codex requires users to review and trust new or changed project hooks with
/hooks; until then Codex skips the hook. Codex currently intercepts only supported simple shell calls, not everyunified_execpath, so the hook is still a defense-in-depth guardrail rather than a complete boundary. npxand friends are not policy-checked: SafeInstall's engine evaluates installs, not one-off executions. Claude/Cursor request approval; Codex fails closed because itsPreToolUsehook does not currently supportask. The local-binary fast path mirrors the runners' own resolution (nearestnode_modules/.bin), so approved project tooling does not trigger the remote-runner gate.- A determined adversary can bypass shell-level analysis — installs can run out of band (a script file written first,
node -e, and similar), and command-line parsing itself is not exhaustive. Do not rely on the shell layer alone: it raises the bar as one layer of defense in depth — combine it with the MCP tool and CI checks.
Agent Trust Surface — self-defending policy
The guard stops raw installs. But a prompt-injected agent does not have to fight the guard — it can just rewrite the rules: weaken safeinstall.config.json, delete an agent hook, disable Codex hooks in .codex/config.toml, plant an invisible-Unicode instruction in AGENTS.md, or register a malicious MCP server. Those files program the next agent session; in the agent era they are the persistence mechanism, the new crontab.
safeinstall trust lock records a hash baseline of that surface. SafeInstall then reconciles the real state against it before guard decisions and before every install/check, and can re-verify it in CI.
--ci github writes .github/workflows/safeinstall-trust.yml, which re-verifies the committed baseline on every pull request by running safeinstall trust status --require-lock with the CLI pinned to an exact version and its sha512 content hash, recorded from the registry at scaffold time (trust on first use) — a registry that later serves different bytes for that version fails the check instead of silently swapping the verifier. The workflow uses pull_request_target, so GitHub loads its definition from the protected base branch; it checks out the proposed revision without credentials, treats it only as data, and never executes PR code. Commit .safeinstall/ and that workflow together. An existing workflow file is never overwritten. A PR therefore cannot neutralize the verifier that judges it — but a fully consistent rewrite of policy plus baseline still needs human review of the diff (see the CODEOWNERS note below).
Two things you must do for the check to actually enforce, because a CLI cannot set them for you:
- Make the workflow a required status check in your branch protection — otherwise a red check does not block a merge.
- Require review of changes to
.safeinstall/and.github/workflows/(e.g. viaCODEOWNERS). The automatic check catches inconsistent tampering; a fully consistent rewrite of the whole baseline in one PR is only caught by a human reviewing the.safeinstall/diff.
The three zones
Instruction content drift only warns by default because Claude Code writes to CLAUDE.md itself (its memory feature) — blocking that would break a normal workflow. Hidden Unicode, on the other hand, has no legitimate purpose in these files and always blocks.
How it defends itself
- The verifier comes from the protected base branch; human review still owns intentional re-baselines. Run
safeinstall trust lock --ci githubto scaffold a workflow that re-hashes the candidate trust surface against its committed lock on every pull request, on a machine the agent does not control, with the CLI pinned to an exact version and its sha512 content hash.pull_request_targetkeeps the verifier definition outside the PR's control, while the candidate checkout is inert data with no credentials. This blocks a PR from editing the workflow to waive its own failure. Honest boundary: a PR can still rewrite policy and baseline consistently, so require independent review of those diffs. (Wiring the Action directly also works — but pin itsversion:input; the defaultlatestfloats.) - Reconciliation on top of interception. The guard is the fast layer; every
safeinstallinstall/check also reconciles against the baseline. Deleting the hook to silence the guard, or editing a protected file, shows up as drift on the next SafeInstall run locally — and, regardless, fails CI re-verification. - Human-gated changes. Both commands that relax enforcement require a human:
safeinstall trust approvereads its confirmation from the controlling terminal (/dev/tty), never from stdin, andtrust unlocklikewise requires a controlling terminal — both refuse to run in CI or known agent-hook contexts, so an agent cannot silently re-baseline or remove the surface.trust statusis read-only (safe in CI, never mutates the repo). - Hash-chained ledger. Baseline decisions are chained in
.safeinstall/ledger.jsonl; the lock binds to a ledger entry (including its enforcementmode), so editing the committed lock to drop a file or downgrade strict→warn breaks the binding. A local head mirror under~/.safeinstall/catches naive/accidental history rewrites; a vanished mirror is self-healed from the verified head on the next clean run, so a fresh clone establishes it automatically without noise.
Trust surface limitations — read honestly
- The strongest layer is the committed lock reviewed on a PR, not the local mirror. Locally this is tamper-evident against mistakes and non-targeted tampering, not tamper-proof. A scheme-aware agent in your own user account can bypass interception (e.g. an install driven from
node -e), rewrite the in-repo lock and ledger into a consistent state, and delete the local head mirror — user space cannot stop that, because the agent can read and delete anything you can. CI re-verification runs on a separate machine from a base-branch-owned workflow, so the PR cannot disable its own verifier. A consistent policy-and-baseline rewrite can still pass automatically. The residual guarantee against a capable adversary is independent human review of the trust-surface diff (workflow, lock, and config) on every dependency PR — enforced by a ruleset once a second trusted reviewer is available. Run the CI check and require that review; they cover different failure modes. - It watches shell commands and files, not intent: an install performed by a program the agent writes and runs is caught by reconciliation on the next SafeInstall/CI run, not prevented at the moment of the call.
- A missing local head mirror is ambiguous (a fresh clone has none too), so on a clean verify it is self-healed from the committed ledger head rather than warned or blocked — the mirror is a best-effort local convenience, never the guarantee.
Configuration
Optional safeinstall.config.json — discovered by walking upward from the project directory, or passed explicitly with --config <path>. An explicit path that cannot be read is an error (exit 1), never a silent fallback to defaults.
Pinning a minimum CLI version
Guard verdicts change across releases (fixed false positives, new checks), but the CLI is typically installed globally — so a project can assume 0.12.0 behavior while an agent's machine still runs 0.11.0. Set minimumCliVersion to the lowest version whose behavior your policy assumes, and every older CLI announces the mismatch loudly:
The check is fully offline (a semver comparison against the running version) and deliberately a warning, never a hard failure: a hard failure would break every agent session after each release until the global CLI is updated. Two sharp edges to know:
- The field must be an exact version (
"0.12.0"), not a range — it is a floor, not a constraint. Anything else fails config parsing closed. - CLIs released before this field existed reject unknown config keys, so they refuse the whole config (fail-closed) instead of warning. Setting the field effectively requires collaborators to run a CLI that knows it.
Run safeinstall init to generate a starter config.
Exit codes
Use exit code 2 like any other failing step in a CI pipeline.
JSON output
Pass --json anywhere in the command. Structured output goes to stdout.
Fields: command, commandString, packageManager, decision, summary, reasons, warnings, affectedPackages, exitCode, exitCodeMeaning. Allowed installs include execution.stdout and execution.stderr.
Examples
Want to see every check in action? Run
bash demo/run.sh— a reproducible script that blocks one scenario per policy check against real packages. See demo/README.md for the walkthrough.Want to see it against a real attack? Run
pnpm replay mastra— it feeds the recorded attack-time state of the June 2026 Mastra compromise into SafeInstall's real policy engine and shows which checks would have blocked it. See demo/replay/README.md.
Fresh release blocked
Git source blocked
Package manager mismatch
Stale lockfile
Typo-squat caught
Publisher mismatch (maintainer compromise defense)
GitHub Action
Run SafeInstall policy checks automatically on every pull request. Five lines of YAML, no configuration required — defaults apply immediately.
The action runs safeinstall check against your direct dependencies and sets the job status to pass or fail. Blocked dependencies appear in the GitHub Actions job summary with the exact block reason and suggestion.
Install mode
To enforce policy during the actual install step (not just a check):
Inputs
Outputs
Limitations
- Not a CVE scanner — pair with
npm auditor Snyk for vulnerability data - Transitive dependencies are evaluated for install scripts and untrusted sources when
transitive.modeis enabled. Release-age, typo-squat, and provenance still apply to direct dependencies only. - Transitive install-script detection is npm-only — pnpm lockfiles do not record install-script presence. Transitive untrusted-source detection works for both.
- Native builds (
binding.gyp) are caught via the install-script check: npm normalizesbinding.gypinto anode-gyp rebuildinstall script at publish time, so it is present in the registry metadata SafeInstall already reads — no tarball download required. The residual edge is a package published through a non-standard client that omits this normalization while still shipping abinding.gyp; detecting that would require tarball content inspection, which is out of scope (see What it does not do). peerDependenciesnot evaluated unless also declared as direct dependencies- Trust downgrade detection requires prior install state in
node_modules - Bun installation fails closed; an artifact-binding adapter is not implemented
- Trusted execution boundary — this is not an OS sandbox. It relies on a trusted npm/pnpm binary enforcing frozen-lockfile integrity, the selected registry metadata, and the local filesystem not being controlled by a concurrent attacker. It does not prove package code benign, inspect every transitive artifact against registry policy, or isolate runtime execution
- Typo-squat target list is curated and refreshed manually between releases; brand new packages published in the last day may not yet appear
- Provenance verification supports GitHub Actions trusted publishers on the public Sigstore root only (GitLab CI, self-hosted Sigstore currently out of scope)
- Git sources are identified by URL for allowlist purposes, not by inferred package name — conflating registry
axioswithgithub:any-fork/axioswould be dangerous - Ambiguous metadata blocks instead of guessing — by design
What it does not do
- Vulnerability scanning or CVE databases
- Registry proxying or tarball rewriting
- Malware detection or package content analysis
- Selective lifecycle script execution (always forces
--ignore-scripts)
Works with
SafeInstall works with any tool that runs package manager commands — including AI coding assistants:
Cursor · Codex · GitHub Copilot · Cline · Claude Code · Windsurf · Aider · Devin · Continue
Just prefix your install commands with safeinstall. Same workflow, one safety layer.
Contributing
Issues and PRs welcome. Author merges at own discretion — this is a solo-maintained project.
License
MIT — see LICENSE.
Disclaimer
SafeInstall is provided as-is under the MIT license. It is a policy tool that enforces configurable rules on package installs. It does not guarantee the safety of any package, does not detect all supply-chain attacks, and does not replace professional security review. Use at your own risk. The authors are not liable for any damages arising from the use of this software.
Last verified: 2026-10-01
safeinstall.dev · npm · GitHub
来源:README.md,提交 15b324b
工具
0版本历史
1- v0.12.0最新Sep 16, 2026
评分
已满足适用检查项的 50%
该评分衡量此收录条目的完整程度,依据注册中心条目、端点与公开来源计算得出。它不是对服务器本身的评价,也不表示服务器安全或当前可用:可连通性是带日期的检查结果,评分也不包含安装量或用户评价。
- 近 30 天内可连通25 分
请保持端点能够响应 MCP 握手;SourceWeft 每 30 天探测一次。
- 已声明认证方式10 分
请在注册中心条目中声明凭据请求头,或保持端点能够响应探测,以便判定其认证方式。
- 已列出工具15 分
请让服务器在无凭据时响应 tools/list,或发布可在沙箱中无凭据启动的软件包。
- 描述必需15 分
- 图标10 分
- 文档15 分
- 许可证10 分