diff --git a/README.md b/README.md index 014d42a..7d1a9c8 100644 --- a/README.md +++ b/README.md @@ -14,11 +14,13 @@ attach-guard is a Claude Code plugin that intercepts package installation comman - Installs as a Claude Code plugin — no manual hook configuration needed - Intercepts `npm install`, `pnpm add`, `pip install`, `go get`, and `cargo add` commands via PreToolUse hooks -- Checks package scores, age, and alerts via Socket.dev -- Denies known malware and low-score packages automatically -- Asks for confirmation on gray-band packages +- Evaluates packages with the configured provider before install commands run +- Uses the current Socket.dev adapter only as an explicit bring-your-own-token local provider, not a hosted/default scoring source +- Denies known malware and high-confidence dangerous packages automatically +- Asks for confirmation on gray-band packages and provider-unavailable cases in local mode - Rewrites unpinned installs to safe pinned versions when possible -- Fails closed when the provider is unavailable + +Attach Open Score is the first-party provider direction and is not wired in yet; see [Attach Open Score provider semantics](docs/OPEN_SCORE_PROVIDER.md) for the planned integration contract. - Logs every decision to a local JSONL audit trail ## Smart Version Replacement: Block Without Breaking Flow @@ -27,20 +29,20 @@ Most security tools just say "no." attach-guard says "no, but here's a safe alte When a risky version is blocked, attach-guard finds the newest version that passes policy and offers it as a replacement. Claude sees the safe alternative and can proceed immediately — your flow doesn't stop, it gets redirected to a safe path. -**npm** — axios v1.14.1 and v0.30.4 were [compromised versions](https://socket.dev/blog/axios-npm-account-compromise) published via a hijacked maintainer account: +**npm** — axios v1.14.1 and v0.30.4 were publicly reported compromised versions published via a hijacked maintainer account: ``` > npm install axios attach-guard evaluates: - axios@1.14.1 --> DENY (supply chain score 40, below threshold 50 — compromised version) - axios@1.14.0 --> ALLOW (supply chain score 71, passes all policy checks) + axios@1.14.1 --> DENY (known compromised version) + axios@1.14.0 --> ALLOW (passes configured policy checks) Result: ASK + rewritten command "npm install axios@1.14.0" ``` -**pip** — litellm v1.82.7 and v1.82.8 were [malicious versions](https://socket.dev/npm/package/litellm) published to PyPI: +**pip** — litellm v1.82.7 and v1.82.8 were malicious versions published to PyPI: ``` > pip install litellm @@ -53,7 +55,7 @@ Result: ASK + rewritten command "pip install litellm==1.82.6" ``` -These are real examples — attach-guard blocks compromised versions automatically based on their supply chain scores. +These examples illustrate the current enforcement flow: attach-guard blocks known compromised or policy-failing versions and offers a safe pinned alternative when one is available. | Scenario | Example | Decision | What happens | |---|---|---|---| @@ -87,7 +89,7 @@ Security enforcement requires interception at the tool-call boundary, before exe ### Quick Start: Claude Code Plugin -The fastest way to try attach-guard. Requires a [Socket.dev](https://socket.dev) API token (free tier available). +The fastest way to try the current packaged plugin. Today's released plugin uses the local bring-your-own-token Socket.dev provider for real scoring. Attach Open Score is the first-party direction and is not wired in yet; Socket must not be treated as hosted/default Attach scoring. ```bash # Add the marketplace and install (one-time) @@ -101,7 +103,7 @@ Or from within a Claude Code session: /plugin install attach-guard@attach-dev ``` -During installation or enablement, Claude Code will prompt for your Socket API token (stored securely in your system keychain). Get a free token at [socket.dev](https://socket.dev). +During installation or enablement, the current Socket-backed plugin path prompts for your Socket API token (stored securely in your system keychain). Get a free token at [socket.dev](https://socket.dev). This token is for local BYO-provider use only. > **If the install/enable prompt didn't appear**, re-trigger it with: > ```bash @@ -136,7 +138,7 @@ For use without the plugin system, or to install the binary globally. #### Prerequisites - [Go 1.21+](https://go.dev/dl/) (to build from source; not needed for the plugin install above) -- A [Socket.dev](https://socket.dev) API token (free tier available) +- A [Socket.dev](https://socket.dev) API token only when using the current Socket BYO-token provider path #### Step 1: Build and install the binary @@ -164,7 +166,7 @@ attach-guard version # attach-guard v0.1.0 ``` -#### Step 2: Set up your Socket API token +#### Step 2: Set up a Socket API token for the current BYO-provider path ```bash export SOCKET_API_TOKEN="your-token-here" @@ -213,7 +215,7 @@ Try installing a known-compromised version to verify attach-guard blocks it: > Install axios@1.14.1 Claude: I'll install axios@1.14.1. -[attach-guard] deny: axios@1.14.1: supply chain score 40 is below minimum threshold 50 +[attach-guard] deny: axios@1.14.1: known compromised version ``` Then try a safe version: @@ -275,9 +277,11 @@ attach-guard hook Default config location: `~/.attach-guard/config.yaml` +Current provider configuration still defaults to the Socket adapter in code. Treat this as a legacy/local BYO-token default until the Attach Open Score provider lands; do not use Socket as hosted/default Attach scoring. + ```yaml provider: - kind: socket # risk intelligence provider + kind: socket # explicit BYO-token local provider today; Open Score is the first-party direction api_token_env: SOCKET_API_TOKEN policy: deny_known_malware: true @@ -334,12 +338,14 @@ Highest priority wins (later sources override earlier): ### Unpinned version handling When you run an unpinned supported command such as `npm install axios`, `pip install requests`, `go get golang.org/x/net`, or `cargo add serde`: -- attach-guard fetches candidate versions from the matching registry and scores them via Socket.dev +- attach-guard fetches candidate versions from the configured provider and evaluates them against policy - If the latest passes policy, the command runs as-is - If the latest fails but an older version passes, attach-guard suggests a rewrite using ecosystem-native syntax - In Claude Code mode: returns `ask` with the rewritten command via `updatedInput` - If no version passes, denies +Attach Open Score integration should be verdict-first: `ALLOW` → allow, `ASK` → ask, `DENY` → deny, and `UNKNOWN` → ask/warn locally by default. CI/team policy may map unknowns to deny by explicit configuration. See [Attach Open Score provider semantics](docs/OPEN_SCORE_PROVIDER.md). + ### Failure handling - Local/interactive mode: asks on provider failure @@ -356,18 +362,18 @@ Every decision is logged to `~/.attach-guard/audit.jsonl`: "user": "dev", "cwd": "/home/dev/project", "package_manager": "npm", - "original_command": "npm install axios@1.14.1", + "original_command": "npm install example-malware@1.0.0", "decision": "deny", - "reason": "axios@1.14.1: supply chain score 40 is below minimum threshold 50", - "packages": [{"ecosystem":"npm","name":"axios","selected_version":"1.14.1","score":{"supply_chain":40,"overall":40}}], - "provider": "socket", + "reason": "example-malware@1.0.0: known malware alert", + "packages": [{"ecosystem":"npm","name":"example-malware","selected_version":"1.0.0","score":{"supply_chain":0,"overall":0},"alerts":[{"severity":"critical","title":"known malware","category":"malware"}]}], + "provider": "mock", "mode": "claude" } ``` -## API Quota +## Socket BYO-token provider quota -attach-guard uses the [Socket.dev API](https://socket.dev) for package risk scoring. The free tier provides **500 quota units per hour**. +The current Socket adapter uses the [Socket.dev API](https://socket.dev) when the user explicitly configures the BYO-token Socket provider. This section is for that local provider path only; Socket data must not be used as a default Attach Open Score input/source absent an explicit partnership. | Ecosystem | Endpoint | Cost per call | |---|---|---| @@ -380,9 +386,9 @@ attach-guard uses the [Socket.dev API](https://socket.dev) for package risk scor - Pinned installs (e.g. `pip install litellm==1.82.8`) use one call to score a single version - Unpinned installs (e.g. `pip install litellm`) use one batch call to score up to 10 candidate versions -**When quota is exhausted**, scoring calls fail and attach-guard falls back to zero scores. This means: -- Pinned installs are **denied** (score 0 < threshold 50) — safe, fails closed -- Unpinned installs show "no acceptable version found" instead of offering a safe alternative — the version rewrite feature requires real scores to identify which version passes policy +**When quota is exhausted**, provider calls fail. In current behavior this means: +- Pinned installs are **denied** because the provider could not return an acceptable evaluation — safe, fails closed +- Unpinned installs show "no acceptable version found" instead of offering a safe alternative — the version rewrite feature requires real provider results to identify which version passes policy To check your remaining quota: ```bash @@ -395,7 +401,8 @@ Quota resets hourly. For higher limits, see [Socket.dev pricing](https://socket. - Direct `pip` / `pip3` (including `uv pip`), `go get` / `go install`, and `cargo add` / `cargo install` are supported; `python -m pip` remains passthrough for now - pip extras/range specs, Cargo requirement syntax, and non-semver Go queries are intentionally passed through for manual review rather than being auto-evaluated -- PyPI, Go, and Cargo scoring uses Socket's `POST /v0/purl` endpoint which has higher quota cost (100 units) compared to npm (10 units) +- Current released provider implementation is Socket BYO-token/local; Attach Open Score first-party provider integration is the next direction +- PyPI, Go, and Cargo scoring through the Socket BYO provider uses Socket's `POST /v0/purl` endpoint, which has higher quota cost (100 units) compared to npm (10 units) - No transitive dependency analysis - No lockfile graph support - Single provider at a time diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index b5bd94a..de8438f 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -36,7 +36,7 @@ raw command string │ ▼ ┌──────────┐ -│ Provider │ ── Socket adapter fetches scores, versions, alerts +│ Provider │ ── scores, versions, alerts today; future Open Score integration maps verdicts └────┬─────┘ │ ▼ @@ -71,7 +71,12 @@ type Provider interface { } ``` -The Socket adapter normalizes Socket API responses into internal `VersionInfo` and `PackageScore` types. No Socket-specific types leak into the policy engine. +Current implementation status: +- the shipped adapter is Socket-backed and should be treated as an explicit local BYO-token provider +- the first-party default direction is Attach Open Score; see [`docs/OPEN_SCORE_PROVIDER.md`](OPEN_SCORE_PROVIDER.md) for verdict mapping and source/legal constraints +- no proprietary provider types should leak into the policy engine + +The current Socket adapter normalizes Socket API responses into internal `VersionInfo` and `PackageScore` types. Future Open Score integration should avoid directly mapping Open Score's risk score into the existing safety-score fields because the polarity is opposite; use verdict-first mapping or an explicit transform with tests. To add a new provider: 1. Implement the `Provider` interface diff --git a/docs/OPEN_SCORE_PROVIDER.md b/docs/OPEN_SCORE_PROVIDER.md new file mode 100644 index 0000000..54c5cef --- /dev/null +++ b/docs/OPEN_SCORE_PROVIDER.md @@ -0,0 +1,155 @@ +# Attach Open Score provider semantics + +Status: design note for the attach-guard integration path +Audience: attach-guard maintainers, Attach Open Score implementers, policy authors + +## Goal + +attach-guard should treat Attach Open Score as the first-party default scoring direction while keeping proprietary providers, including Socket, as bring-your-own-token local integrations unless an explicit partnership permits broader hosted/default use. + +This note defines how Attach Open Score verdicts should map into attach-guard behavior before code is added. + +## Source and licensing posture + +Allowed default Attach Open Score inputs are public, open, or otherwise terms-permitted sources with attribution and source references, including OSV, GitHub Advisory Database, deps.dev, OpenSSF Scorecard, public registry metadata, and package artifacts where allowed by each source's license/terms. + +Forbidden for default or hosted Attach scoring unless explicitly reviewed and permitted: + +- copying, scraping, reselling, or redistributing Socket/Snyk/Aikido/Sonatype/Endor scores or vendor data +- using proprietary vendor scores as calibration labels, training data, fixtures, public examples, or threshold targets +- exposing a paid API that behaves like a raw upstream dataset redistribution service + +Socket can remain useful as a local BYO-token provider, but it must not be framed as the default Attach scoring source. + +## Decision mapping + +Attach Open Score v0 emits uppercase public decisions: + +| Attach Open Score | attach-guard behavior | Local default | CI/team default | +|---|---|---|---| +| `ALLOW` | allow install | allow | allow | +| `ASK` | require review / user confirmation | ask/warn | configurable; often deny or require policy approval | +| `DENY` | block install | deny | deny | +| `UNKNOWN` | insufficient evidence | ask/warn | configurable; often deny or require policy approval | + +attach-guard currently has internal `allow`, `ask`, and `deny` decisions only. Until `unknown` becomes a first-class internal decision, Open Score `UNKNOWN` should map to `ask` at the provider/policy boundary for local mode. CI/team policy may map `UNKNOWN` to deny by explicit configuration. + +## Integration boundary + +The preferred v0 implementation should avoid forcing Open Score through the existing Socket-style `PackageScore` threshold path. Add an explicit verdict-carrying result at the provider/policy boundary, for example: + +```go +type ProviderVerdict struct { + Decision string // ALLOW, ASK, DENY, UNKNOWN + RiskScore *int // Open Score risk score, higher means riskier + Reasons []string // Open Score reason codes or rendered reason IDs + SourceRefs []string // source reference IDs/URLs safe for audit output +} +``` + +For the next implementation pass, use option 1: extend the provider/policy result shape so policy can consume `ProviderVerdict` directly. Legacy Socket score thresholds should remain provider-specific signals, not the generic contract for Open Score. + +Decision precedence should remain conservative: + +- explicit local/team denylist beats provider `ALLOW` +- known malware or high-confidence critical evidence beats provider `ALLOW` +- provider `DENY` blocks unless an explicit allowlist/policy override exists +- provider `ASK` and `UNKNOWN` require confirmation locally and may fail policy in CI/team mode +- provider unavailability maps to local ask/warn by default, not silent allow + +## Score polarity warning + +Attach Open Score's numeric `score` is a risk score: higher means riskier. + +attach-guard's current `PackageScore.SupplyChain` and `PackageScore.Overall` fields are treated as safety-ish scores: lower means worse, and the current policy denies when `supply_chain < threshold`. + +Therefore, do not map Open Score `score` directly into `PackageScore.SupplyChain` or `Overall`. That would invert behavior. + +Acceptable implementation patterns: + +1. **Verdict-first bridge** — map Open Score `decision` directly to attach-guard allow/ask/deny behavior, and preserve score/reasons/source refs for explanation/audit UX. +2. **Explicit score transform** — if existing threshold code must be reused temporarily, transform `safety_score = 100 - risk_score` and add tests proving polarity for ALLOW/ASK/DENY/UNKNOWN fixtures. +3. **Policy refactor** — make attach-guard policy understand decision-first verdicts and keep risk score as supporting context rather than the primary decision variable. + +The preferred v0 integration is verdict-first. This leaves less room for accidental polarity inversions. + +## Reason and source propagation + +Open Score verdicts include reason codes and `source_refs`. attach-guard should preserve these in user-facing reasons and audit logs where possible. + +Initial implementation can compress reasons into a human-readable reason string, but should avoid discarding structured data permanently. Future audit entries should be able to include: + +- Open Score reason codes +- severity / decision effect +- source reference IDs +- source names and URLs where safe +- evaluation timestamp and TTL + +## Provider availability + +Local developer mode should not default to fail-closed on provider unavailability or unknown evidence. Default local behavior: + +```text +provider unavailable → ASK / warn +UNKNOWN verdict → ASK / warn +``` + +CI/team mode can be stricter by explicit configuration: + +```text +provider unavailable → DENY or policy failure +UNKNOWN verdict → DENY or policy failure +``` + +## Config direction + +Current config supports a single provider kind and an environment override: + +```yaml +provider: + kind: socket +``` + +```bash +ATTACH_GUARD_PROVIDER=mock +``` + +Future provider kind for this integration should be `open-score`. + +```yaml +provider: + kind: open-score + endpoint: http://127.0.0.1:8757 # local or hosted Attach Open Score-compatible HTTP endpoint + timeout_seconds: 5 +policy: + unknown_behavior: + local: ask # ask | deny | allow + ci: deny + provider_unavailable_behavior: + local: ask # ask | deny | allow + ci: deny +``` + +The v0 implementation target is an HTTP client provider. Embedded Go package or external CLI modes can be added later, but should not block the first provider pass. + +Socket provider docs should show explicit opt-in: + +```yaml +provider: + kind: socket + api_token_env: SOCKET_API_TOKEN +``` + +## Implementation checklist + +Before adding code: + +- [x] Provider kind for the next pass: `open-score`. +- [x] Runtime shape for the next pass: HTTP client provider against a local or hosted Attach Open Score-compatible endpoint. +- [ ] Extend provider/policy result shape with a verdict-first result such as `ProviderVerdict`. +- [ ] Add fixture-driven tests using public-safe synthetic verdicts. +- [ ] Test `UNKNOWN` mapping in local and CI modes via `policy.unknown_behavior`. +- [ ] Test provider-unavailable behavior via `policy.provider_unavailable_behavior`. +- [ ] Test score polarity so high-risk scores cannot accidentally become high-safety scores. +- [ ] Preserve source/legal attribution in docs and audit output. +- [ ] Keep Socket as explicit BYO-token/local provider.