mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-06-11 02:33:10 +08:00
security: harden advisory intake and dependency coverage
This commit is contained in:
83
.github/dependabot.yml
vendored
83
.github/dependabot.yml
vendored
@@ -4,18 +4,99 @@ updates:
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
day: "monday"
|
||||
open-pull-requests-limit: 10
|
||||
labels:
|
||||
- "dependencies"
|
||||
- "npm"
|
||||
groups:
|
||||
minor-and-patch:
|
||||
npm-minor-and-patch:
|
||||
patterns:
|
||||
- "*"
|
||||
update-types:
|
||||
- "minor"
|
||||
- "patch"
|
||||
npm-security:
|
||||
applies-to: "security-updates"
|
||||
patterns:
|
||||
- "*"
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
day: "monday"
|
||||
labels:
|
||||
- "dependencies"
|
||||
- "ci"
|
||||
groups:
|
||||
actions-security:
|
||||
applies-to: "security-updates"
|
||||
patterns:
|
||||
- "*"
|
||||
actions-minor-and-patch:
|
||||
patterns:
|
||||
- "*"
|
||||
update-types:
|
||||
- "minor"
|
||||
- "patch"
|
||||
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
day: "monday"
|
||||
labels:
|
||||
- "dependencies"
|
||||
- "python"
|
||||
groups:
|
||||
pip-security:
|
||||
applies-to: "security-updates"
|
||||
patterns:
|
||||
- "*"
|
||||
pip-minor-and-patch:
|
||||
patterns:
|
||||
- "*"
|
||||
update-types:
|
||||
- "minor"
|
||||
- "patch"
|
||||
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/skills/skill-comply"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
day: "monday"
|
||||
labels:
|
||||
- "dependencies"
|
||||
- "python"
|
||||
groups:
|
||||
skill-comply-pip-security:
|
||||
applies-to: "security-updates"
|
||||
patterns:
|
||||
- "*"
|
||||
skill-comply-pip-minor-and-patch:
|
||||
patterns:
|
||||
- "*"
|
||||
update-types:
|
||||
- "minor"
|
||||
- "patch"
|
||||
|
||||
- package-ecosystem: "cargo"
|
||||
directory: "/ecc2"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
day: "monday"
|
||||
labels:
|
||||
- "dependencies"
|
||||
- "rust"
|
||||
groups:
|
||||
cargo-security:
|
||||
applies-to: "security-updates"
|
||||
patterns:
|
||||
- "*"
|
||||
cargo-minor-and-patch:
|
||||
patterns:
|
||||
- "*"
|
||||
update-types:
|
||||
- "minor"
|
||||
- "patch"
|
||||
|
||||
145
SECURITY.md
145
SECURITY.md
@@ -2,100 +2,155 @@
|
||||
|
||||
## Supported Versions
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 1.9.x | :white_check_mark: |
|
||||
| 1.8.x | :white_check_mark: |
|
||||
| < 1.8 | :x: |
|
||||
| Version | Supported |
|
||||
| --- | --- |
|
||||
| 2.x / rc builds | :white_check_mark: |
|
||||
| 1.10.x | :white_check_mark: |
|
||||
| 1.9.x | Critical fixes only |
|
||||
| < 1.9 | :x: |
|
||||
|
||||
Security fixes land on `main` first. Backports are best-effort and only for currently supported release lines.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you discover a security vulnerability in ECC, please report it responsibly.
|
||||
Use GitHub private vulnerability reporting whenever possible:
|
||||
|
||||
**Do not open a public GitHub issue for security vulnerabilities.**
|
||||
- <https://github.com/affaan-m/ECC/security/advisories/new>
|
||||
|
||||
Instead, email **<security@ecc.tools>** with:
|
||||
You can also email **<security@ecc.tools>**.
|
||||
|
||||
- A description of the vulnerability
|
||||
- Steps to reproduce
|
||||
- The affected version(s)
|
||||
- Any potential impact assessment
|
||||
Do **not** open a public GitHub issue for security vulnerabilities.
|
||||
|
||||
You can expect:
|
||||
Include:
|
||||
|
||||
- **Acknowledgment** within 48 hours
|
||||
- **Status update** within 7 days
|
||||
- **Fix or mitigation** within 30 days for critical issues
|
||||
- affected file, package, version, commit, and install path
|
||||
- steps to reproduce from a clean checkout
|
||||
- expected impact and affected trust boundary
|
||||
- whether exploitation requires local shell access, a malicious repo, a malicious package, a remote unauthenticated actor, or maintainer credentials
|
||||
- any PoC logs with tokens, keys, local paths, and private data redacted
|
||||
|
||||
If the vulnerability is accepted, we will:
|
||||
Expected response:
|
||||
|
||||
- Credit you in the release notes (unless you prefer anonymity)
|
||||
- Fix the issue in a timely manner
|
||||
- Coordinate disclosure timing with you
|
||||
- **Acknowledgment:** within 48 hours
|
||||
- **Initial assessment:** within 7 days
|
||||
- **Critical fix or mitigation target:** within 14 days when the report affects a supported release and crosses a real trust boundary
|
||||
- **Coordinated disclosure:** before public advisory publication
|
||||
|
||||
If the vulnerability is declined, we will explain why and provide guidance on whether it should be reported elsewhere.
|
||||
If a report is declined, we will explain whether it is not reproducible, out of scope, already fixed, or needs a stronger attack path.
|
||||
|
||||
## Scope
|
||||
|
||||
This policy covers:
|
||||
|
||||
- The ECC plugin and all scripts in this repository
|
||||
- Hook scripts that execute on your machine
|
||||
- Install/uninstall/repair lifecycle scripts
|
||||
- MCP configurations shipped with ECC
|
||||
- The AgentShield security scanner ([github.com/affaan-m/agentshield](https://github.com/affaan-m/agentshield))
|
||||
- the `affaan-m/ECC` repository
|
||||
- the `ecc-universal` npm package
|
||||
- ECC plugin, install, repair, dashboard, hook, rule, skill, MCP, and command surfaces shipped from this repository
|
||||
- GitHub Actions workflows and release automation in this repository
|
||||
- the ECC Tools GitHub App integration points documented by this repository
|
||||
- AgentShield usage docs when they are embedded here. AgentShield code issues belong in <https://github.com/affaan-m/agentshield>
|
||||
|
||||
## Official Distribution Surfaces
|
||||
|
||||
Official ECC surfaces are:
|
||||
|
||||
- GitHub repo: <https://github.com/affaan-m/ECC>
|
||||
- npm package: `ecc-universal`
|
||||
- GitHub App: <https://github.com/apps/ecc-tools>
|
||||
- marketplace/plugin slug: `ecc@ecc`
|
||||
- website: <https://ecc.tools>
|
||||
|
||||
Official AgentShield surface:
|
||||
|
||||
- npm package: `ecc-agentshield`
|
||||
- GitHub repo: <https://github.com/affaan-m/agentshield>
|
||||
|
||||
The following packages have been observed using ECC repository metadata but are **not maintained by ECC**:
|
||||
|
||||
- `@chil_ntl/ecc-cli`
|
||||
- `ecc-100xprompt-plugin`
|
||||
|
||||
Treat any package not listed under official surfaces as unofficial until verified. Do not install packages named `opencode-ecc`, `everything-claude-code`, or other ECC-like aliases unless this repository explicitly documents them as official.
|
||||
|
||||
GitHub dependency graph may also show Go module aliases such as `github.com/affaan-m/ecc` or historical repository paths. ECC is not currently distributed as a supported Go module.
|
||||
|
||||
## Out of Scope
|
||||
|
||||
Reports are usually out of scope when they only show:
|
||||
|
||||
- local command execution where the user already controls the local shell and no higher-privilege trust boundary is crossed
|
||||
- screenshots, stale line numbers, or reports against `affaan-m/everything-claude-code` that do not reproduce on current `affaan-m/ECC`
|
||||
- self-XSS or social engineering with no repository-controlled exploit path
|
||||
- dependency graph/package metadata confusion without an install path to an official ECC package
|
||||
- vulnerabilities in third-party packages unless ECC pins, installs, or executes them in a way that creates extra impact
|
||||
|
||||
Local developer tools can still be valid security issues when untrusted repository content, package installation, generated hooks, or CI automation can trigger execution without clear user intent. Show that trust boundary in the report.
|
||||
|
||||
## Supply-Chain Rules
|
||||
|
||||
ECC treats supply-chain exposure as a first-class security surface.
|
||||
|
||||
- GitHub Actions must use pinned commit SHAs for third-party actions.
|
||||
- Workflows must avoid shelling untrusted GitHub context directly into `run:` blocks.
|
||||
- Release and install docs must point only to official packages.
|
||||
- Package metadata should point at `affaan-m/ECC`, not historical repo paths.
|
||||
- Private vulnerability reports are triaged privately before public disclosure.
|
||||
- Security advisories are published only when a supported release is affected and coordinated disclosure is appropriate.
|
||||
|
||||
## Operational Guidance
|
||||
|
||||
### Secrets Handling
|
||||
|
||||
`mcp-configs/mcp-servers.json` is a **template**. All `YOUR_*_HERE` values must be replaced at install time from env-vars or a secrets manager. Never commit real credentials. If a secret is accidentally committed, rotate it immediately and rewrite history; do not rely on a plain revert.
|
||||
`mcp-configs/mcp-servers.json` is a **template**. All `YOUR_*_HERE` values must be replaced at install time from env-vars or a secrets manager. Never commit real credentials. If a secret is accidentally committed, rotate it immediately and rewrite history. Do not rely on a plain revert.
|
||||
|
||||
The same rule applies to your user-scope Claude Code config (`~/.claude/settings.json` or `%USERPROFILE%\.claude\settings.json`). That file is outside this repository, but it is commonly shared via `claude doctor` output, screenshots, or bug reports. Do not hardcode PATs, API keys, or OAuth tokens into its `mcpServers[*].env` blocks; resolve them at spawn time from the OS keychain or env-vars your MCP server already supports. A quick audit:
|
||||
The same rule applies to user-scope Claude Code config (`~/.claude/settings.json` or `%USERPROFILE%\.claude\settings.json`). That file is outside this repository, but it is commonly shared through `claude doctor` output, screenshots, and bug reports. Do not hardcode PATs, API keys, or OAuth tokens into `mcpServers[*].env` blocks. Resolve them at spawn time from the OS keychain or env-vars your MCP server already supports.
|
||||
|
||||
Quick audit:
|
||||
|
||||
```bash
|
||||
# macOS / Linux
|
||||
grep -EnH '(TOKEN|SECRET|KEY|PASSWORD)\s*"\s*:\s*"[A-Za-z0-9_-]{16,}"' ~/.claude/settings.json
|
||||
|
||||
# Windows PowerShell
|
||||
Select-String -Path "$env:USERPROFILE\.claude\settings.json" -Pattern '(TOKEN|SECRET|KEY|PASSWORD)"\s*:\s*"[A-Za-z0-9_-]{16,}"'
|
||||
```
|
||||
|
||||
If the audit matches, rotate the secret at the issuing provider, then move it out of the file (per-provider env-var or `credentialHelper` for servers that support it).
|
||||
If the audit matches, rotate the secret at the issuing provider, then move it out of the file.
|
||||
|
||||
### Local MCP Ports
|
||||
|
||||
Some bundled MCP servers connect over plain HTTP to a localhost port (e.g. `devfleet` to `http://localhost:18801/mcp`). Before first use, verify the listening process:
|
||||
Some bundled MCP servers connect over plain HTTP to a localhost port. Before first use, verify the listening process:
|
||||
|
||||
```bash
|
||||
# Windows
|
||||
netstat -ano | findstr :18801
|
||||
|
||||
# macOS / Linux
|
||||
lsof -iTCP:18801 -sTCP:LISTEN
|
||||
```
|
||||
|
||||
Compare the PID against the expected devfleet binary. Any other process on that port can intercept MCP traffic.
|
||||
Compare the PID against the expected binary. Any other process on that port can intercept MCP traffic.
|
||||
|
||||
## Triage: suspicious `<system-reminder>` blocks
|
||||
|
||||
ECC runs inside Claude Code, which injects **ephemeral client-side system reminders** into the model's input on every turn (TodoWrite nudges, date-changed notices, file-modified notices, etc.). These blocks:
|
||||
ECC runs inside agent harnesses that may inject ephemeral client-side system reminders into the model input on every turn. These blocks are not automatically repository-carried payloads.
|
||||
|
||||
- typically end with phrasing like *"ignore if not applicable"* or *"NEVER mention this reminder to the user"* / *"Don't tell the user this, since they are already aware"*; that wording is Anthropic's own prompt, not a malicious tail;
|
||||
- are added by the CLI per turn and are **not persisted** in the session transcript at `~/.claude/projects/<slug>/<sessionId>.jsonl`.
|
||||
Before treating one as an attack, verify:
|
||||
|
||||
That combination makes them easy to mistake for a prompt-injection appended to a tool result. Before treating one as an attack, verify:
|
||||
1. Is the block actually in a file under this repo?
|
||||
|
||||
1. Is the block actually in a file under this repo? `grep -rEn "system-reminder|NEVER mention|DO NOT mention" .`; if nothing, it is not carried by the repo.
|
||||
2. Is the block stored in the transcript? Inspect the current session's `.jsonl`; if the exact text does not appear inside a `tool_result` body there, it is a client-injected ephemeral reminder, not a payload from any tool.
|
||||
3. Is the content contextually consistent with Anthropic's known reminders (TodoWrite nudge, date-changed, file-modified notice)? If yes, it is the ephemeral-reminder mechanism and no action is needed.
|
||||
```bash
|
||||
grep -rEn "system-reminder|NEVER mention|DO NOT mention" .
|
||||
```
|
||||
|
||||
Escalate to Anthropic only if a block is **both** (a) present in the transcript inside a `tool_result` **and** (b) not attributable to the file or URL that was actually read. Minimal report: a fresh session, a read of a clean local file, the exact text observed, and the transcript excerpt. Send to <https://github.com/anthropics/claude-code/issues> (non-sensitive) or <mailto:security@anthropic.com> (embargo-class).
|
||||
2. Is the block stored in the session transcript as part of a tool result?
|
||||
3. Is it consistent with known client reminders such as TodoWrite nudges, date notices, or file-modified notices?
|
||||
|
||||
Do not sanitize repo files in response to ephemeral reminders; they are not the carrier.
|
||||
Escalate upstream only when the block is present inside a tool result or repository file and is not attributable to the file, URL, or command that was actually read.
|
||||
|
||||
## Security Resources
|
||||
|
||||
- **AgentShield**: Scan your agent config for vulnerabilities — `npx ecc-agentshield scan`
|
||||
- **Security Guide**: [The Shorthand Guide to Everything Agentic Security](./the-security-guide.md)
|
||||
- **Supply-chain incident response**: [npm/GitHub Actions package-registry playbook](./docs/security/supply-chain-incident-response.md)
|
||||
- **OWASP MCP Top 10**: [owasp.org/www-project-mcp-top-10](https://owasp.org/www-project-mcp-top-10/)
|
||||
- **OWASP Agentic Applications Top 10**: [genai.owasp.org](https://genai.owasp.org/resource/owasp-top-10-for-agentic-applications-for-2026/)
|
||||
- **AgentShield:** `npx ecc-agentshield scan`
|
||||
- **Security Guide:** [The Shorthand Guide to Everything Agentic Security](./the-security-guide.md)
|
||||
- **Supply-chain incident response:** [npm/GitHub Actions package-registry playbook](./docs/security/supply-chain-incident-response.md)
|
||||
- **OWASP MCP Top 10:** <https://owasp.org/www-project-mcp-top-10/>
|
||||
- **OWASP Agentic Applications Top 10:** <https://genai.owasp.org/resource/owasp-top-10-for-agentic-applications-for-2026/>
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
],
|
||||
"author": {
|
||||
"name": "Affaan Mustafa",
|
||||
"url": "https://x.com/affaanmustafa"
|
||||
"url": "https://x.com/affaan"
|
||||
},
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -23,6 +23,9 @@ import subprocess
|
||||
import sys
|
||||
import re
|
||||
import shutil
|
||||
import ipaddress
|
||||
import socket
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
from datetime import datetime, timedelta, timezone
|
||||
@@ -181,6 +184,55 @@ def _validate_instinct_id(instinct_id: str) -> bool:
|
||||
return bool(re.match(r"^[A-Za-z0-9][A-Za-z0-9._-]*$", instinct_id))
|
||||
|
||||
|
||||
def _validate_import_url(source: str) -> str:
|
||||
"""Validate remote instinct imports before opening a network connection."""
|
||||
parsed = urllib.parse.urlparse(source)
|
||||
if parsed.scheme != "https":
|
||||
raise ValueError("remote instinct imports require https URLs")
|
||||
if not parsed.hostname:
|
||||
raise ValueError("remote import URL is missing a hostname")
|
||||
|
||||
try:
|
||||
addr_infos = socket.getaddrinfo(parsed.hostname, parsed.port or 443, type=socket.SOCK_STREAM)
|
||||
except socket.gaierror as exc:
|
||||
raise ValueError(f"remote import host could not be resolved: {parsed.hostname}") from exc
|
||||
|
||||
for family, _, _, _, sockaddr in addr_infos:
|
||||
host = sockaddr[0]
|
||||
try:
|
||||
ip = ipaddress.ip_address(host)
|
||||
except ValueError:
|
||||
continue
|
||||
if (
|
||||
ip.is_private
|
||||
or ip.is_loopback
|
||||
or ip.is_link_local
|
||||
or ip.is_multicast
|
||||
or ip.is_reserved
|
||||
or ip.is_unspecified
|
||||
):
|
||||
raise ValueError(f"remote import host resolves to a non-public address: {host}")
|
||||
|
||||
return urllib.parse.urlunparse(parsed)
|
||||
|
||||
|
||||
def _fetch_import_url(source: str, *, max_bytes: int = 2 * 1024 * 1024) -> str:
|
||||
"""Fetch a validated remote instinct file with bounded size and timeout."""
|
||||
url = _validate_import_url(source)
|
||||
req = urllib.request.Request(url, headers={"User-Agent": "ECC-instinct-import/2"})
|
||||
with urllib.request.urlopen(req, timeout=15) as response:
|
||||
content_type = response.headers.get("Content-Type", "")
|
||||
if content_type and not any(
|
||||
allowed in content_type.lower()
|
||||
for allowed in ("text/", "markdown", "yaml", "json", "octet-stream")
|
||||
):
|
||||
raise ValueError(f"unsupported remote content type: {content_type}")
|
||||
data = response.read(max_bytes + 1)
|
||||
if len(data) > max_bytes:
|
||||
raise ValueError(f"remote import exceeds {max_bytes} bytes")
|
||||
return data.decode("utf-8")
|
||||
|
||||
|
||||
def _yaml_quote(value: str) -> str:
|
||||
"""Quote a string for safe YAML frontmatter serialization.
|
||||
|
||||
@@ -794,8 +846,7 @@ def cmd_import(args) -> int:
|
||||
if source.startswith('http://') or source.startswith('https://'):
|
||||
print(f"Fetching from URL: {source}")
|
||||
try:
|
||||
with urllib.request.urlopen(source) as response:
|
||||
content = response.read().decode('utf-8')
|
||||
content = _fetch_import_url(source)
|
||||
except Exception as e:
|
||||
print(f"Error fetching URL: {e}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
@@ -44,6 +44,7 @@ _promote_auto = _mod._promote_auto
|
||||
_find_cross_project_instincts = _mod._find_cross_project_instincts
|
||||
load_registry = _mod.load_registry
|
||||
_validate_instinct_id = _mod._validate_instinct_id
|
||||
_validate_import_url = _mod._validate_import_url
|
||||
_update_registry = _mod._update_registry
|
||||
_confidence_bar = _mod._confidence_bar
|
||||
|
||||
@@ -326,6 +327,32 @@ def test_validate_relative_path(tmp_path, monkeypatch):
|
||||
assert result == test_file.resolve()
|
||||
|
||||
|
||||
def test_validate_import_url_rejects_http():
|
||||
"""Remote imports should not downgrade to plaintext HTTP."""
|
||||
with pytest.raises(ValueError, match="require https"):
|
||||
_validate_import_url("http://example.com/instincts.yaml")
|
||||
|
||||
|
||||
def test_validate_import_url_rejects_private_hosts(monkeypatch):
|
||||
"""Remote imports should not resolve to private or loopback addresses."""
|
||||
monkeypatch.setattr(
|
||||
_mod.socket,
|
||||
"getaddrinfo",
|
||||
lambda *args, **kwargs: [(None, None, None, None, ("127.0.0.1", 443))],
|
||||
)
|
||||
with pytest.raises(ValueError, match="non-public address"):
|
||||
_validate_import_url("https://example.com/instincts.yaml")
|
||||
|
||||
|
||||
def test_validate_import_url_allows_public_https(monkeypatch):
|
||||
monkeypatch.setattr(
|
||||
_mod.socket,
|
||||
"getaddrinfo",
|
||||
lambda *args, **kwargs: [(None, None, None, None, ("93.184.216.34", 443))],
|
||||
)
|
||||
assert _validate_import_url("https://example.com/instincts.yaml") == "https://example.com/instincts.yaml"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────
|
||||
# detect_project tests
|
||||
# ─────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user