
Create Rule
- 44 installs
- 126 repo stars
- Updated August 4, 2026
- seqra/opentaint
Helps with ai & agent building tasks.
About
create-rule is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- create-rule
- AI & Agent Building
- AI-coding skill
Create Rule by the numbers
- 44 all-time installs (skills.sh)
- Ranked #7,794 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/seqra/opentaint --skill create-ruleAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 44 |
|---|---|
| repo stars | ★ 126 |
| Last updated | August 4, 2026 |
| Repository | seqra/opentaint ↗ |
What it does
Helps with ai & agent building tasks.
Files
Skill: Create Rule
Per package, author the new source/sink lib rules the requirements name, wire each to the generic Taint marker in a test join, and verify against the package's marker test projects until every sample passes
Two roles: the main one authors a package's lib rules (above); a fix narrows or broadens a created rule the main scan later flags. The cross-package security joins are written by assemble-lib-rules, not here
Inputs
From the caller; if omitted, fall back to the default. Ask only when a required input is missing and has no sensible default
- Requirements
<requirements>— the per-package lib unit naming the new sources/sinks (a tracking file), or for a fix the rule to change - Compiled test projects
<test-compiled>— the marker models to verify against. Default:.opentaint/test-compiled/<name>/sinksand.opentaint/test-compiled/<name>/sources(<name>= the package-kebab) - Test project
<test-project>— the sources tree; the test joins go in each side's<test-project>/<side>/test-rules(onlytest rule runloads them, never the main scan). Default:.opentaint/test-projects/<name> - Rules directory
<rules-dir>— where the lib rules are written. Default:.opentaint/rules - Tracking file
<tracking-file>— the lib unit file. Default:.opentaint/tracking/rules/lib/<name>.yaml - Approximation directories
<config-dir>/<approx-dir>(optional) — apply on a re-dispatch when the test project needs a library model that's now built. Default: none
Built-in rules are available at opentaint health --rules
Workflow
1. Check existing coverage
Browse builtin rules at opentaint health --rules for source/sink library rules to reference. A refs to a built-in source/sink is cheaper and more accurate than a new one
2. Wire sources and sinks
Prefer referencing built-in source/sink library rules; write a custom one only when no built-in fits. Derive each pattern from the requirements' fully-qualified names and annotations
Reference built-ins:
refs:
- rule: java/lib/generic/servlet-untrusted-data-source.yaml#java-servlet-untrusted-data-source
as: servlet-source
- rule: java/lib/spring/untrusted-data-source.yaml#spring-untrusted-data-source
as: spring-sourceCustom source library rule (<rules-dir>/java/lib/generic/my-source.yaml), if no built-in fits:
rules:
- id: my-custom-source
options:
lib: true
severity: NOTE
message: Custom untrusted data source
languages: [java]
patterns:
- pattern-either:
- patterns:
- pattern: |
$RETURNTYPE $METHOD(HttpServletRequest $UNTRUSTED, ...) { ... }
- metavariable-pattern:
metavariable: $METHOD
pattern-either:
- pattern: doGet
- pattern: doPostCustom sink library rule (<rules-dir>/java/lib/generic/my-sink.yaml):
rules:
- id: my-custom-sink
options:
lib: true
severity: NOTE
message: Custom dangerous operation
languages: [java]
mode: taint
pattern-sinks:
- patterns:
- pattern-either:
- pattern: (java.sql.Statement $S).executeQuery($UNTRUSTED)
- pattern: (java.sql.Statement $S).execute($UNTRUSTED)
- focus-metavariable: $UNTRUSTED3. Write the test joins (against the generic marker)
A lib rule emits nothing alone — to exercise it you need a join. Write one test join per sub-project into its test-rules/java/security/, wiring your new lib rules to the generic Taint marker. These live only in the test project (never <rules-dir>), so the main scan never loads them. Name each <name>-sinks / <name>-sources so the samples' value/id resolve:
sinks/→<name>-sinks: ref the generic source + every new sink lib rule, wiringsrc.$UNTRUSTED -> <sink>.$UNTRUSTEDfor eachsources/→<name>-sources: ref every new source lib rule + the generic sink, wiring<source>.$UNTRUSTED -> sink.$VALUEfor each
# .opentaint/test-projects/<name>/sinks/test-rules/java/security/<name>-sinks.yaml
rules:
- id: <name>-sinks
severity: ERROR
message: Tainted value reaches a sink under test
metadata:
cwe: CWE-000
short-description: test join for the package's sinks
languages: [java]
mode: join
join:
refs:
- rule: java/lib/test/generic-source.yaml#generic-taint-source
as: src
- rule: java/lib/<area>/my-new-sink.yaml#my-new-sink
as: sink
on:
- 'src.$UNTRUSTED -> sink.$UNTRUSTED'The marker rules resolve from the sub-project's test-rules root, your lib rules from <rules-dir> — pass both to test rule run. Metavariable names must match across refs and on
4. Test until success
Run the tests against each compiled sub-project, loading your lib rules (<rules-dir>) and the test joins + markers (<test-project>/<side>/test-rules); iterate until every sample passes:
opentaint test rule run <test-compiled>/sinks \
-o .opentaint/test-results/<name>/sinks \
--ruleset <rules-dir> --ruleset <test-project>/sinks/test-rulestest rule run auto-loads the built-in rules, so pass only your custom rulesets — a literal builtin here would be treated as a path. When the caller passed <config-dir> / <approx-dir>, append --passthrough-approximations <config-dir> / --dataflow-approximations <approx-dir> — without them a library method the test flow relies on drops taint and the positive can't pass. Read .opentaint/test-results/<name>/sinks/test-result.json:
falseNegative(positive didn't trigger) → patterns too narrow; broadenpattern-either, check metavariable names match across branches and betweenrefsandonfalsePositive(negative triggered) → patterns too broad; addpattern-not,pattern-not-inside,pattern-sanitizers, ormetavariable-regexskipped/disabled→ the rule wasn't exercised; fix the annotationvalue/id, or enable the rule
5. When a positive won't pass after a couple of fixes
A @PositiveRuleSample that won't trigger after ~2 fix attempts may have a cause no rule edit can fix — a library method on its flow killing taint. Before escalating, scan that sub-project's model with --track-external-methods (add the marker test-rules so the join resolves):
opentaint scan --project-model <test-compiled>/sinks \
-o .opentaint/test-results/<name>/sinks/diag.sarif \
--ruleset builtin --ruleset <rules-dir> --ruleset <test-project>/sinks/test-rules \
--track-external-methodsRead dropped-external-methods.yaml next to it; either way leave tests_passing: pending:
- a dropped method on the failing sample's source→sink path → that's the cause, not the rule: report which methods need a model, to be approximated before you're re-dispatched
- nothing dropped and no clear rule cause → report non-convergence for escalation, rather than editing blindly
Output
- The new lib rule file(s) under
<rules-dir>, and the test join(s) under each test project'stest-rules/ - Tracking updated: the lib rules'
rule_ids/artifact,stages.tests_passing(per Tracking) - Report the lib rule ids, a one-line test summary per sub-project, and the exact
test rule runcommand used - If blocked (step 5): leave
tests_passing: pendingand report the cause instead
Tracking
In <tracking-file>, once the lib rules exist and every sub-project's samples pass:
artifact: .opentaint/rules/java/lib/generic/my-sink.yaml
stages:
tests_passing: doneConstraints
- Library rules MUST have
options.lib: trueandseverity: NOTE - Security rules (the joins) MUST have
metadata.cweandmetadata.short-description - Source/sink metavariable names must match across
refsandonclauses, or the join won't connect; bind the tainted value as$UNTRUSTEDin every lib source/sink rule, so the security joins assemble-lib-rules writes later reference one consistent name - The
rule:path inrefsis relative to the ruleset root — a marker ref resolves under the test project'stest-rules, a lib ref under<rules-dir> - Rule IDs must be globally unique
- For simple structural patterns (no dataflow), omit
mode:(uses default mode) - Custom library rules go under
<rules-dir>/java/lib/generic/or<rules-dir>/java/lib/spring/(for Spring-specific), mirroring the built-in layout — never directly underjava/lib/; the test joins go in the test project'stest-rules/java/security/, never<rules-dir>
Gotchas
- A wrong argument position in
(..., $UNTRUSTED, ...)focuses the wrong parameter — pointfocus-metavariableat the tainted one - Refine the rule, never the test project — don't edit or weaken samples here; if one is wrong, hand it back upstream
- A positive that won't pass because a library method drops taint is not a rule bug — don't broaden the rule to force it; surface it for approximation (step 5)
- The
#comments in the examples here are for you — don't copy them into the rule files you write; keep produced YAML comment-free - An implicit-receiver pattern
this.method(...)is unsupported ("Failed to transform pattern: ThisExpr") — match the unqualified call as a baremethod($X)pattern instead - A structural (no-source) sink and a taint-flow sink can't share one join id — the engine forbids one id being both; if a class needs both, split them into separate rules/joins
- Don't unpack or grep the analyzer JAR for built-in rules — its internals aren't a stable API; read the YAMLs from
opentaint health --rules