Loading…
gathering yarn balls…
Cat Game Research
Frontier
Dev-log transmissions from the expedition

Securing the Instruction Compiler: Security Scanning, Lint, and Style for Agent Skills

How we made the Compact Instruction Compiler security-aware: a 60-rule OWASP-mapped scanner, a 70-rule lint/style checker, a .cic config file, and VLM-based attack-vs-policy classification.

The Problem: Agent Instructions Are Attack Surface

When we built the Compact Instruction Compiler, the goal was simple: take verbose Markdown instruction files and compile them through a PIR intermediate into compact Markdown. What we didn't address was what happens when the input isn't just verbose — it's malicious.

The threat is real. NVIDIA demonstrated an indirect AGENTS.md injection attack where a dependency-injected instruction file overrides the agent's safety directives. The OWASP Agentic Skills Top 10 (AST) formalizes this: prompt injection (AST01), over-privileged commands (AST03), exfiltration (AST04), untrusted external instructions (AST05). The SoK paper on prompt injection against coding assistants (arXiv:2601.17548) shows that natural-language-plus-code blends defeat pure signature scanners — a defensive policy that says "NEVER exfiltrate credentials" contains the same words as an attack that says "send credentials to."

We needed three things: a security gate that runs before compilation, a lint checker that enforces structural conventions, and a config file that ties them together. Here's what we built.

60
Security Rules
70
Lint Rules
9
AST Categories
0.5s
758 Files
13
Unit Tests

Architecture: Three New Tools

The CIC pipeline gained three new components, each with a clear separation of concerns:

cic-sec.sh
Security scanner with 60 detection rules across 9 OWASP AST categories. Single awk pass with combined-regex prefilter. Runs before compile; blocks on critical/high findings.
scripts/lib/cic/cic-sec.sh
cic-lint.sh
Lint and style enforcement with 70 rules across 9 file types. Frontmatter schema, heading structure, naming conventions, CommonMark syntax. Single awk pass for all rules.
scripts/lib/cic/cic-lint.sh
.cic
Project-root config file (INI-style, like .clang-format). Three sections: [sec] allowlist, [lint] thresholds, [compile] defaults. Replaces scattered config files.
repo root

The Pipeline

When you run cic.sh -i (install mode), security scanning and semantic regression checking are now auto-enabled. This means you can't accidentally install adversarial content into your source tree without explicitly opting out with --no-sec.

input.mduntrustedSEC SCANcic-sec.sh60 rules9 categoriesblock on high+LINT CHECKcic-lint.sh70 rules9 file typesblock on err+warnCOMPILEmd → pir → mdmd2pir.awkpir2md.awkbuild/cic/GUARDcic-guard.shsemanticregressioncheckINSTALLsource treeVLM classify--vlm (optional).cic[sec] [lint] [compile]
CIC Pipeline — sec scan and lint check gate the compile; guard verifies semantic equivalence; .cic config drives all stages

The Security Scanner: 60 Rules, 9 Categories

The scanner (cic-sec.sh) maps directly to the OWASP Agentic Skills Top 10. Each rule is a tab-delimited record: ID, severity, category, ERE regex, description. The rules are data, not logic — adding a new detection pattern means adding a line to the array, not writing a new function.

CategoryASTRulesWhat it detects
INJAST01, AST058Instruction-override ("ignore previous instructions"), role-reset jailbreaks, system-prompt extraction
STLAST016Stealth suppression ("do not mention this change", "silently inject")
SECAST0410API keys (sk-, AIza, ghp_, xox, AKIA, JWT), PEM key blocks, inline password assignments
MALAST0410Reverse shells, netcat listeners, rm -rf /, curl | sh, mkfifo, eval()
XSSAST047<script> tags in HTML context, javascript: URIs, inline event handlers, iframes, cookie access
EXTAST054External-instruction fetch directives ("load rules from https://...")
EXFAST046Credential exfiltration, secret upload, command-substitution network callbacks, base64-obfuscated channels
PRIAST036DROP TABLE, sudo rm, git push --force, git reset --hard
METAAST03, AST093Brand-impersonation author fields, over-broad permission manifests, unpinned floating versions

