| Version | Supported |
|---|---|
0.4.x |
Current |
< 0.4.0 |
No |
Please do not open a public issue for security vulnerabilities.
Report vulnerabilities privately via GitHub Security Advisories:
We aim to respond within 48 hours and to publish a patch within 30 days for confirmed vulnerabilities.
bluetemberg generates AI tool configuration and writes auto-executing hooks into user projects (.claude/settings.json, .cursor/rules/, .github/instructions/). The primary attack surface is:
- Pack supply chain — malicious content in third-party packs installed via
bluetemberg install - Guardrail hooks — commands written into
.claude/settings.jsonthat execute automatically when Claude Code calls a tool - Path traversal — pack names/versions used as filesystem paths during extraction
When bluetemberg install downloads a pack from the npm registry:
| Control | Where |
|---|---|
| SHA-512 integrity | Tarball hashed on download and compared against dist.integrity from registry metadata. Mismatches abort and clean up the partial extraction. |
| Registry host pinning | dist.tarball must resolve to the same hostname as the configured registry. A compromised metadata response cannot redirect downloads to an attacker-controlled host. |
| Size cap | Downloads are aborted when they exceed 50 MiB (streamed to temp file; partial temp file is removed by the installer on abort). |
| Path traversal protection | Pack names/versions that would resolve outside .bluetemberg/packs/ are rejected before any extraction. |
| Symlink rejection | The tarball extractor rejects symlinks unconditionally. |
| Integrity cache marker | .bluetemberg-integrity written per cached version; re-validated on cache hits. |
| Guardrail shell-injection guard | Guardrail check.field is validated to a safe character set (a dotted JSON-key path). It, the condition regexes, and the message are passed as positional arguments to a fixed script, never interpolated onto a command line. |
| Guardrail regex validation | check.matches / check.not_matches are POSIX ERE compiled by the host libc, not JavaScript regexes. Each is validated as portable ERE at sync time; a pattern that would not compile — or a JS/Perl idiom such as \d that compiles to something else — is a sync error and generates no hook. |
| Guardrail conditions fail closed | [[ =~ ]] returns status 2, not 1, when a pattern fails to compile. The generated hook captures the status explicitly and blocks on anything other than a clean evaluation, so a condition that cannot be evaluated denies the tool call instead of allowing it (GHSA-grpx-fj8v-q8g9). |
| Mandatory signatures on npmjs.org | The ECDSA signature check cannot be disabled for registry.npmjs.org. --skip-signature-verification applies only to non-default registries, never relaxes integrity checking, and every install that skips the check announces it with a warning line. |
| Host-scoped credentials | An .npmrc credential is bound to the host+path it is declared for and sent only there. A tarball on a different host (--allow-external-tarball-host), a different port, or plain http: is fetched unauthenticated, and GITHUB_TOKEN used for external sources is never sent to a registry. |
| Untrusted registry URL | llm/packages.json is a committed file, so a bare NPM_TOKEN/NODE_AUTH_TOKEN — which names no host — is applied only to registry.npmjs.org or to $NPM_CONFIG_REGISTRY. Cloning a repository and running install cannot redirect the token to a host that repository chose. |
| Credentials over https only | A plain-http: registry receives no credential (loopback excepted, where the request never leaves the machine). The rule is re-checked against the tarball URL itself, so registry metadata cannot downgrade the transport. Override for an internal http registry with BLUETEMBERG_ALLOW_INSECURE_REGISTRY_AUTH=1. |
| No credentials at rest | Tokens are read from .npmrc and the environment only. They are never written to the manifest, the lockfile, or log output, and inline user:pass@host userinfo is stripped from URLs before they appear in errors or lock entries. |
| No unsigned-signature forgery | A pack installed with signature verification skipped records version and integrity but no keyid, so a later bluetemberg verify still reports it as unsigned. |
Private pack distribution requires transmitting credentials, so the boundaries are explicit:
- Credentials resolve from
<project>/.npmrc, then~/.npmrc(or$NPM_CONFIG_USERCONFIG), thenNPM_TOKEN/NODE_AUTH_TOKEN..npmrcentries are host-and-path scoped, most specific first; the env fallback is not host-scoped and is therefore limited to a registry the user named (see Untrusted registry URL above). - A credential that is configured but withheld reports why, and how to configure one that would be sent — silently behaving as if none existed turns a security decision into a mystery 401.
- A
${VAR}reference that resolves to nothing yields no credential rather than a literal placeholder, so a misconfigured CI cannot send${NPM_TOKEN}as a bearer token. - An unparseable registry URL yields no credential: without an identifiable host there is no way to know who would receive it.
- Downloads may be redirected to a signed CDN URL. The platform
fetchdropsAuthorizationon a cross-origin redirect; a test asserts this against real HTTP servers so replacing the HTTP client cannot regress it silently.
Registry metadata is fetched over HTTPS but is not signed at the application layer (no Subresource Integrity on the metadata fetch itself). This is mitigated by using the official npm registry (registry.npmjs.org) by default, and by the host-pinning check which prevents redirects even if metadata is compromised. The publish workflow is configured to emit OIDC provenance attestations (npm publish --provenance); these attest the source commit and CI build for each published version and become verifiable via npm audit signatures once a release reaches the registry. Provenance does not sign install-time registry metadata — the application-layer integrity risk for metadata fetches remains, and verifying the npm signing key against dist.integrity at install time is tracked as future hardening.