This document explains the security model of Design Studio.
Primary goal: Protect a non-technical designer's MacBook from supply-chain attacks that could occur when AI tools install npm packages during React development.
Assumption: The designer is not technical and will not deliberately bypass security controls. The AI tools might hallucinate package names or attempt to install malicious code.
All development (Node.js, npm, AI tools) runs inside a Docker devcontainer. The host MacBook only runs VS Code and Docker Desktop. Even if the container is compromised, the host is protected.
The container runs as the node user, not root. This limits what a compromised process can do inside the container.
The .npmrc enforces:
ignore-scripts=true— prevents postinstall malwaresave-exact=true— no accidental semver range upgradessave-prefix=— disables default^prefix on savesregistry=https://registry.npmjs.org/— no third-party registriespackage-lock=true— ensures lockfile is always generated
Note:
.npmrcis auto-generated by the container'spost-create.shscript if missing. Do not manually modify it after setup.
Two lists control which packages can be installed:
- Base list (
config/allowed-packages-base.json) — always enforced - Extended list (
config/allowed-packages-extended.json) — toggleable viaconfig/security-config.json
If a package is not in either allowlist, the safe-install script queries the npm registry:
- Package must be ≥ 1 year old
- Package must have ≥ 20,000 weekly downloads
If both conditions pass, installation proceeds with hardened flags. If not, installation is blocked.
As an additional hardening layer, every npm install command is executed with a --before=30 days ago flag. This prevents installation of versions published within the last 30 days, even if the package itself is allowlisted or auto-validated. It provides a cooling-off window to catch newly compromised releases.
When a package fails validation, the designer sees:
❌ SECURITY ERROR: Package installation blocked.
With a suggestion to ask their AI for an alternative library.
OpenCode is configured via .opencode/opencode.json to deny npm install, npm add, npm i, and npx commands at the AI tool level. This prevents the AI from even attempting bypasses.
The designer follows the standard OpenCode flow to configure their API key (e.g., via opencode login or environment variables inside the container). API keys are not mounted or managed by the project setup.
If compromise is suspected:
- Revoke the designer's API key from the provider's admin portal (e.g., OpenCode dashboard) to prevent further unauthorized usage
- Destroy & Rebuild the devcontainer (
Dev Containers: Rebuild Container) - Generate a new API key to be used in OpenCode.
The designer's source code is safe because it lives on the host MacBook via bind mount.
- Verify the package is legitimate (check npm registry, GitHub repo)
- Add it to
config/allowed-packages-base.jsonwith an exact version - Commit and push
Edit config/security-config.json:
{
"extended_list_enabled": true
}If a previously allowed package becomes compromised, remove it from the allowlist JSON file. This does not technically block the package, instead, if a new attempt is made to install that package it will go through the standard auto-validation checks (age ≥ 1 year, downloads ≥ 20,000).
For a previously reputable package, these checks will likely pass. However, the 30-day publication buffer ensures that only versions published more than 30 days ago can be installed. This provides protection against recently compromised releases, unless the attackers have uploaded versions with manipulated publication dates.
The safe-npx.js wrapper applies the same validation logic to npx. Only allowlisted or auto-validated packages can be executed via npx.
.npmrcscripts/safe-install.jsscripts/safe-npx.jsconfig/allowed-packages-*.json(unless you're updating the allowlist)config/security-config.json.opencode/opencode.jsonAGENTS.md
The wrapper scripts log validation results to the console. Check the VS Code terminal output to see which packages passed or failed.