Skip to content

Commit 84544f4

Browse files
committed
feat: initial version
1 parent 2473cf2 commit 84544f4

File tree

4 files changed

+266
-0
lines changed

4 files changed

+266
-0
lines changed

.github/workflows/lint.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# This workflow will check our code for having a proper format, as well as the commit message to meet the expected ones
2+
3+
name: Lint
4+
5+
on:
6+
push:
7+
branches: ["main"]
8+
pull_request:
9+
branches: ["main"]
10+
11+
jobs:
12+
lint-commit:
13+
runs-on: ubuntu-latest
14+
name: "Lint commit message"
15+
container:
16+
image: commitizen/commitizen:4.8.3@sha256:08a078c52b368f85f34257a66e10645ee74d8cbe9b471930b80b2b4e95a9bd4a
17+
steps:
18+
- name: Check out
19+
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
20+
- name: Check commit message
21+
run: |
22+
git config --global --add safe.directory .
23+
cz check --rev-range HEAD

.github/workflows/test-action.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
name: Test Twyn Action
2+
3+
on:
4+
pull_request:
5+
branches: [main]
6+
push:
7+
branches: [main]
8+
workflow_dispatch:
9+
10+
jobs:
11+
test-action:
12+
runs-on: ubuntu-latest
13+
name: Test GH action
14+
steps:
15+
- name: Checkout
16+
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
17+
18+
- name: Test Twyn Action
19+
uses: ./
20+
with:
21+
vv: true
22+
publish: true
23+
github-token: ${{ secrets.GITHUB_TOKEN }}