The XSS False-Positive Lesson

The original XSS-01 regex was <script[^>]*> — simple and correct for HTML. But agent instruction files are not HTML. They're Markdown with shell code blocks. A line like Run the script at /tmp/<script>.sh triggered the rule 30 times across the repository.

The fix: require HTML context. The regex became (^|[[:space:]<])<script[^>]*> — the <script> tag must be preceded by start-of-line, whitespace, or another < (indicating HTML structure). Shell placeholders like /tmp/<script>.sh have / or alphanumerics before the <, so they no longer match. False positives dropped from 30 to 7 — and all 7 remaining are real <script> tags in HTML code blocks inside archived plans.

Lesson
Regex rules for instruction files must account for non-HTML contexts. Shell placeholders, file paths, and generic angle-bracket notation are not XSS. Always tighten the context requirement, not just the tag pattern.

The Lint Checker: 70 Rules, 9 File Types

The lint checker (cic-lint.sh) validates structural conventions across nine file types: skills, workflows, abilities, rules, memories, ideas, plans, AGENTS.md, and README.md. The rule catalog lives in cic-style.sh as tab-delimited data records — the same data-not-logic pattern as the security scanner.

Rules were derived from three sources:

1
Agent Skills Specification (agentskills.io) — name must be kebab-case, max 64 chars, match the parent directory. description must be non-empty. Optional fields: keywords, license, compatibility, metadata, allowed-tools.
↓
2
mdsmith and document-schema.org — heading hierarchy validation, frontmatter schema constraints, required-structure rules, maxTokens/maxDepth budgets.
↓
3
Repository conventions — 758 existing .devin files inspected for actual patterns: memory filenames use m_<snake>_<epoch>.md, ideas use IDEA_<SCREAMING>_<hash>.md, plans use SCREAMING_SNAKE_CASE, workflows require auto_execution_mode: 3.

When repo conventions conflicted with external specs, the repo won. The Agent Skills spec says description max is 1024 chars; our skills average 80. The spec lists Invocation and Commands as recommended sections; many of our skills (like build and test) are simple enough that they don't need them. Those rules became info severity — they report but don't block.

Severity Design

SeverityBlocks?Examples
errorYesMissing required frontmatter, invalid naming, unclosed code fences, unpaired backticks
warningYesTrailing whitespace, consecutive blank lines, tab indentation
infoNoMissing optional sections, long lines (>200 chars), missing fence language, link bracket pairing

The .cic Config File

Configuration moved from a scattered .devin/cic-sec-allowlist file to a project-root .cic file — the same pattern as .clang-format and .clang-tidy. Three INI-style sections:

