diff --git a/CHANGELOG.md b/CHANGELOG.md index 011fcb5..8549814 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to Sunglasses are documented here. +## [0.2.65] — 2026-06-10 + +### Added (discovery_file_poisoning expansion) + +- **+19 discovery_file_poisoning patterns** (`GLS-DFP-058`..`GLS-DFP-082`, excluding 060/063/066/069/070/076) — new coverage for poisoned discovery surfaces: Allure/test-report metadata, `security.txt`, `.well-known` manifests, syndication feeds, and related agent-policy carriers. **1,019 → 1,038 patterns / 65 categories / 7,548 keywords.** +- 6 patterns from the same batch were **held back** for false-positive tightening (they fired on clean code/docs) — the FP credibility gate from v0.2.64 caught them before ship. They will return after regex hardening. + +### Fixed (credibility) + +- Genericized 3 pattern descriptions that referenced internal operator paths/filenames (no detection change) — public surfaces no longer expose internal infrastructure naming. + ## [0.2.64] — 2026-06-09 ### Fixed (engine reliability — false positives + scanner hang) diff --git a/README.md b/README.md index 1b84850..99052c2 100644 --- a/README.md +++ b/README.md @@ -139,8 +139,8 @@ result = scanner.scan_auto("any_file.ext") |--------|-------| | Average text scan | <1ms (avg 0.26ms on M3 Max, single-threaded) | | Throughput | ~3,800 scans/sec (single-threaded, M3 Max) | -| Patterns | 1019 | -| Keywords | 7,350 | +| Patterns | 1038 | +| Keywords | 7,548 | | Languages | 23 | | Attack categories | 65 | | Normalization techniques | 17 | @@ -151,15 +151,15 @@ result = scanner.scan_auto("any_file.ext") | Core dependencies | Zero for text scan; optional deps for media | | Platforms | Mac, Windows, Linux — anywhere Python runs | -_All performance numbers verified against `stats/current.json` (v0.2.64, updated Jun 6, 2026). Measured on Apple M3 Max, 48GB RAM, single-threaded Python 3.11. Your hardware will differ._ +_All performance numbers verified against `stats/current.json` (v0.2.65, updated Jun 6, 2026). Measured on Apple M3 Max, 48GB RAM, single-threaded Python 3.11. Your hardware will differ._ ## 23 Languages English, Spanish, Portuguese, French, German, Italian, Dutch, Russian, Ukrainian, Polish, Czech, Turkish, Azerbaijani, Arabic, Hebrew, Persian, Chinese, Japanese, Korean, Hindi, Bengali, Indonesian, Vietnamese — plus normalization handles romanization, Unicode confusables, and 17 other obfuscation techniques. Community language contributions welcome. -## What Works Today (v0.2.64) +## What Works Today (v0.2.65) -- ✅ Text scanning: 1019 patterns, 7,350 keywords, 23 languages, 65 attack categories +- ✅ Text scanning: 1038 patterns, 7,548 keywords, 23 languages, 65 attack categories - ✅ Negation handling: "do NOT run rm -rf" correctly downgrades severity - ✅ Multi-stage pipeline: normalization (17 techniques) → pattern match → decision - ✅ Image scanning: OCR + EXIF metadata + hidden text detection (requires Tesseract) diff --git a/setup.py b/setup.py index 7dead43..36527fc 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name="sunglasses", - version="0.2.64", + version="0.2.65", description="Sunglasses for AI agents. Protection layer + neighborhood watch.", long_description=open("README.md").read(), long_description_content_type="text/markdown", diff --git a/stats/current.json b/stats/current.json index 3a72a31..3a80002 100644 --- a/stats/current.json +++ b/stats/current.json @@ -1,7 +1,7 @@ { - "version": "0.2.64", - "patterns": 1019, - "keywords": 7350, + "version": "0.2.65", + "patterns": 1038, + "keywords": 7548, "categories": 65, "languages": 23, "normalization_techniques": 17, @@ -16,8 +16,8 @@ "media_types": 6, "reports_published": 3, "team_size": 5, - "last_updated": "2026-06-09T23:43:25-07:00", + "last_updated": "2026-06-10T04:23:21-07:00", "last_updated_by": "sunglasses-publish-sh", "_note": "THIS IS THE SINGLE SOURCE OF TRUTH. All pages, JSON-LD, meta tags, llms.txt, and sitemap must read from this file. Do NOT hardcode numbers anywhere else.", - "released": "2026-06-09" + "released": "2026-06-10" } diff --git a/sunglasses/__init__.py b/sunglasses/__init__.py index 33536e9..f5f4fd3 100644 --- a/sunglasses/__init__.py +++ b/sunglasses/__init__.py @@ -4,4 +4,4 @@ # # "Don't let your agents get fooled." -__version__ = "0.2.64" +__version__ = "0.2.65" diff --git a/sunglasses/patterns.py b/sunglasses/patterns.py index 04ec1de..1fa72b1 100644 --- a/sunglasses/patterns.py +++ b/sunglasses/patterns.py @@ -9035,7 +9035,7 @@ r"(?i)(?:write|append|log|record)\s+(?:fake|forged|fabricated|premature)\s+(?:success|status|completion|done)\s+(?:before|prior\s+to|without|skipping)\s+(?:verification|validation|run|execution)", r"(?i)(?:status|reply|append|audit)\s+(?:log|file|trail)\s+(?:desync|drift|divergence|laundering|truncation\s+blindspot)" ], - "description": "GO MODE relies on append-only status artifacts (cava_wins_log.md + CAVA_REPLY_*) as the operational source of truth. If an attacker can inject instructions that desynchronize **what got executed** vs **what got logged**, operators lose visibility and may ship unverified claims." + "description": "GO MODE relies on append-only status artifacts (wins-log files and status-reply files) as the operational source of truth. If an attacker can inject instructions that desynchronize **what got executed** vs **what got logged**, operators lose visibility and may ship unverified claims." }, { "id": "GLS-AW-019", @@ -9246,7 +9246,7 @@ r"(?i)(?:prepend|inject|insert)\s+(?:stale|outdated|old|prior)\s+(?:BLOCKED?|FAIL(?:ED)?|READY|status|verdict)\s+(?:section|block|header|heading)", r"(?i)(?:near[- ]identical|duplicate|colliding)\s+(?:heading|marker|status\s+label|section\s+title)\s+(?:so|to)\s+(?:tool|parser|operator)" ], - "description": "In GO MODE operations, /opt/sunglasses/review/CAVA_REPLY_... is treated as a high-trust operator status surface. Attackers can inject or preserve stale status blocks so that old blocker text overshadows fresh cycle outcomes. This can force bad prioritization (re-open solved tasks, suppress live P0 pivots, or mislabel readiness). The failure mode is subtle because the reply file is expected to be append-only and long." + "description": "In GO MODE operations, an append-only operator status file is treated as a high-trust status surface. Attackers can inject or preserve stale status blocks so that old blocker text overshadows fresh cycle outcomes. This can force bad prioritization (re-open solved tasks, suppress live P0 pivots, or mislabel readiness). The failure mode is subtle because the reply file is expected to be append-only and long." }, { "id": "GLS-AW-027", @@ -9655,7 +9655,7 @@ "regex": [ r"""(?i)(?:(?:forge|fabricate|inject|append)\s+(?:cava[- ]reply|wins[- ]log|status[- ]log|reply[- ]log)\s+(?:entry|line|artifact)|(?:append[- ]only|status)\s+(?:log|artifact|channel)\s+(?:drift|forgery|poisoning))""", ], - "description": "Attacker injects or forges append-only operational status artifacts (CAVA_REPLY, wins-log, cron acknowledgements) so the agent's GO MODE reads fabricated success signals as ground truth, advancing pipelines on work that never ran or hiding failures behind ghost log lines." + "description": "Attacker injects or forges append-only operational status artifacts (status-reply files, wins-logs, cron acknowledgements) so the agent's GO MODE reads fabricated success signals as ground truth, advancing pipelines on work that never ran or hiding failures behind ghost log lines." }, { "id": "GLS-AW-035", @@ -21290,4 +21290,760 @@ ], "description": "A hostile `PULL_REQUEST_TEMPLATE.md` can smuggle agent-facing instructions into the PR authoring workflow, telling AI coding agents or review bots that the template is authoritative policy and should suppress findings, omit reviewers, or include local secrets in the PR body." }, + # === v0.2.65 batch (19 patterns) === + { + "id": "GLS-DFP-058", + "name": "Allure report metadata poisoning", + "category": "discovery_file_poisoning", + "severity": "high", + "channel": ["file", "web_content"], + "keywords": [ + "AI agents", + "allure-results/*.json", + "categories.json", + "container.json", + "do not obey/treat/use Allure metadata", + "environment.properties", + "executor.json", + "for agents", + "result.json", + "scanner instruction", + "statusDetails", + ], + "regex": [ + r"""(?six)^ +(?!.*\b(?:do\s+not|does\s+not|never|must\s+not|should\s+never|should\s+not)\s+(?:be\s+)?(?:obey(?:ed)?|follow(?:ed)?|treat(?:ed)?|us(?:e|ed)|trust(?:ed)?|include|attach|forward|send|share|override).{0,180}\b(?:allure|allure-results|test\s+report|test\s+result|statusDetails|attachment\s+name)) +(?!.*\b(?:security\s+guide|scanner\s+docs?|training\s+example|incident\s+report|postmortem|reference)\b.{0,180}\b(?:reported|detected|blocked|flagged|refused|not\s+obeyed|must\s+be\s+reported|should\s+report)) +(?=.*(?:\ballure(?:[-_\s]?results?|\s+report|\s+test\s+report)?\b|allure-results/[^\s]+\.json|result\.json|container\.json|categories\.json|executor\.json|environment\.properties|history-trend\.json|widgets/[^\s]+\.json|statusDetails|\"status\"\s*:\s*\"(?:passed|failed|broken|skipped)\"|\"attachments?\"\s*:|\"labels?\"\s*:)) +(?=.*(?:ai\s+agents?|agents?|assistants?|scanners?|reviewers?|ci\s+agents?|test\s+agents?|release\s+agents?|security\s+agents?|automated\s+(?:test|ci|release|security)?\s*(?:review|scan|audit|verification)|agent[-_\s]?instructions?|scanner[-_\s]?instructions?|for[-_\s]?agents?)) +(?=.*(?: + (?:authoritative|canonical|definitive|governing|primary|single\s+source\s+of\s+truth|takes?\s+precedence|supersed(?:e|es|ed|ing)|overrides?|agent\s+instructions?|scanner\s+instructions?).{0,120}(?:policy|rules?|instructions?|verdict|review|guidance|playbook|runbook|procedure) + |(?:suppress(?:es|ed|ing)?|hide|hidden|omit(?:ted|s|ting)?|exclude(?:d|s|ing)?|downgrade|deprioritize|demote|redact|skip|mark|treat|classify|route|move|leave|keep).{0,120}(?:findings?|warnings?|alerts?|reports?|results?|observations?|diagnostics?|failures?|defects?|vulnerabilit(?:y|ies)|security\s+issues?|audit\s+memo|final\s+summary|blocking|informational|advisory-only|background-only) + |(?:findings?|warnings?|alerts?|reports?|results?|observations?|diagnostics?|failures?|defects?).{0,80}(?:are|is|should\s+be|must\s+be)\s+(?:suppressed|hidden|omitted|excluded|redacted|downgraded|treated|classified|marked).{0,60}(?:informational|advisory-only|background-only|non-blocking)? + |(?:include|attach|forward|send|share|provide|expose|copy|collect|pass\s+along|hand\s+over|relay).{0,120}(?:api\s*keys?|tokens?|secrets?|authorization|auth\s+headers?|cookies?|session|ci\s+environment|environment\s+(?:variables?|context|settings?|details?)|runtime\s+(?:context|settings?|details?)|local\s+state|execution\s+context) +)) +.*""", + ], + "description": "Allure test-report JSON, widget, executor, environment, attachment-name, or rendered report metadata can carry agent-directed instructions that make CI/review agents suppress findings, rewrite test verdicts, or forward CI/runtime context." + }, + { + "id": "GLS-DFP-059", + "name": "API portal/catalog metadata poisoning", + "category": "discovery_file_poisoning", + "severity": "high", + "channel": ["file", "web_content"], + "keywords": [ + "agent_instructions", + "assistant_note", + "externalDocs", + "scanner_policy", + ], + "regex": [ + r"""(?six)^ +(?!.*(?:\b(?:security\s+(?:training|guide|reference)|scanner\s+docs?|defensive\s+example|incident\s+report|quoted\s+attack|do\s+not\s+(?:obey|follow|trust|treat|use|save|persist|store|write)|does\s+not\s+(?:obey|follow|trust|treat|use|override|instruct)|never\s+(?:obey|trust|treat|use|override|forward|include)|must\s+not\s+(?:obey|trust|treat|use|override|forward|include)|should\s+(?:report|detect|flag|refuse|block|not\s+\w+|never\b)|must\s+be\s+reported|not\s+obeyed).{0,180}?\b(?:api\s+(?:portal|catalog|reference|docs?|documentation)|developer\s+portal|stoplight|readme(?:\.com)?|redocly|endpoint\s+catalog)\b|\b(?:api\s+(?:portal|catalog|reference|docs?|documentation)|developer\s+portal|stoplight|readme(?:\.com)?|redocly|endpoint\s+catalog)\b.{0,180}?\b(?:do\s+not|does\s+not|never|should\s+never|must\s+not)\b.{0,80}?\b(?:override|suppress|forward|include|obey|treat)\b)) +(?=.*(?:\b(?:stoplight|readme(?:\.com)?|redocly|api\s+(?:portal|catalog|reference|documentation|docs)|developer\s+portal|generated\s+api\s+(?:docs|reference)|reference\s+page|operation\s+reference|endpoint\s+catalog|externalDocs|api\s+styleguide|docs\s+portal)\b|(?", + "