mirror of https://github.com/garrytan/gstack.git
6 Commits
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
97584f9a59
|
feat(security): ML prompt injection defense for sidebar (v1.4.0.0) (#1089)
* chore(deps): add @huggingface/transformers for prompt injection classifier
Dependency needed for the ML prompt injection defense layer coming in the
follow-up commits. @huggingface/transformers will host the TestSavantAI
BERT-small classifier that scans tool outputs for indirect prompt injection.
Note: this dep only runs in non-compiled bun contexts (sidebar-agent.ts).
The compiled browse binary cannot load it because transformers.js v4 requires
onnxruntime-node (native module, fails to dlopen from bun compile's temp
extract dir). See docs/designs/ML_PROMPT_INJECTION_KILLER.md for the full
architectural decision.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(security): add security.ts foundation for prompt injection defense
Establishes the module structure for the L5 canary and L6 verdict aggregation
layers. Pure-string operations only — safe to import from the compiled browse
binary.
Includes:
* THRESHOLDS constants (BLOCK 0.85 / WARN 0.60 / LOG_ONLY 0.40), calibrated
against BrowseSafe-Bench smoke + developer content benign corpus.
* combineVerdict() implementing the ensemble rule: BLOCK only when the ML
content classifier AND the transcript classifier both score >= WARN.
Single-layer high confidence degrades to WARN to prevent any one
classifier's false-positives from killing sessions (Stack Overflow
instruction-writing-style FPs at 0.99 on TestSavantAI alone).
* generateCanary / injectCanary / checkCanaryInStructure — session-scoped
secret token, recursively scans tool arguments, URLs, file writes, and
nested objects per the plan's all-channel coverage decision.
* logAttempt with 10MB rotation (keeps 5 generations). Salted SHA-256 hash,
per-device salt at ~/.gstack/security/device-salt (0600).
* Cross-process session state at ~/.gstack/security/session-state.json
(atomic temp+rename). Required because server.ts (compiled) and
sidebar-agent.ts (non-compiled) are separate processes.
* getStatus() for shield icon rendering via /health.
ML classifier code will live in a separate module (security-classifier.ts)
loaded only by sidebar-agent.ts — compiled browse binary cannot load the
native ONNX runtime.
Plan: ~/.gstack/projects/garrytan-gstack/ceo-plans/2026-04-19-prompt-injection-guard.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(security): wire canary injection into sidebar spawnClaude
Every sidebar message now gets a fresh CANARY-XXXXXXXXXXXX token embedded
in the system prompt with an instruction for Claude to never output it on
any channel. The token flows through the queue entry so sidebar-agent.ts
can check every outbound operation for leaks.
If Claude echoes the canary into any outbound channel (text stream, tool
arguments, URLs, file write paths), the sidebar-agent terminates the
session and the user sees the approved canary leak banner.
This operation is pure string manipulation — safe in the compiled browse
binary. The actual output-stream check (which also has to be safe in
compiled contexts) lives in sidebar-agent.ts (next commit).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(security): make sidebar-agent destructure check regex-tolerant
The test asserted the exact string `const { prompt, args, stateFile, cwd, tabId } = queueEntry`
which breaks whenever security or other extensions add fields (canary, pageUrl,
etc.). Switch to a regex that requires the core fields in order but tolerates
additional fields in between. Preserves the test's intent (args come from the
queue entry, not rebuilt) while allowing the destructure to grow.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(security): canary leak check across all outbound channels
The sidebar-agent now scans every Claude stream event for the session's
canary token before relaying any data to the sidepanel. Channels covered
(per CEO review cross-model tension #2):
* Assistant text blocks
* Assistant text_delta streaming
* tool_use arguments (recursively, via checkCanaryInStructure — catches
URLs, commands, file paths nested at any depth)
* tool_use content_block_start
* tool_input_delta partial JSON
* Final result payload
If the canary leaks on any channel, onCanaryLeaked() fires once per session:
1. logAttempt() writes the event to ~/.gstack/security/attempts.jsonl
with the canary's salted hash (never the payload content).
2. sends a `security_event` to the sidepanel so it can render the approved
canary-leak banner (variant A mockup — ceo-plan 2026-04-19).
3. sends an `agent_error` for backward-compat with existing error surfaces.
4. SIGTERM's the claude subprocess (SIGKILL after 2s if still alive).
The leaked content itself is never relayed to the sidepanel — the event is
dropped at the boundary. Canary detection is pure-string substring match,
so this all runs safely in the sidebar-agent (non-compiled bun) context.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(security): add security-classifier.ts with TestSavantAI + Haiku
This module holds the ML classifier code that the compiled browse binary
cannot link (onnxruntime-node native dylib doesn't load from Bun compile's
temp extract dir — see CEO plan §"Pre-Impl Gate 1 Outcome"). It's imported
ONLY by sidebar-agent.ts, which runs as a non-compiled bun script.
Two layers:
L4 testsavant_content — TestSavantAI BERT-small ONNX classifier. First call
triggers a one-time 112MB model download to ~/.gstack/models/testsavant-small/
(files staged into the onnx/ layout transformers.js v4 expects). Classifies
page snapshots and tool outputs for indirect prompt injection + jailbreak
attempts. On benign-corpus dry-run: Wikipedia/HN/Reddit/tech-blog all score
SAFE 0.98+, attack text scores INJECTION 0.99+, Stack Overflow
instruction-writing now scores SAFE 0.98 on the shorter form (was 0.99
INJECTION on the longer form — instruction-density threshold). Ensemble
combiner downgrades single-layer high to WARN to cover this case.
L4b transcript_classifier — Claude Haiku reasoning-blind pre-tool-call scan.
Sees only {user_message, last 3 tool_calls}, never Claude's chain-of-thought
or tool results (those are how self-persuasion attacks leak). 2000ms hard
timeout. Fail-open on any subprocess failure so sidebar stays functional.
Gated by shouldRunTranscriptCheck() — only runs when another layer already
fired at >= LOG_ONLY, saving ~70% of Haiku spend.
Both layers degrade gracefully: load/spawn failures set status to 'degraded'
and return confidence=0. Shield icon reflects this via getClassifierStatus()
which security.ts's getStatus() composes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(security): wire TestSavantAI + ensemble into sidebar-agent pre-spawn scan
The sidebar-agent now runs a ML security check on the user message BEFORE
spawning claude. If the content classifier and (gated) transcript classifier
ensemble returns BLOCK, the session is refused with a security_event +
agent_error — the sidepanel renders the approved banner.
Two pieces:
1. On agent startup, loadTestsavant() warms the classifier in the background.
First run triggers a 112MB model download from HuggingFace (~30s on
average broadband). Non-blocking — sidebar stays functional during
cold-start, shield just reports 'off' until warmed.
2. preSpawnSecurityCheck() runs the ensemble against the user message:
- L4 (testsavant_content) always runs
- L4b (transcript_classifier via Haiku) runs only if L4 flagged at
>= LOG_ONLY — plan §E1 gating optimization, saves ~70% of Haiku spend
combineVerdict() applies the BLOCK-requires-both-layers rule, which
downgrades any single-layer high confidence to WARN. Stack Overflow-style
instruction-heavy writing false-positives on TestSavantAI alone are
caught by this degrade — Haiku corrects them when called.
Fail-open everywhere: any subprocess/load/inference error returns confidence=0
so the sidebar keeps working on architectural controls alone. Shield icon
reflects degraded state via getClassifierStatus().
BLOCK path emits both:
- security_event {verdict, reason, layer, confidence, domain} (for the
approved canary-leak banner UX mockup — variant A)
- agent_error "Session blocked — prompt injection detected..."
(backward-compat with existing error surface)
Regression test suite still passes (12/12 sidebar-security tests).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(security): add security.ts unit tests (25 tests, 62 assertions)
Covers the pure-string operations that must behave deterministically in both
compiled and source-mode bun contexts:
* THRESHOLDS ordering invariant (BLOCK > WARN > LOG_ONLY > 0)
* combineVerdict ensemble rule — THE critical path:
- Empty signals → safe
- Canary leak always blocks (regardless of ML signals)
- Both ML layers >= WARN → BLOCK (ensemble_agreement)
- Single layer >= BLOCK → WARN (single_layer_high) — the Stack Overflow
FP mitigation that prevents one classifier killing sessions alone
- Max-across-duplicates when multiple signals reference the same layer
* Canary generation + injection + recursive checking:
- Unique CANARY-XXXXXXXXXXXX tokens (>= 48 bits entropy)
- Recursive structure scan for tool_use inputs, nested URLs, commands
- Null / primitive handling doesn't throw
* Payload hashing (salted sha256) — deterministic per-device, differs across
payloads, 64-char hex shape
* logAttempt writes to ~/.gstack/security/attempts.jsonl
* writeSessionState + readSessionState round-trip (cross-process)
* getStatus returns valid SecurityStatus shape
* extractDomain returns hostname only, empty string on bad input
All 25 tests pass in 18ms — no ML, no network, no subprocess spawning.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(security): expose security status on /health for shield icon
The /health endpoint now returns a `security` field with the classifier
status, suitable for driving the sidepanel shield icon:
{
status: 'protected' | 'degraded' | 'inactive',
layers: { testsavant, transcript, canary },
lastUpdated: ISO8601
}
Backend plumbing:
* server.ts imports getStatus from security.ts (pure-string, safe in
compiled binary) and includes it in the /health response.
* sidebar-agent.ts writes ~/.gstack/security/session-state.json when the
classifier warmup completes (success OR failure). This is the cross-
process handoff — server.ts reads the state file via getStatus() to
surface the result to the sidepanel.
The sidepanel rendering (SVG shield icon + color states + tooltip) is a
follow-up commit in the extension/ code.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(security): document the sidebar security stack in CLAUDE.md
Adds a security section to the Browser interaction block. Covers:
* Layered defense table showing which modules live where (content-security.ts
in both contexts vs security-classifier.ts only in sidebar-agent) and why
the split exists (onnxruntime-node incompatibility with compiled Bun)
* Threshold constants (0.85 / 0.60 / 0.40) and the ensemble rule that
prevents single-classifier false-positives (the Stack Overflow FP story)
* Env knobs — GSTACK_SECURITY_OFF kill switch, cache paths, salt file,
attack log rotation, session state file
This is the "before you modify the security stack, read this" doc. It lives
next to the existing Sidebar architecture note that points at
SIDEBAR_MESSAGE_FLOW.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(todos): mark ML classifier v1 in-progress + file v2 follow-ups
Reframes the P0 item to reflect v1 scope (branch 2 architecture, TestSavantAI
pivot, what shipped) and splits v2 work into discrete TODOs:
* Shield icon + canary leak banner UI (P0, blocks v1 user-facing completion)
* Attack telemetry via gstack-telemetry-log (P1)
* Full BrowseSafe-Bench at gate tier (P2)
* Cross-user aggregate attack dashboard (P2)
* DeBERTa-v3 as third signal in ensemble (P2)
* Read/Glob/Grep ingress coverage (P2, flagged by Codex review)
* Adversarial + integration + smoke-bench test suites (P1)
* Bun-native 5ms inference (P3 research)
Each TODO carries What / Why / Context / Effort / Priority / Depends-on so
it's actionable by someone picking it up cold.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(telemetry): add attack_attempt event type to gstack-telemetry-log
Extends the existing telemetry pipe with 5 new flags needed for prompt
injection attack reporting:
--url-domain hostname only (never path, never query)
--payload-hash salted sha256 hex (opaque — no payload content ever)
--confidence 0-1 (awk-validated + clamped; malformed → null)
--layer testsavant_content | transcript_classifier | aria_regex | canary
--verdict block | warn | log_only
Backward compatibility:
* Existing skill_run events still work — all new fields default to null
* Event schema is a superset of the old one; downstream edge function can
filter by event_type
No new auth, no new SDK, no new Supabase migration. The same tier gating
(community → upload, anonymous → local only, off → no-op) and the same
sync daemon carry the attack events. This is the "E6 RESOLVED" path from
the CEO plan — riding the existing pipe instead of spinning up parallel infra.
Verified end-to-end:
* attack_attempt event with all fields emits correctly to skill-usage.jsonl
* skill_run event with no security flags still works (backward compat)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(security): wire logAttempt to gstack-telemetry-log (fire-and-forget)
Every local attempt.jsonl write now also triggers a subprocess call to
gstack-telemetry-log with the attack_attempt event type. The binary handles
tier gating internally (community → Supabase upload, anonymous → local
JSONL only, off → no-op), so security.ts doesn't need to re-check.
Binary resolution follows the skill preamble pattern — never relies on PATH,
which breaks in compiled-binary contexts:
1. ~/.claude/skills/gstack/bin/gstack-telemetry-log (global install)
2. .claude/skills/gstack/bin/gstack-telemetry-log (symlinked dev)
3. bin/gstack-telemetry-log (in-repo dev)
Fire-and-forget:
* spawn with stdio: 'ignore', detached: true, unref()
* .on('error') swallows failures
* Missing binary is non-fatal — local attempts.jsonl still gives audit trail
Never throws. Never blocks. Existing 37 security tests pass unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(ui): add security banner markup + styles (approved variant A)
HTML + CSS for the canary leak / ML block banner. Structure matches the
approved mockup from /plan-design-review 2026-04-19 (variant A — centered
alert-heavy):
* Red alert-circle SVG icon (no stock shield, intentional — matches the
"serious but not scary" tone the review chose)
* "Session terminated" Satoshi Bold 18px red headline
* "— prompt injection detected from {domain}" DM Sans zinc subtitle
* Expandable "What happened" chevron button (aria-expanded/aria-controls)
* Layer list rendered in JetBrains Mono with amber tabular-nums scores
* Close X in top-right, 28px hit area, focus-visible amber outline
Enter animation: slide-down 8px + fade, 250ms, cubic-bezier(0.16,1,0.3,1) —
matches DESIGN.md motion spec. Respects `role="alert"` + `aria-live="assertive"`
so screen readers announce on appearance. Escape-to-dismiss hook is in the
JS follow-up commit.
Design tokens all via CSS variables (--error, --amber-400, --amber-500,
--zinc-*, --font-display, --font-mono, --radius-*) — already established in
the stylesheet. No new color constants introduced.
JS wiring lands in the next commit so this diff stays focused on
presentation layer only.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(ui): wire security banner to security_event + interactivity
Adds showSecurityBanner() and hideSecurityBanner() plus the addChatEntry
routing for entry.type === 'security_event'. When the sidebar-agent emits
a security_event (canary leak or ML BLOCK), the banner renders with:
* Title ("Session terminated")
* Subtitle with {domain} if present, otherwise generic
* Expandable layer list — each row: SECURITY_LAYER_LABELS[layer] +
confidence.toFixed(2) in mono. Readable + auditable — user can see
which layer fired at what score
Interactivity, wired once on DOMContentLoaded:
* Close X → hideSecurityBanner()
* Expand/collapse "What happened" → toggles details + aria-expanded +
chevron rotation (200ms css transition already in place)
* Escape key dismisses while banner is visible (a11y)
No shield icon yet — that's a separate commit that will consume the
`security` field now returned by /health.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(ui): add security shield icon in sidepanel header (3 states)
Small "SEC" badge in the top-right of the sidepanel that reflects the
security module's current state. Three states drive color:
protected green — all layers ok (TestSavantAI + transcript + canary)
degraded amber — one+ ML layer offline but canary + arch controls active
inactive red — security module crashed, arch controls only
Consumes /health.security (surfaced in commit
|
|
|
|
7450b5160b
|
fix: security audit remediation — 12 fixes, 20 tests (v0.13.1.0) (#595)
* fix: remove auth token from /health, secure extension bootstrap (CRITICAL-02 + HIGH-03) - Remove token from /health response (was leaked to any localhost process) - Write .auth.json to extension dir for Manifest V3 bootstrap - sidebar-agent reads token from state file via BROWSE_STATE_FILE env var - Remove getToken handler from extension (token via health broadcast) - Extension loads token before first health poll to prevent race condition * fix: require auth on cookie-picker data routes (CRITICAL-01) - Add Bearer token auth gate on all /cookie-picker/* data/action routes - GET /cookie-picker HTML page stays unauthenticated (UI shell) - Token embedded in served HTML for picker's fetch calls - CORS preflight now allows Authorization header * fix: add state file TTL and plaintext cookie warning (HIGH-02) - Add savedAt timestamp to state save output - Warn on load if state file older than 7 days - Auto-delete stale state files (>7 days) on server startup - Warning about plaintext cookie storage in save message * fix: innerHTML XSS in extension content script and sidepanel (MEDIUM-01) - content.js: replace innerHTML with createElement/textContent for ref panel - sidepanel.js: escape entry.command with escapeHtml() in activity feed - Both found by security audit + Codex adversarial red team * fix: symlink bypass in validateReadPath (MEDIUM-02) - Always resolve to absolute path first (fixes relative path bypass) - Use realpathSync to follow symlinks before boundary check - Throw on non-ENOENT realpathSync failures (explicit over silent) - Resolve SAFE_DIRECTORIES through realpathSync (macOS /tmp → /private/tmp) - Resolve directory part for non-existent files (ENOENT with symlinked parent) * fix: freeze hook symlink bypass and prefix collision (MEDIUM-03) - Add POSIX-portable path resolution (cd + pwd -P, works on macOS) - Fix prefix collision: /project-evil no longer matches /project freeze dir - Use trailing slash in boundary check to require directory boundary * fix: shell script injection in gstack-config and telemetry (MEDIUM-04) - gstack-config: validate keys (alphanumeric+underscore only) - gstack-config: use grep -F (fixed string) instead of -E (regex) - gstack-config: escape sed special chars in values, drop newlines - gstack-telemetry-log: sanitize REPO_SLUG and BRANCH via json_safe() * test: 20 security tests for audit remediation - server-auth: verify token removed from /health, auth on /refs, /activity/* - cookie-picker: auth required on data routes, HTML page unauthenticated - path-validation: symlink bypass blocked, realpathSync failure throws - gstack-config: regex key rejected, sed special chars preserved - state-ttl: savedAt timestamp, 7-day TTL warning - telemetry: branch/repo with quotes don't corrupt JSON - adversarial: sidepanel escapes entry.command, freeze prefix collision * chore: bump version and changelog (v0.13.1.0) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: tone down changelog — defense in depth, not catastrophic bugs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |
|
|
|
b343ba2797
|
fix: community PRs + security hardening + E2E stability (v0.12.7.0) (#552)
* fix(security): skip hidden directories in skill template discovery
discoverTemplates() scans subdirectories for SKILL.md.tmpl files but
only skips node_modules, .git, and dist. Hidden directories like
.claude/, .agents/, and .codex/ (which contain symlinked skill
installs) were being scanned, allowing a malicious .tmpl in a
symlinked skill to inject into the generation pipeline.
Fix: add !d.name.startsWith('.') to the subdirs() filter. This skips
all dot-prefixed directories, matching the standard convention that
hidden dirs are not source code.
* fix(security): sanitize telemetry JSONL inputs against injection
SKILL, OUTCOME, SESSION_ID, SOURCE, and EVENT_TYPE values go directly
into printf %s for JSONL output. If any contain double quotes,
backslashes, or newlines, the JSON breaks — or worse, injects
arbitrary fields.
Fix: strip quotes, backslashes, and control characters from all
string fields before JSONL construction via json_safe() helper.
* fix(security): validate JSON input in gstack-review-log
gstack-review-log appends its argument directly to a JSONL file with
no validation. Malformed or crafted input could corrupt the review log
or inject arbitrary content.
Fix: validate input is parseable JSON via python3 before appending.
Reject with exit 1 and stderr message if invalid.
* fix: treat relative dot-paths as file paths in screenshot command
Closes #495
* fix: use host-specific co-author trailer in /ship and /document-release
Codex-generated skills hardcoded a Claude co-author trailer in commit
messages. Users running gstack under Codex pushed commits attributed
to the wrong AI assistant.
Add {{CO_AUTHOR_TRAILER}} resolver that emits the correct trailer
based on ctx.host:
- claude: Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- codex: Co-Authored-By: OpenAI Codex <noreply@openai.com>
Replace hardcoded trailers in ship/SKILL.md.tmpl and
document-release/SKILL.md.tmpl with the resolver placeholder.
Fixes #282. Fixes #383.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: auto-upgrade marker no longer masks newer remote versions
When a just-upgraded-from marker persists across sessions, the update
check would write UP_TO_DATE to cache and exit immediately — never
fetching the remote VERSION. Users silently miss updates that landed
after their last upgrade.
Remove the early exit and premature cache write so the script falls
through to the remote check after consuming the marker. This ensures
JUST_UPGRADED is still emitted for the preamble, while also detecting
any newer versions available upstream.
Fixes #515
* fix: decouple doc generation from binary compilation in build script
The build script chains gen:skill-docs and bun build --compile with &&,
so a doc generation failure (e.g. missing Codex host config, template
error) prevents the browse binary from being compiled. Users end up
with a broken install where setup reports the binary is missing.
Replace && with ; for the two gen:skill-docs steps so they run
independently of the compilation chain. Doc generation errors are still
visible in stderr, but no longer block binary compilation.
Fixes #482
* fix: extend security sanitization + add 10 tests for merged community PRs
- Extend json_safe() to ERROR_CLASS and FAILED_STEP fields
- Improve ERROR_MESSAGE escaping to handle backslashes and newlines
- Replace python3 with bun for JSON validation in gstack-review-log
- Add 7 telemetry injection prevention tests
- Add 2 review-log JSON validation tests
- Add 1 discover-skills hidden directory filtering test
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: stabilize flaky E2E tests (browse-basic, ship-base-branch, dashboard-via)
browse-basic: bump maxTurns 5→7 (agent reads PNG per SKILL.md instruction)
ship-base-branch: extract Step 0 only instead of full 1900-line ship/SKILL.md
dashboard-via: extract dashboard section only + increase timeout 90s→180s
Root cause: copying full SKILL.md files into test fixtures caused context bloat,
leading to timeouts and flaky turn limits. Extracting only the relevant section
cut dashboard-via from timing out at 240s to finishing in 38s.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs: add E2E fixture extraction rule to CLAUDE.md
Never copy full SKILL.md files into E2E test fixtures. Extract only
the section the test needs. Also: run targeted evals in foreground,
never pkill and restart mid-run.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: stabilize journey-think-bigger routing test
Use exact trigger phrases from plan-ceo-review skill description
("think bigger", "expand scope", "ambitious enough") instead of
the ambiguous "thinking too small". Reduce maxTurns 5→3 to cut
cost per attempt ($0.12 vs $0.25). Test remains periodic tier
since LLM routing is inherently non-deterministic.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* remove: delete journey-think-bigger routing test
Never passed reliably. Tests ambiguous routing ("think bigger" →
plan-ceo-review) but Claude legitimately answers directly instead
of invoking a skill. The other 10 journey tests cover routing
with clear, actionable signals.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: bump version and changelog (v0.12.7.0)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Arun Kumar Thiagarajan <arunkt.bm14@gmail.com>
Co-authored-by: bluzername <bluzer@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Greg Jackson <gregario@users.noreply.github.com>
|
|
|
|
8500136d15
|
feat: remove trigger guard + proactive opt-out prompt (#457)
* fix: telemetry source tagging + duration guards Add --source, --error-message, --failed-step flags to gstack-telemetry-log. Source tagging (live vs test via GSTACK_TELEMETRY_SOURCE env) prevents E2E tests from polluting production data. Duration guards cap unreasonable values (>24h or negative → null). Partial cherry-pick from garrytan/community-mode — non-breaking parts only. Skips install_fingerprint rename (needs schema migration). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: remove trigger guard + proactive opt-out prompt Remove "MANUAL TRIGGER ONLY" injection from all skill descriptions. This frees 59 chars per skill from the 1024-char Codex description budget and lets skills auto-fire based on semantic matching. Merge auto-fire control into the existing `proactive` setting — when false, Claude won't auto-invoke skills or suggest them. Users are prompted once about this preference (chains after the telemetry prompt, fires on second skill run). Also trims the root gstack description by removing the skill catalog (already in the body), saving ~500 chars. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: bump version and changelog (v0.11.16.0) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
|
|
|
2b85b1df46
|
fix: random UUID installation_id + verify-rls.sh edge cases (v0.11.16.1) (#462)
* fix: random UUID installation_id + gitignore supabase/.temp Replace SHA-256(hostname+user) with random UUID v4 stored in ~/.gstack/installation-id. Not derivable from public inputs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: random UUID installation_id + verify-rls.sh edge cases (v0.11.16.1) Replace SHA-256(hostname+user) with random UUID v4 stored in ~/.gstack/installation-id. Gitignore supabase/.temp/. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
|
|
|
3b22fc39e6
|
feat: opt-in usage telemetry + community intelligence platform (v0.8.6) (#210)
* feat: add gstack-telemetry-log and gstack-analytics scripts Local telemetry infrastructure for gstack usage tracking. gstack-telemetry-log appends JSONL events with skill name, duration, outcome, session ID, and platform info. Supports off/anonymous/community privacy tiers. gstack-analytics renders a personal usage dashboard from local data. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add telemetry preamble injection + opt-in prompt + epilogue Extends generatePreamble() with telemetry start block (config read, timer, session ID, .pending marker), opt-in prompt (gated by .telemetry-prompted), and epilogue instructions for Claude to log events after skill completion. Adds 5 telemetry tests. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: regenerate all SKILL.md files with telemetry blocks Automated regeneration from gen-skill-docs.ts changes. All skills now include telemetry start block, opt-in prompt, and epilogue. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add Supabase schema, edge functions, and SQL views Telemetry backend infrastructure: telemetry_events table with RLS (insert-only), installations table for retention tracking, update_checks for install pings. Edge functions for update-check (version + ping), telemetry-ingest (batch insert), and community-pulse (weekly active count). SQL views for crash clustering and skill co-occurrence sequences. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add telemetry-sync, community-dashboard, and integration tests gstack-telemetry-sync: fire-and-forget JSONL → Supabase sync with privacy tier field stripping, batch limits, and cursor tracking. gstack-community-dashboard: CLI tool querying Supabase for skill popularity, crash clusters, and version distribution. 19 integration tests covering all telemetry scripts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: session-specific .pending markers + crash_clusters view fix Addresses Codex review findings: - .pending race condition: use .pending-$SESSION_ID instead of shared .pending file to prevent concurrent session interference - crash_clusters view: add total_occurrences and anonymous_occurrences columns since anonymous tier has no installation_id - Added test: own session pending marker is not finalized Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: dual-attempt update check with Supabase install ping Fires a parallel background curl to Supabase during the slow-path version fetch. Logs upgrade_prompted event only on fresh fetches (not cached replays) to avoid overcounting. GitHub remains the primary version source — Supabase ping is fire-and-forget. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: integrate telemetry usage stats into /retro output Retro now reads ~/.gstack/analytics/skill-usage.jsonl and includes gstack usage metrics (skill run counts, top skills, success rate) in the weekly retrospective output. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: move 'Skill usage telemetry' to Completed in TODOS.md Implemented in this branch: local JSONL logging, opt-in prompt, privacy tiers, Supabase backend, community dashboard, /retro integration. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: wire Supabase credentials and expose tables via Data API Add supabase/config.sh with project URL and publishable key (safe to commit — RLS restricts to INSERT only). Update telemetry-sync, community-dashboard, and update-check to source the config and include proper auth headers for the Supabase REST API. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: add SELECT RLS policies to migration for community dashboard reads All telemetry data is anonymous (no PII), so public reads via the publishable key are safe. Needed for the community dashboard to query skill popularity, crash clusters, and version distribution. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: bump version and changelog (v0.8.6) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: analytics backward-compatible with old JSONL format Handle old-format events (no event_type field) alongside new format. Skip hook_fire events. Fix grep -c whitespace issues and unbound variable errors. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: map JSONL field names to Postgres columns in telemetry-sync Local JSONL uses short names (v, ts, sessions) but the Supabase table expects full names (schema_version, event_timestamp, concurrent_sessions). Add sed mapping during field stripping. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address Codex adversarial findings — cursor, opt-out, queries - Sync cursor now advances on HTTP 2xx (not grep for "inserted") - Update-check respects telemetry opt-out before pinging Supabase - Dashboard queries use correct view column names (total_occurrences) - Sync strips old-format "repo" field to prevent privacy leak Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: add Privacy & Telemetry section to README Transparent disclosure of what telemetry collects, what it never sends, how to opt out, and a link to the schema so users can verify. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |