Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Changelog

## Unreleased

### Added

- `scripts/ci/validate-agents.js`: detects duplicate top-level YAML frontmatter keys in agent files (e.g. two `model:` lines silently collapsing to the last value) and fails validation with the duplicated key names.
- `SECURITY.md`: new operational sections — Secrets Handling (including a user-scope `~/.claude/settings.json` audit recipe for macOS/Linux/Windows), Local MCP Ports (how to verify the listener on bundled HTTP MCP endpoints such as `devfleet`), and a Triage section explaining Claude Code's ephemeral client-side `<system-reminder>` blocks (to avoid misclassifying them as prompt injections).

### Fixed

- `agents/a11y-architect.md`: removed stray duplicate `model:` line so YAML parsers no longer silently override `sonnet` with a later `opus` entry.
Comment on lines +7 to +12
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Unreleased block references files not in this PR

The Unreleased section documents changes to scripts/ci/validate-agents.js and agents/a11y-architect.md, but neither file appears in this PR's diff (only CHANGELOG.md and SECURITY.md are changed). If those changes are intended to land in a separate PR, having them recorded here will conflate unrelated work under one changelog entry and make the release diff hard to audit. If they're already merged to main, they should be under a versioned heading, not Unreleased.


## 1.10.0 - 2026-04-05

### Highlights
Expand Down
47 changes: 47 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,53 @@ This policy covers:
- MCP configurations shipped with ECC
- The AgentShield security scanner ([github.com/affaan-m/agentshield](https://github.com/affaan-m/agentshield))

## 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.

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:

```bash
# macOS / Linux
grep -EnH '(TOKEN|SECRET|KEY|PASSWORD)\s*"\s*:\s*"[A-Za-z0-9_-]{16,}"' ~/.claude/settings.json
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Audit grep misses lowercase key names

The pattern (TOKEN|SECRET|KEY|PASSWORD) is case-sensitive (no -i flag), so common lowercase variants like "api_key", "secret_token", or "access_token" are silently skipped. Adding -i closes this gap:

Suggested change
grep -EnH '(TOKEN|SECRET|KEY|PASSWORD)\s*"\s*:\s*"[A-Za-z0-9_-]{16,}"' ~/.claude/settings.json
grep -EniH '(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).

### Local MCP Ports

Some bundled MCP servers connect over plain HTTP to a localhost port (e.g. `devfleet → http://localhost:18801/mcp`). 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.

## Triage: suspicious `<system-reminder>` blocks
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Heading level breaks document hierarchy

The Triage section uses an ## heading, placing it at the same level as ## Operational Guidance and ## Security Resources, but its content (like Secrets Handling and Local MCP Ports above it) is clearly operational guidance. Using ### keeps the ToC hierarchy consistent and avoids readers thinking this is a separate top-level policy section.

Suggested change
## Triage: suspicious `<system-reminder>` blocks
### 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:

- 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`.
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Security triage uses an absolute transcript-persistence claim that is version-sensitive and can cause false negatives in incident classification.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At SECURITY.md, line 83:

<comment>Security triage uses an absolute transcript-persistence claim that is version-sensitive and can cause false negatives in incident classification.</comment>

<file context>
@@ -45,6 +45,53 @@ This policy covers:
+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:
+
+- 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`.
+
+That combination makes them easy to mistake for a prompt-injection appended to a tool result. Before treating one as an attack, verify:
</file context>
Fix with Cubic


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? `grep -rEn "system-reminder|NEVER mention|DO NOT mention" .` — if nothing, it is not carried by the repo.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Self-referential grep false positive in triage step 1

The triage checklist instructs users to run grep -rEn "system-reminder|NEVER mention|DO NOT mention" . to determine whether a suspicious block is carried by the repo. However, SECURITY.md now contains the literal strings "system-reminder" and "NEVER mention" (lines 82–87 document them verbatim), so this grep will always produce a match against SECURITY.md itself. The logic immediately after the command — "if nothing, it is not carried by the repo" — becomes unreliable: there will never be "nothing", causing every triager to conclude the repo is a carrier when it isn't.

Fix: exclude SECURITY.md from the grep, or adjust the surrounding prose to say "ignore hits in SECURITY.md":

Suggested change
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.
1. Is the block actually in a file under this repo? `grep -rEn --exclude=SECURITY.md "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.

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).

Do not sanitize repo files in response to ephemeral reminders — they are not the carrier.

## Security Resources

- **AgentShield**: Scan your agent config for vulnerabilities — `npx ecc-agentshield scan`
Expand Down