drafted 1.14.6 → 1.14.8
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/cli/drafted.mjs +43 -17
- package/mcp/server.mjs +5 -3
- package/package.json +1 -1
package/cli/drafted.mjs
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { program } from 'commander';
|
|
11
|
-
import { spawn, execSync } from 'child_process';
|
|
12
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, statSync, unlinkSync } from 'fs';
|
|
11
|
+
import { spawn, execSync, execFileSync } from 'child_process';
|
|
12
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, statSync, unlinkSync, chmodSync } from 'fs';
|
|
13
13
|
import { join, dirname, basename, resolve } from 'path';
|
|
14
14
|
import { homedir, tmpdir, platform } from 'os';
|
|
15
15
|
import { fileURLToPath } from 'url';
|
|
@@ -38,9 +38,9 @@ const PACKAGE_VERSION = (() => {
|
|
|
38
38
|
}
|
|
39
39
|
})();
|
|
40
40
|
|
|
41
|
-
// Ensure state directory exists
|
|
41
|
+
// Ensure state directory exists (0700 — holds the session token in auth.json)
|
|
42
42
|
if (!existsSync(DEFAULT_STATE_DIR)) {
|
|
43
|
-
mkdirSync(DEFAULT_STATE_DIR, { recursive: true });
|
|
43
|
+
mkdirSync(DEFAULT_STATE_DIR, { recursive: true, mode: 0o700 });
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
// Helper: Read projects
|
|
@@ -203,9 +203,13 @@ function readAuth() {
|
|
|
203
203
|
|
|
204
204
|
function writeAuth(authData) {
|
|
205
205
|
if (!existsSync(DEFAULT_STATE_DIR)) {
|
|
206
|
-
mkdirSync(DEFAULT_STATE_DIR, { recursive: true });
|
|
206
|
+
mkdirSync(DEFAULT_STATE_DIR, { recursive: true, mode: 0o700 });
|
|
207
207
|
}
|
|
208
|
-
|
|
208
|
+
// SECURITY: auth.json holds the long-lived Drafted session token. Restrict it to the
|
|
209
|
+
// owner (0600) so no other local user on a shared host can read it. writeFileSync's mode
|
|
210
|
+
// only applies on create, so chmod after to also fix a pre-existing world-readable file.
|
|
211
|
+
writeFileSync(DEFAULT_AUTH_FILE, JSON.stringify(authData, null, 2), { mode: 0o600 });
|
|
212
|
+
try { chmodSync(DEFAULT_AUTH_FILE, 0o600); } catch { /* best effort */ }
|
|
209
213
|
}
|
|
210
214
|
|
|
211
215
|
function clearAuth() {
|
|
@@ -422,12 +426,15 @@ program
|
|
|
422
426
|
console.log('');
|
|
423
427
|
console.log(' If the browser doesn\'t open, visit the URL above manually.');
|
|
424
428
|
|
|
425
|
-
// Open browser
|
|
429
|
+
// Open browser. SECURITY: verificationUrl comes from the server — pass it as an argv
|
|
430
|
+
// arg (execFileSync), never interpolated into a shell string, so a compromised/MITM'd
|
|
431
|
+
// server response can't inject `$(...)`/backticks that run on this host.
|
|
426
432
|
try {
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
433
|
+
if (process.platform === 'win32') {
|
|
434
|
+
execFileSync('cmd', ['/c', 'start', '', verificationUrl], { stdio: 'ignore' });
|
|
435
|
+
} else {
|
|
436
|
+
execFileSync(process.platform === 'darwin' ? 'open' : 'xdg-open', [verificationUrl], { stdio: 'ignore' });
|
|
437
|
+
}
|
|
431
438
|
} catch {
|
|
432
439
|
// Browser open failed — user will use the URL manually
|
|
433
440
|
}
|
|
@@ -1584,7 +1591,12 @@ async function syncOneSkill(ref, outDir, org) {
|
|
|
1584
1591
|
mkdirSync(dirname(full), { recursive: true });
|
|
1585
1592
|
writeFileSync(full, content);
|
|
1586
1593
|
}
|
|
1587
|
-
|
|
1594
|
+
// createdBy + forkedFrom decide whether this skill's setup may auto-run: shell authored by
|
|
1595
|
+
// someone else must not run unattended on sync. forkedFrom is load-bearing — the CLI/MCP
|
|
1596
|
+
// AUTO-forks a global/other-org skill on use, which copies the source author's setup but
|
|
1597
|
+
// stamps createdBy = the forking (victim) user, so createdBy alone would mislabel foreign
|
|
1598
|
+
// setup as self-authored. A forked skill is never treated as self-authored.
|
|
1599
|
+
return { ref, slug, hash, status: 'ok', createdBy: skill.createdBy ?? null, forkedFrom: skill.forkedFrom ?? null };
|
|
1588
1600
|
}
|
|
1589
1601
|
|
|
1590
1602
|
const skillCmd = program.command('skill').description('Skill library operations');
|
|
@@ -1594,10 +1606,11 @@ skillCmd
|
|
|
1594
1606
|
.option('--ref <ref>', 'skill ref slug[@hash] (repeatable)', (v, acc) => { acc.push(v); return acc; }, [])
|
|
1595
1607
|
.requiredOption('--out <dir>', 'output directory for bundles')
|
|
1596
1608
|
.option('--org <org>', 'resolve against this Drafted org (id or name); scopes per-request without switching the session')
|
|
1597
|
-
.option('--no-setup', 'do not run each skill\'s setup after materializing (default: run setup
|
|
1609
|
+
.option('--no-setup', 'do not run each skill\'s setup after materializing (default: run setup only for skills you authored)')
|
|
1610
|
+
.option('--allow-untrusted-setup', 'also run setup for skills authored by someone else (SECURITY: runs their shell on this host — only for skills you trust)')
|
|
1598
1611
|
.option('--format <fmt>', 'output format: json or text', 'text')
|
|
1599
1612
|
.action(async (opts) => {
|
|
1600
|
-
requireLogin();
|
|
1613
|
+
const auth = requireLogin();
|
|
1601
1614
|
const refs = opts.ref || [];
|
|
1602
1615
|
mkdirSync(opts.out, { recursive: true });
|
|
1603
1616
|
const results = [];
|
|
@@ -1611,9 +1624,22 @@ skillCmd
|
|
|
1611
1624
|
// failure is reported but the materialized source stays put (sync is still
|
|
1612
1625
|
// `ok` — the bytes are there, just not built).
|
|
1613
1626
|
if (r.status === 'ok' && opts.setup !== false) {
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1627
|
+
// SECURITY: setup commands are arbitrary shell (execSync). Auto-run them ONLY for
|
|
1628
|
+
// skills the current user authored — self-consent, like your own repo. Skills
|
|
1629
|
+
// authored by another org member, a fork, or an imported bundle must NOT run their
|
|
1630
|
+
// shell unattended (Causeway auto-syncs pinned skills), or a teammate's skill is
|
|
1631
|
+
// RCE on this host. `--allow-untrusted-setup` is the explicit opt-in.
|
|
1632
|
+
// Self-authored = created by me AND not a fork (a fork carries another author's
|
|
1633
|
+
// setup under my createdBy — see syncOneSkill). Forks/other-authored require the
|
|
1634
|
+
// explicit opt-in.
|
|
1635
|
+
const selfAuthored = r.createdBy && auth?.userId && r.createdBy === auth.userId && !r.forkedFrom;
|
|
1636
|
+
if (selfAuthored || opts.allowUntrustedSetup) {
|
|
1637
|
+
const s = runSkillSetup(join(opts.out, r.slug));
|
|
1638
|
+
r.setup = s.skipped ? 'none' : (s.ok ? 'ok' : 'failed');
|
|
1639
|
+
if (!s.ok && !s.skipped) r.setupError = s.failed ? `${s.failed}: ${s.error}` : s.error;
|
|
1640
|
+
} else {
|
|
1641
|
+
r.setup = 'skipped-untrusted';
|
|
1642
|
+
}
|
|
1617
1643
|
}
|
|
1618
1644
|
results.push(r);
|
|
1619
1645
|
if (r.status !== 'ok') allOk = false;
|
package/mcp/server.mjs
CHANGED
|
@@ -1693,9 +1693,11 @@ if (!isRemote) tool('auth', 'Sign in to Drafted. On a local install the DESKTOP
|
|
|
1693
1693
|
console.error(`\n[MCP] Sign in at: ${verificationUrl}\n${qrText ? qrText + '\n' : ''}[MCP] Waiting for approval...`);
|
|
1694
1694
|
|
|
1695
1695
|
if (!reusingPending) {
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1696
|
+
// SECURITY: verificationUrl is server-supplied — pass as an argv arg (execFile, no
|
|
1697
|
+
// shell) so a compromised/MITM'd server response can't inject shell into this host.
|
|
1698
|
+
const { execFile } = await import('child_process');
|
|
1699
|
+
if (process.platform === 'win32') execFile('cmd', ['/c', 'start', '', verificationUrl]);
|
|
1700
|
+
else execFile(process.platform === 'darwin' ? 'open' : 'xdg-open', [verificationUrl]);
|
|
1699
1701
|
}
|
|
1700
1702
|
|
|
1701
1703
|
const deadline = Date.now() + (expiresIn * 1000);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "drafted",
|
|
3
|
-
"version": "1.14.
|
|
3
|
+
"version": "1.14.8",
|
|
4
4
|
"description": "Drafted — visual thinking surface for humans and AI agents. Renders HTML, markdown, images, and code as frames on a zoomable canvas, with MCP tools for AI agents and real-time sync for humans.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|