Skip to content

fix(skills): harden auto-learn pipeline and enable skills guard scan - #89

Merged
jkyberneees merged 1 commit into
mainfrom
fix/skills-autolearn-security
Jul 22, 2026
Merged

fix(skills): harden auto-learn pipeline and enable skills guard scan#89
jkyberneees merged 1 commit into
mainfrom
fix/skills-autolearn-security

Conversation

@jkyberneees

Copy link
Copy Markdown
Contributor

Follow-up to the auto-learn security review. Makes the skill self-improvement system safe to use by default, with PiGuard as the semantic second opinion on top of structural fixes. All findings were verified against the code before fixing.

The problem (verified)

  1. "Skills scan disabled" meant zero scanning. The three guard call sites (ScanSuggestionBody, scanSkill, applyGuardToSkills) early-returned when the scope was off — and it defaulted off everywhere — so not even the free local rule scan ran. A learned skill body containing injection patterns saved and loaded unchecked.
  2. NeedsReview didn't gate trigger matching. It only pinned skills out of auto-load; lazy matchers had no provenance filter and fire on a single keyword hit. So a flagged/tainted skill was still injected into context on near-every turn (learned triggers derive from LLM/body keywords like run/test/build). The PiGuard flag was nearly toothless.
  3. Project-dir skills were fully trusted. ./.odek/skills/ was scanned first with priority, auto_load: true honored, no provenance — a cloned malicious repo could auto-inject a SKILL.md into the system prompt. Inconsistent with how ./odek.json is treated everywhere else.
  4. Taint coverage gaps. web_search/vision/delegate_tasks didn't taint the session, so skills learned from injected search results were auto-saved as "clean". Auto-load skill context and skill_load output were not wrapped as untrusted.

The fixes

  1. Local-scan floor — all three call sites now go through guard.ScanContentWithScope: local rule scan always, PiGuard when the scope is enabled.
  2. NeedsReview enforced at trigger time — lazy matchers are built from the non-NeedsReview set; flagged skills stay listable/promotable but cannot be trigger-injected until promoted.
  3. Project-dir skill distrust — forced NeedsReview + project source (same model as ./odek.json). odek skill promote now falls back to the project dir so these skills are promotable after review (requires --force since they carry a source — deliberate).
  4. Taint coverage + wrappingweb_search/vision/delegate_tasks now taint (shell deliberately excluded — it's the primary work tool; comment documents why). Auto-load skill context and skill_load output are wrapped with the nonce'd untrusted boundary.
  5. scan.skills defaults to enabledDefaultScanConfig, both docker configs, and docs/CONFIG.md. PiGuard now scans skill bodies at learn-loop save time (one call per saved skill, off the hot path) and at reload (one batch per skill-set change).

Verification

  • 12+ new/updated tests: local-floor flagging with scope off, matcher exclusion + promote-to-matchable, project-dir distrust (incl. the cached reload path), extended taint set, auto-load/skill_load wrapping, promote project-dir fallback.
  • go test ./... — all green; -race on internal/skills — green; repo-wide golangci-lint — 0 issues.
  • AGENTS.md and docs/SECURITY.md synced to the new behavior.

Operator note: existing project-dir skills will appear as NeedsReview after this change and won't trigger until odek skill promote <name> --force. Skills in ~/.odek/skills are unaffected.

Makes the skill auto-learn system safe to use by default:

1. Local-scan floor: ScanSuggestionBody, scanSkill, and applyGuardToSkills
   now always run the local rule scan via guard.ScanContentWithScope -
   previously 'skills scan disabled' (the default everywhere) meant zero
   scanning, so a learned skill body containing injection patterns saved
   and loaded unchecked.

2. NeedsReview is now enforced at trigger time: lazy matchers are built
   from the non-NeedsReview skill set, so flagged/tainted skills can no
   longer be trigger-injected on a single keyword match. Previously
   NeedsReview only pinned skills out of auto-load, leaving per-turn
   injection wide open.

3. Project-dir skills (./.odek/skills) are distrusted like ./odek.json:
   forced NeedsReview with a 'project' source, so a cloned repo can no
   longer auto-inject a SKILL.md. 'odek skill promote' now falls back to
   the project dir so these skills can be reviewed and promoted.

4. Taint coverage: web_search, vision, and delegate_tasks now taint the
   session (like browser/http_batch), so skills learned from such
   sessions are declined by auto-save. shell is deliberately excluded
   (primary work tool - tainting it would taint nearly every session).
   Auto-load skill context and skill_load tool output are now wrapped
   with the nonce'd untrusted-content boundary.

5. The 'skills' guard scan scope now defaults to enabled (config +
   docker configs + docs), adding the PiGuard semantic second opinion on
   top of the local floor at save and load time.

Tests: 12+ new/updated tests across skills, memory, guard, config, and
CLI packages; full suite, -race on skills, and repo-wide golangci-lint
all clean.
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
odek 918d76b Commit Preview URL

Branch Preview URL
Jul 22 2026, 10:44 AM

@jkyberneees
jkyberneees merged commit c3c3e42 into main Jul 22, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant