
Discover Attack Surface
- 45 installs
- 126 repo stars
- Updated August 4, 2026
- seqra/opentaint
Helps with ai & agent building tasks.
About
discover-attack-surface is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- discover-attack-surface
- AI & Agent Building
- AI-coding skill
Discover Attack Surface by the numbers
- 45 all-time installs (skills.sh)
- +1 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #7,749 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 discover-attack-surfaceAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 45 |
|---|---|
| repo stars | ★ 126 |
| Last updated | August 4, 2026 |
| Repository | seqra/opentaint ↗ |
What it does
Helps with ai & agent building tasks.
Files
Skill: Discover Attack Surface
Take one library the triage flagged, settle what the built-in rules already cover for the package members this project uses, and write that project-used rule plan — the untrusted-data sources and dangerous sinks actually relevant to this project — for the next phase to build
Inputs
From the caller; if omitted, fall back to the default. Ask only when a required input is missing and has no sensible default
- Package
<package>— the flagged library to drill (apendingentry incoverage.yaml) - Dependency jars
<deps-dir>— the project's resolved dependency jars, one per library. Default:.opentaint/project/dependencies - Project model
<model-dir>— the built model. Default:.opentaint/project - Tracking directory
<tracking-dir>— where the coverage record and the per-package lib units live. Default:.opentaint/tracking
Workflow
1. Settle built-in coverage first
Before planning anything, see what the built-ins already match for this package's project-used members — read the lib rules (opentaint health --rules) plus .opentaint/rules. Decide one of:
- full — the built-ins already match the project-used package sources/sinks → write no lib unit, flip the
coverage.yamlentry todonewith abuiltin_coverage: fullnote, and stop. Don't drill further - partial — built-ins match some project-used methods/overloads/classes but miss others → plan only the missing used members (
coverage: expand, ref the built-in for the rest) - none — plan the package's project-used surface from scratch
2. Scope project-used sources and sinks
Find the package's jar in <deps-dir> only to confirm the dependency identity and inspect signatures/docs for members already in scope (match the artifact from the dependency GAV; unzip -l <jar> | grep <package-as-path> confirms it owns the package). To get the bytecode-derived list of package methods the project statically references, run this skill's bundled scripts/package-usages.sh <model-dir> <package> (Windows: scripts/package-usages.ps1; the scripts live in the skill directory, not the project) and save its output to <tracking-dir>/usage/<package-kebab>.yaml (create usage/ if needed). It reads moduleClasses/packages: from project.yaml and disassembles the project's own compiled classes only — a model's moduleClasses can mix project + dependency jars/dirs, so when the modules carry a packages: list only classes under those roots are scanned, otherwise moduleClasses is already project-only — then prints the deduped // Method/// InterfaceMethod call sites whose owner is in <package>.
This catches only bytecode invocations, so it misses members reached through annotations, class literals, casts, reflection, dynamic proxies, framework/container dispatch, config strings, or generated code absent from the model. Treat the output as the main used-in-project scope, then inspect app source, dependency API/source, and framework configuration only to classify those used members and to add indirectly reached members the bytecode list cannot show. Do not enumerate the whole package API. Never disassemble the analyzer jar — only the project's own classes
- sources — the exact place untrusted data first enters from a boundary (network, persistence, serialization, messaging, execution): a method that returns attacker-controlled data — HTTP/RPC request data, a message-broker payload. NOT a method that merely passes data it was handed along — that's a propagator the engine already handles, not a source. General, not class-tagged
- sinks — dangerous operations (query construction, command/file/path ops, deserialization, template/EL, LDAP/JNDI, reflection); tag each with its vuln class (
ssrf,sqli,path-traversal, …)
Verify each is real before recording: a source genuinely attacker-controlled, a sink genuinely dangerous with tainted input. Don't trace a flow between them — the analyzer pairs them at scan time
3. Write the package's rule plan
Write <tracking-dir>/rules/lib/<package-kebab>.yaml — only the project-used new sources and sinks, grouped by vuln_class, the dependency GAV, stages.description: done, and each coverage: new or expand. Then flip the package's coverage.yaml entry to status: done. <package-kebab> is the dotted package with . → -; the package: field keeps the real dotted name
Output
- A
<tracking-dir>/rules/lib/<package-kebab>.yamlrule plan for project-used members only (or, forfullcoverage, none — just the coverage note) - A
<tracking-dir>/usage/<package-kebab>.yamlpackage usage snapshot frompackage-usages.sh - The package's
coverage.yamlentry setstatus: donewith a one-linenotes - A brief summary to the caller: the sources and sinks planned (one line each, marked
new/expand). The unit holds the detail — don't paste it back
Tracking
<tracking-dir>/coverage.yaml — flip this package's entry when done:
- package: org.springframework.web.reactive.function.client
status: done
notes: WebClient request methods — SSRF sink; built-ins cover get(), expand with post()/put(); no new source<tracking-dir>/usage/<package-kebab>.yaml — temporary-but-persisted project-used scope. Keep it next to the rule plans so resumed agents can reuse it instead of rerunning extraction:
functions:
- function: "org.springframework.web.reactive.function.client.WebClient#get()Lorg/springframework/web/reactive/function/client/WebClient$RequestHeadersUriSpec;"
classes:
- class: "org.springframework.web.reactive.function.client.WebClient"<tracking-dir>/rules/lib/<package-kebab>.yaml — the rule plan; fill only the discovery-stage fields (create-test-project and create-rule fill the rest):
package: org.springframework.web.reactive.function.client
dependencies:
- org.springframework:spring-webflux:6.1.0
builtin_coverage: partial # partial | none
sources: # general, not class-tagged
- idea: ServerRequest body/params/headers — untrusted request data
coverage: new # new | expand
builtin: null
rule_id: null
sinks: # grouped by vuln class
- vuln_class: ssrf
idea: WebClient.get/post/put().uri($UNTRUSTED)
coverage: expand
builtin: java/lib/generic/ssrf-sinks.yaml#java-ssrf-sink
rule_id: null
stages:
description: done
test_project: pending
tests_passing: pending
notes: >
free-formEngine notes
- Spring projects: the analyzer auto-discovers Spring endpoints, so
networkinbound sources are largely ones the built-ins already see — focus on the sinks - Generic projects: the analyzer treats all public/protected methods of public classes as entry points
- Stored / second-order injection (data persisted then read back) is modeled by the engine on its own — don't plan a source for the read-back or a propagator for the store→read path
Gotchas
- Plan, don't write — record source/sink ideas only; the lib rules are written and tested in the next phase
- Don't re-declare a source or sink a built-in already matches —
coverage: expandwith only the missing used methods, or fold it intofullcoverage - Don't add unused package APIs just because they look security-relevant — this phase scopes rules to what the project uses or reaches indirectly
param(
[Parameter(Mandatory)][string]$ModelDir,
[Parameter(Mandatory)][string]$Package
)
$pp = $Package -replace '\.','/'
$yaml = Get-Content (Join-Path $ModelDir 'project.yaml')
function Get-YamlList([string]$key) {
$f = $false
foreach ($l in $yaml) {
if ($l -match "^\s*${key}:\s*$") { $f = $true; continue }
if ($f) {
if ($l -match '^\s*-\s+(.+?)\s*$' -and $l -notmatch ':') { $Matches[1] }
elseif ($l -match ':') { $f = $false }
}
}
}
$roots = (Get-YamlList 'packages' | ForEach-Object { [regex]::Escape(($_ -replace '\.','/')) }) -join '|'
$out = foreach ($e in (Get-YamlList 'moduleClasses')) {
$p = Join-Path $ModelDir $e
if (Test-Path -LiteralPath $p -PathType Container) {
$base = (Resolve-Path -LiteralPath $p).Path
$names = Get-ChildItem -LiteralPath $p -Recurse -Filter *.class |
ForEach-Object { ($_.FullName.Substring($base.Length).TrimStart('\','/') -replace '\.class$','') -replace '[\\/]','.' }
} else {
$names = & jar tf $p | Where-Object { $_ -match '\.class$' } |
ForEach-Object { ($_ -replace '\.class$','') -replace '/','.' }
}
if ($roots) { $names = $names | Where-Object { ($_ -replace '\.','/') -match "^($roots)/" } }
if ($names) {
$batch = [System.Collections.Generic.List[string]]::new(); $len = 0
foreach ($n in $names) {
$batch.Add($n); $len += $n.Length + 1
if ($len -ge 25000) { & javap -c -p -classpath $p @($batch) 2>$null; $batch.Clear(); $len = 0 }
}
if ($batch.Count) { & javap -c -p -classpath $p @($batch) 2>$null }
}
}
$out |
Select-String -Pattern ("// (Interface)?Method " + [regex]::Escape($pp) + "/\S+") -AllMatches |
ForEach-Object { $_.Matches } | ForEach-Object { $_.Value } |
Sort-Object -Unique
#!/usr/bin/env bash
MODEL=$1; PKG=$2
[ -n "$MODEL" ] && [ -n "$PKG" ] || { echo "usage: package-usages.sh <model-dir> <package>" >&2; exit 2; }
pp=${PKG//.//}
ylist(){ awk -v k="$1" '$0~"^[[:space:]]*"k":[[:space:]]*$"{f=1;next} f&&/^[[:space:]]*-[[:space:]]/&&$0!~/:/{sub(/^[^-]*-[[:space:]]*/,"");print;next} f&&/:/{f=0}' "$MODEL/project.yaml"; }
roots=$(ylist packages | tr . / | paste -sd'|' -)
ylist moduleClasses | while IFS= read -r e; do
p="$MODEL/$e"
{ if [ -d "$p" ]; then (cd "$p" && find . -name '*.class' | sed 's#^\./##'); else jar tf "$p" | grep '\.class$'; fi; } \
| { [ -n "$roots" ] && grep -E "^($roots)/" || cat; } \
| sed 's#\.class$##; s#/#.#g' | xargs -r javap -c -p -classpath "$p" 2>/dev/null
done | grep -oE '// (Interface)?Method '"$pp"'/[^ ]+' | sort -u