action.yml

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
name: "Twyn action"
2+
description: "Security tool against dependency typosquatting attacks"
3+
author: "Elements Interactive"
4+
5+
branding:
6+
icon: "search"
7+
color: "blue"
8+
9+
inputs:
10+
github-token:
11+
description: "Token needed to publish results to the PR."
12+
required: false
13+
14+
config:
15+
description: "Path to the config file"
16+
required: false
17+
18+
dependency-file:
19+
description: "Dependency file(s) to analyze. Comma-separated if multiple. By default, twyn will search in the current directory for supported files, but this option will override that behavior."
20+
required: false
21+
22+
selector-method:
23+
description: "Which method twyn should use to select possible typosquats. 'first-letter' only compares dependencies that share the first letter, 'nearby-letter' compares against dependencies whose first letter is nearby in an English keyboard. 'all' compares the given dependencies against all of those in the reference."
24+
required: false
25+
26+
no-track:
27+
description: "Do not show the progress bar while processing packages"
28+
required: false
29+
default: "false"
30+
31+
json:
32+
description: "Display the results in json format. It implies no-track."
33+
required: false
34+
default: "false"
35+
36+
table:
37+
description: "Display the results in a table format. It implies no-track."
38+
required: false
39+
default: "false"
40+
41+
recursive:
42+
description: "Recursively look for files when trying to locate them automatically. Ignored if dependency-file is given."
43+
required: false
44+
default: "false"
45+
46+
pypi-source:
47+
description: "Alternative PyPI source URL to use for fetching trusted packages"
48+
required: false
49+
50+
npm-source:
51+
description: "Alternative npm source URL to use for fetching trusted packages"
52+
required: false
53+
54+
v:
55+
description: "Enable verbose output (-v)"
56+
required: false
57+
default: "false"
58+
59+
vv:
60+
description: "Enable extra verbose output (-vv)"
61+
required: false
62+
default: "false"
63+
64+
version:
65+
description: "Twyn version (latest, v1.0.0, etc.)"
66+
required: false
67+
default: "latest"
68+
69+
publish:
70+
description: "Whether to publish the twyn results as PR comments (requires table format)"
71+
required: false
72+
default: "false"
73+
74+
runs:
75+
using: "composite"
76+
steps:
77+
- name: Run Twyn Security Check
78+
shell: bash
79+
run: |
80+
# Build arguments as an array for safety (avoids word-splitting issues)
81+
ARGS=()
82+
83+
# Optional config file
84+
if [ -n "${{ inputs.config }}" ]; then
85+
ARGS+=(--config "${{ inputs.config }}")
86+
fi
87+
88+
# Dependency files (multiple allowed)
89+
if [ -n "${{ inputs.dependency-file }}" ]; then
90+
IFS=',' read -ra DEPENDENCY_FILES <<< "${{ inputs.dependency-file }}"
91+
for file in "${DEPENDENCY_FILES[@]}"; do
92+
if [ -n "$file" ]; then
93+
ARGS+=(--dependency-file "$file")
94+
fi
95+
done
96+
fi
97+
98+
# Dependencies (multiple allowed)
99+
if [ -n "${{ inputs.dependency }}" ]; then
100+
IFS=',' read -ra DEPENDENCIES <<< "${{ inputs.dependency }}"
101+
for dep in "${DEPENDENCIES[@]}"; do
102+
if [ -n "$dep" ]; then
103+
ARGS+=(--dependency "$dep")
104+
fi
105+
done
106+
fi
107+
108+
# Selector method
109+
if [ -n "${{ inputs.selector-method }}" ]; then
110+
ARGS+=(--selector-method "${{ inputs.selector-method }}")
111+
fi
112+
113+
# Package ecosystem
114+
if [ -n "${{ inputs.package-ecosystem }}" ]; then
115+
ARGS+=(--package-ecosystem "${{ inputs.package-ecosystem }}")
116+
fi
117+
118+
# Boolean flags
119+
if [ "${{ inputs.no-cache }}" = "true" ]; then
120+
ARGS+=(--no-cache)
121+
fi
122+
123+
if [ "${{ inputs.no-track }}" = "true" ]; then
124+
ARGS+=(--no-track)
125+
fi
126+
127+
if [ "${{ inputs.json }}" = "true" ]; then
128+
ARGS+=(--json)
129+
fi
130+
131+
# Force table format when publishing
132+
if [ "${{ inputs.publish }}" = "true" ] || [ "${{ inputs.table }}" = "true" ]; then
133+
ARGS+=(--table)
134+
fi
135+
136+
if [ "${{ inputs.recursive }}" = "true" ]; then
137+
ARGS+=(--recursive)
138+
fi
139+
140+
# Source URLs
141+
if [ -n "${{ inputs.pypi-source }}" ]; then
142+
ARGS+=(--pypi-source "${{ inputs.pypi-source }}")
143+
fi
144+
145+
if [ -n "${{ inputs.npm-source }}" ]; then
146+
ARGS+=(--npm-source "${{ inputs.npm-source }}")
147+
fi
148+
149+
# Verbose mode
150+
if [ "${{ inputs.verbose }}" = "true" ]; then
151+
ARGS+=(-vv)
152+
fi
153+
154+
echo $ARGS
155+
156+
# Run twyn using Docker and capture output and exit code
157+
# Use 'set +e' to prevent script from exiting on non-zero exit codes
158+
set +e
159+
TWYN_OUTPUT=$(docker run --rm \
160+
-v "${{ github.workspace }}:/workspace" \
161+
-w /workspace \
162+
elementsinteractive/twyn:${{ inputs.version }} run \
163+
"${ARGS[@]}" 2>&1)
164+
TWYN_EXIT_CODE=$?
165+
set -e
166+
167+
# Also display output in action logs
168+
echo "$TWYN_OUTPUT"
169+
170+
# Create PR comment with twyn results
171+
if [ "${{ inputs.publish }}" = "true" ]; then
172+
173+
# Check if github-token is provided
174+
if [ -z "${{ inputs.github-token }}" ]; then
175+
echo "❌ Error: github-token is required when publish is enabled. Skipping..."
176+
177+
else
178+
179+
cat > comment.md << EOF
180+
181+
\`\`\`
182+
$TWYN_OUTPUT
183+
\`\`\`
184+
185+
EOF
186+
187+
curl -X POST \
188+
-H "Authorization: token ${{ inputs.github-token}}" \
189+
-H "Accept: application/vnd.github.v3+json" \
190+
-d "$(cat comment.md | jq -Rs '{"body": .}')" \
191+
"https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments"
192+
193+
if [ $? -eq 0 ]; then
194+
echo "✅ Successfully posted comment to PR"
195+
else
196+
echo "❌ Failed to post comment to PR"
197+
fi
198+
fi
199+
200+
else
201+
echo "ℹ️ Publish to PR is disabled (publish: ${{ inputs.publish }})"
202+
fi
203+
204+
# Set final exit code for the action
205+
# Exit with 0 if we're just reporting findings (exit code 1)
206+
# Exit with the actual code for real errors (exit codes > 1)
207+
if [ $TWYN_EXIT_CODE -gt 1 ]; then
208+
exit $TWYN_EXIT_CODE
209+
else
210+
exit 0
211+
fi

requirements.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Test files for Twyn Action
2+
3+
# Python requirements with potential typosquats
4+
requests==2.28.0
5+
nmupy==1.24.0 # typo: numpy
6+
pandsa==1.5.0 # typo: pandas
7+
falsk==2.2.0 # typo: flask
8+
djagno==4.1.0 # typo: django
9+
beatifulsoup4==4.11.0 # typo: beautifulsoup4

0 commit comments

Comments
 (0)