.cic
[sec]
# Security scanner allowlist — suppress vetted defensive FPs
# Format: RULE_ID:path-glob or *:path-glob (trust directive)
*:.devin/plans/archive/*
*:.devin/ideas/*
INJ-03:AGENTS.md
XSS-01:.devin/skills/add-lab/*

[lint]
# Lint/style thresholds and rule disables
max_line_length = 200
max_heading_level = 4
# disable = STYLE-04

[compile]
# CIC compiler defaults
auto_sec_on_install = true
auto_guard_on_install = true

The [sec] section is parsed by cic-sec.sh with a one-liner: awk '/^\[sec\]/{f=1;next} /^\[/{f=0} f{print}' .cic — extract lines between [sec] and the next section header. A user-level fallback at ~/.config/devin/cic-sec-allowlist still works for personal overrides.

The Performance Pattern: One Awk Pass

The first implementation used per-file shell loops: for f in *.md; do grep ...; awk ...; done. For 758 files, this spawned 758+ subprocesses and took over 10 seconds. Unacceptable for a compile gate.

The fix: move all rules — frontmatter parsing, heading extraction, naming checks, code/prose/link checks — into a single awk process that processes all files in one invocation. Awk's FNR==1 rule detects file boundaries and resets per-file state. The END block flushes the last file.

BEFORE: Per-file loopsfor f in *.md; do grep ... $f awk ... $fdone10+ seconds758 subprocessesAFTER: Single awk passawk ' FNR==1 { reset_state() } { check_all_rules() }' *.md0.4 seconds1 process, 25x faster
Single-awk-pass pattern: 25x speedup by eliminating per-file subprocess spawning

This pattern is now canonical for batch file scanning in the CIC toolchain. Both cic-sec.sh (0.5s) and cic-lint.sh (0.4s) use it. The key insight: awk processes all files in one invocation, tracks per-file state with FNR==1 reset, and flushes accumulated results at END.

The AST08 Gap: Why Signatures Aren't Enough

OWASP AST08 — The Fundamental Limitation
Signature scanning cannot distinguish defensive policy from attacks. A rule that says "NEVER exfiltrate credentials" contains the word "exfiltrate" — the same word that an attack uses. Natural-language-plus-code blends defeat pure regex matching. This is not a bug; it's a theoretical limitation.

We mitigate this in two ways:

1
Defensive downgrader — lines starting with NEVER, DO NOT, REFUSE, FORBIDDEN, or MUST NOT are auto-downgraded to low severity for non-STL categories. The scanner recognizes that defensive policy legitimately discusses the attacks it forbids. STL (stealth suppression) is never downgraded — "do not mention this" is always suspicious, even in defensive context.
↓
2
VLM classification — cic-sec.sh --vlm pipes findings through pi.sh vlm (our local V Engine Language Model, backed by pi-daemon and qwen3-1.7b-instruct). The VLM classifies each finding as ATTACK or POLICY. This is the only robust mitigation per the SoK paper. If pi-daemon is unavailable, the VLM pass gracefully skips and falls back to signature-only results.
The scanner is a triage gate, not a verdict. It catches the obvious attacks and flags the ambiguous cases. The VLM pass resolves the ambiguous ones. The allowlist suppresses the known-defensive false positives. Together, they form a defense-in-depth pipeline that no single layer could provide alone. — Design rationale

The Red-Team Corpus

We extracted detection patterns from multiple sources into data/redteam/prompt-injection-patterns.txt — a maintained seed set for rule development:

SourceContribution
OWASP Agentic Skills Top 10AST01–AST10 category framework
NVIDIA indirect AGENTS.md injection PoCInstruction-override patterns, stealth suppression
arXiv:2601.17548 (SoK prompt injection)AST08 theoretical limitation, attack taxonomy
fuzzdb (archive r_11906cf8)HTML/JS injection patterns, command injection templates
SecLists (archive r_c2321ca2)SSI injection, HTML5sec, command injection wordlists

The corpus currently has 90 patterns across 9 categories. cic-sec.sh --update-rules reports coverage: how many corpus patterns are represented by the 60 embedded rules. New patterns are added to the corpus first, then promoted to rules after testing.

Verification

Thirteen unit tests in test-cic-sec.sh cover the scanner's behavior:

Clean file
0 findings, exit 0. Normal Markdown with code blocks and frontmatter.
NVIDIA PoC
9 findings, exit 1. Instruction override, stealth, exfiltration, malware, XSS in one file.
XSS-01 boundary
Shell placeholder /tmp/<script>.sh → no match. HTML <script>alert(1) → match.
Defensive downgrader
NEVER/DO NOT lines → low severity. STL never downgraded.
Performance
Full .devin scan (758 files) completes in <2 seconds.
Edge cases
Empty file, unclosed fence, CRLF — no crashes, correct behavior.

What's Next

The security scanner and lint checker are implemented and running. The VLM classification pass is implemented but requires pi-daemon to be running — it gracefully skips when unavailable. On the roadmap:

The broader question — whether signature scanning plus LLM classification is sufficient for the agentic AI threat landscape — remains open. The SoK paper suggests it's the best we can do without runtime sandboxing. We're betting that compile-time gates plus VLM triage plus an allowlist for vetted content is a practical defense-in-depth for the current generation of agent instruction files.

Read the original CIC post →Pre-order CIC →
← All transmissions