-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathaction.yml
More file actions
255 lines (211 loc) · 8.79 KB
/
action.yml
File metadata and controls
255 lines (211 loc) · 8.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
name: "Twyn action"
description: "Security tool against dependency typosquatting attacks"
author: "Elements Interactive"
branding:
icon: "search"
color: "blue"
inputs:
github-token:
description: "Token needed to publish results to the PR."
required: false
config:
description: "Path to the config file"
required: false
dependency-file:
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."
required: false
selector-method:
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."
required: false
no-track:
description: "Do not show the progress bar while processing packages"
required: false
default: "false"
json:
description: "Display the results in json format. It implies no-track. Mutually exclusive with --table."
required: false
default: "false"
table:
description: "Display the results in a table format. It implies no-track. Mutually exclusive with --json."
required: false
default: "false"
recursive:
description: "Recursively look for files when trying to locate them automatically. Ignored if dependency-file is given."
required: false
default: "false"
pypi-source:
description: "Alternative PyPI source URL to use for fetching trusted packages"
required: false
npm-source:
description: "Alternative npm source URL to use for fetching trusted packages"
required: false
v:
description: "Enable verbose output (-v)"
required: false
default: "false"
vv:
description: "Enable extra verbose output (-vv)"
required: false
default: "false"
version:
description: "Twyn version (latest, v1.0.0, etc.)"
required: false
default: "latest"
publish:
description: "Whether to publish the twyn results as PR comments (requires table format)"
required: false
default: "false"
outputs:
results:
description: "Raw output from twyn scan"
value: ${{ steps.twyn-scan.outputs.results }}
exit-code:
description: "Exit code from twyn scan"
value: ${{ steps.twyn-scan.outputs.exit-code }}
runs:
using: "composite"
steps:
- name: Run Twyn Security Check
id: twyn-scan
shell: bash
run: |
# Validate input combinations
if [ "${{ inputs.publish }}" = "true" ] && [ "${{ inputs.json }}" = "true" ]; then
echo "❌ Error: 'publish' and 'json' cannot be used together."
echo " Publishing requires table format, but JSON format was requested."
echo " Please use either 'publish: true' with 'table: true' or use 'json: true' without publishing."
exit 1
fi
if [ "${{ inputs.json }}" = "true" ] && [ "${{ inputs.table }}" = "true" ]; then
echo "❌ Error: 'json' and 'table' are mutually exclusive."
echo " Please choose either JSON or table format, not both."
exit 1
fi
# Build arguments as an array for safety (avoids word-splitting issues)
ARGS=()
# Optional config file
if [ -n "${{ inputs.config }}" ]; then
ARGS+=(--config "${{ inputs.config }}")
fi
# Dependency files (multiple allowed)
if [ -n "${{ inputs.dependency-file }}" ]; then
IFS=',' read -ra DEPENDENCY_FILES <<< "${{ inputs.dependency-file }}"
for file in "${DEPENDENCY_FILES[@]}"; do
if [ -n "$file" ]; then
ARGS+=(--dependency-file "$file")
fi
done
fi
# Selector method
if [ -n "${{ inputs.selector-method }}" ]; then
ARGS+=(--selector-method "${{ inputs.selector-method }}")
fi
# Boolean flags
if [ "${{ inputs.no-track }}" = "true" ]; then
ARGS+=(--no-track)
fi
if [ "${{ inputs.json }}" = "true" ]; then
ARGS+=(--json)
fi
# Force table format when publishing
if [ "${{ inputs.publish }}" = "true" ] || [ "${{ inputs.table }}" = "true" ]; then
VERSION="${{ inputs.version }}"
# Check if version is in format vX.Y.Z or just latest
if [[ "$VERSION" =~ ^v[0-9]+(\.[0-9]+)*$ ]]; then
# Extract version number (remove 'v' prefix)
VERSION_NUM=${VERSION#v}
MAJOR_VERSION=$(echo "$VERSION_NUM" | cut -d. -f1)
if [ "$MAJOR_VERSION" -lt 6 ]; then
echo "❌ Error: Table format requires Twyn version >= v6."
echo " Current version: $VERSION"
echo " Please set 'version: \"v6\"' or higher to use table format and to publish to a PR."
exit 1
fi
elif [ "$VERSION" != "latest" ]; then
echo "❌ Error: Invalid version format '$VERSION'."
echo " Expected format: 'vX.Y.Z' (e.g., 'v6.0.0') or 'latest'."
echo " Table format and publishing require version >= v6."
exit 1
fi
# If the check was successful, add the table arg
ARGS+=(--table)
fi
if [ "${{ inputs.recursive }}" = "true" ]; then
ARGS+=(--recursive)
fi
# Source URLs
if [ -n "${{ inputs.pypi-source }}" ]; then
ARGS+=(--pypi-source "${{ inputs.pypi-source }}")
fi
if [ -n "${{ inputs.npm-source }}" ]; then
ARGS+=(--npm-source "${{ inputs.npm-source }}")
fi
# Verbose mode
if [ "${{ inputs.vv }}" = "true" ]; then
ARGS+=(-vv)
elif [ "${{ inputs.v }}" = "true" ]; then
ARGS+=(-v)
fi
# Run twyn using Docker and capture output and exit code
# Use 'set +e' to prevent script from exiting on non-zero exit codes
set +e
TWYN_OUTPUT=$(docker run --rm \
-v "${{ github.workspace }}:/results" \
-w /results \
elementsinteractive/twyn:${{ inputs.version }} run \
"${ARGS[@]}" 2>/dev/null)
TWYN_EXIT_CODE=$?
set -e
# Display output in action logs
echo "$TWYN_OUTPUT"
# Set action outputs
echo "results<<EOF" >> $GITHUB_OUTPUT
echo "$TWYN_OUTPUT" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
echo "exit-code=$TWYN_EXIT_CODE" >> $GITHUB_OUTPUT
# Create PR comment with twyn results
if [ "${{ inputs.publish }}" = "true" ] && [ $TWYN_EXIT_CODE -ge 1 ]; then
# Check if github-token is provided
if [ -z "${{ inputs.github-token }}" ]; then
echo "❌ Error: github-token is required when publish is enabled. Skipping..."
exit $TWYN_EXIT_CODE
fi
# Check if we're in a Pull Request context
if [ "${{ github.event_name }}" != "pull_request" ] || [ -z "${{ github.event.pull_request.number }}" ]; then
echo "❌ Error: Publishing the results to the PR only works in Pull Request context."
echo " Current event: ${{ github.event_name }}"
echo " To enable publishing, make sure your workflow is triggered by 'pull_request' events."
echo " Example:"
echo " on:"
echo " pull_request:"
echo " branches: [main]"
exit $TWYN_EXIT_CODE
fi
# Create comment content with proper formatting
echo "## 🛡️ Twyn Detection Results" > comment.md
echo "" >> comment.md
echo '```' >> comment.md
# Process the output to handle escape sequences properly
echo "$TWYN_OUTPUT" | sed 's/\\n/\n/g' >> comment.md
echo '```' >> comment.md
curl -X POST \
-H "Authorization: token ${{ inputs.github-token}}" \
-H "Accept: application/vnd.github.v3+json" \
-d "$(cat comment.md | jq -Rs '{"body": .}')" \
"https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments"
if [ $? -eq 0 ]; then
echo "✅ Successfully posted comment to PR"
else
echo "❌ Failed to post comment to PR"
fi
else
echo "ℹ️ Publish to PR is disabled (publish: ${{ inputs.publish }})"
fi
# Set final exit code for the action
# Exit with 0 if we're just reporting findings (exit code 1)
# Exit with the actual code for real errors (exit codes > 1)
if [ $TWYN_EXIT_CODE -gt 1 ]; then
exit $TWYN_EXIT_CODE
else
exit 0
fi