dw-kit 1.9.1 → 1.9.2

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.
Files changed (48) hide show
  1. package/.claude/agents/planner.md +100 -100
  2. package/.claude/agents/quality-checker.md +86 -86
  3. package/.claude/agents/researcher.md +93 -93
  4. package/.claude/agents/reviewer.md +126 -126
  5. package/.claude/hooks/supply-chain-scan.sh +0 -0
  6. package/.claude/rules/code-style.md +37 -37
  7. package/.claude/settings.json +2 -28
  8. package/.claude/skills/dw-plan/template-plan.md +47 -47
  9. package/.claude/skills/dw-research/template-research.md +51 -51
  10. package/.claude/skills/dw-review/checklist.md +88 -88
  11. package/.claude/skills/dw-thinking/THINKING.md +91 -91
  12. package/.claude/templates/agent-report.md +35 -35
  13. package/.claude/templates/en/task-context.md +77 -73
  14. package/.claude/templates/en/task-plan.md +83 -79
  15. package/.claude/templates/en/task-progress.md +69 -65
  16. package/.claude/templates/pr-template.md +56 -56
  17. package/.claude/templates/task-context.md +77 -73
  18. package/.claude/templates/task-plan.md +83 -79
  19. package/.claude/templates/task-progress.md +69 -65
  20. package/.dw/adapters/claude-cli/extensions/README.md +36 -36
  21. package/.dw/adapters/claude-cli/generated/README.md +23 -23
  22. package/.dw/adapters/claude-cli/overrides/README.md +37 -37
  23. package/.dw/adapters/generic/README.md +21 -21
  24. package/.dw/config/presets/enterprise.yml +52 -52
  25. package/.dw/config/presets/small-team.yml +39 -39
  26. package/.dw/config/presets/solo-quick.yml +37 -37
  27. package/.dw/core/AGENTS.md +53 -53
  28. package/.dw/core/QUALITY.md +220 -220
  29. package/.dw/core/THINKING.md +126 -126
  30. package/.dw/core/WORKFLOW.md +17 -12
  31. package/.dw/core/templates/v2/spec.md +2 -0
  32. package/.dw/core/templates/v2/tracking.md +2 -0
  33. package/.dw/core/templates/vi/task-context.md +96 -92
  34. package/.dw/core/templates/vi/task-plan.md +97 -93
  35. package/.dw/core/templates/vi/task-progress.md +60 -56
  36. package/LICENSE +201 -201
  37. package/NOTICE +26 -26
  38. package/bin/dw.mjs +28 -28
  39. package/package.json +1 -1
  40. package/src/commands/claude-vn-fix.mjs +267 -267
  41. package/src/commands/prompt.mjs +112 -112
  42. package/src/commands/validate.mjs +102 -102
  43. package/src/lib/clipboard.mjs +24 -24
  44. package/src/lib/platform.mjs +39 -39
  45. package/src/lib/prompt-suggest.mjs +84 -84
  46. package/src/lib/ui.mjs +66 -66
  47. package/src/lib/update-checker.mjs +73 -73
  48. package/.dw/security/advisory-snapshot.json +0 -157
@@ -1,84 +1,84 @@
1
- import { execSync } from 'node:child_process';
2
-
3
- const TEMPLATE_SUGGESTIONS = [
4
- 'fix: ',
5
- 'fix authentication redirect after login',
6
- 'fix null pointer / undefined error in ',
7
- 'fix performance issue in ',
8
- 'feat: add ',
9
- 'feat: implement ',
10
- 'feat: support ',
11
- 'refactor: simplify ',
12
- 'refactor: extract ',
13
- 'refactor: rename ',
14
- 'perf: optimize ',
15
- 'perf: reduce load time of ',
16
- 'chore: update dependencies',
17
- 'docs: update README',
18
- 'test: add tests for ',
19
- ];
20
-
21
- // Common verbs to detect whether a description has intent
22
- const INTENT_VERBS = [
23
- 'fix', 'add', 'update', 'remove', 'delete', 'create', 'implement', 'refactor',
24
- 'rename', 'move', 'improve', 'optimize', 'support', 'extract', 'migrate',
25
- 'replace', 'upgrade', 'enable', 'disable', 'integrate', 'patch',
26
- ];
27
-
28
- function getTemplateSuggestions() {
29
- return [...TEMPLATE_SUGGESTIONS];
30
- }
31
-
32
- export function getGitSuggestions(cwd) {
33
- try {
34
- const out = execSync('git log --oneline -50 --no-merges', {
35
- cwd,
36
- encoding: 'utf-8',
37
- timeout: 3000,
38
- stdio: ['ignore', 'pipe', 'ignore'],
39
- });
40
- return out
41
- .trim()
42
- .split('\n')
43
- .filter(Boolean)
44
- .map((line) => line.replace(/^[a-f0-9]+ /, '').trim()) // strip hash
45
- .filter((msg) => msg.length > 5)
46
- .slice(0, 30);
47
- } catch {
48
- return [];
49
- }
50
- }
51
-
52
- export function getSuggestions(cwd = process.cwd()) {
53
- const git = getGitSuggestions(cwd);
54
- const templates = getTemplateSuggestions();
55
- // git log first (most relevant to this repo), then templates
56
- const merged = [...git, ...templates];
57
- // dedupe
58
- return [...new Set(merged)].slice(0, 20);
59
- }
60
-
61
- /**
62
- * Returns true if the description is likely too vague to give Claude good context.
63
- */
64
- export function isVague(text) {
65
- const trimmed = (text || '').trim();
66
- if (trimmed.length < 50) return true;
67
- const lower = trimmed.toLowerCase();
68
- const hasVerb = INTENT_VERBS.some((v) => lower.startsWith(v) || lower.includes(` ${v} `));
69
- return !hasVerb;
70
- }
71
-
72
- /**
73
- * Expand a short description into a structured prompt.
74
- * @param {string} text - core task description
75
- * @param {{ area?: string, outcome?: string }} extras
76
- * @returns {string}
77
- */
78
- export function expandTemplate(text, { area = '', outcome = '' } = {}) {
79
- const base = text.trim();
80
- const parts = [base];
81
- if (area) parts.push(`Scope: ${area.trim()}.`);
82
- if (outcome) parts.push(`Expected: ${outcome.trim()}.`);
83
- return parts.join('\n');
84
- }
1
+ import { execSync } from 'node:child_process';
2
+
3
+ const TEMPLATE_SUGGESTIONS = [
4
+ 'fix: ',
5
+ 'fix authentication redirect after login',
6
+ 'fix null pointer / undefined error in ',
7
+ 'fix performance issue in ',
8
+ 'feat: add ',
9
+ 'feat: implement ',
10
+ 'feat: support ',
11
+ 'refactor: simplify ',
12
+ 'refactor: extract ',
13
+ 'refactor: rename ',
14
+ 'perf: optimize ',
15
+ 'perf: reduce load time of ',
16
+ 'chore: update dependencies',
17
+ 'docs: update README',
18
+ 'test: add tests for ',
19
+ ];
20
+
21
+ // Common verbs to detect whether a description has intent
22
+ const INTENT_VERBS = [
23
+ 'fix', 'add', 'update', 'remove', 'delete', 'create', 'implement', 'refactor',
24
+ 'rename', 'move', 'improve', 'optimize', 'support', 'extract', 'migrate',
25
+ 'replace', 'upgrade', 'enable', 'disable', 'integrate', 'patch',
26
+ ];
27
+
28
+ function getTemplateSuggestions() {
29
+ return [...TEMPLATE_SUGGESTIONS];
30
+ }
31
+
32
+ export function getGitSuggestions(cwd) {
33
+ try {
34
+ const out = execSync('git log --oneline -50 --no-merges', {
35
+ cwd,
36
+ encoding: 'utf-8',
37
+ timeout: 3000,
38
+ stdio: ['ignore', 'pipe', 'ignore'],
39
+ });
40
+ return out
41
+ .trim()
42
+ .split('\n')
43
+ .filter(Boolean)
44
+ .map((line) => line.replace(/^[a-f0-9]+ /, '').trim()) // strip hash
45
+ .filter((msg) => msg.length > 5)
46
+ .slice(0, 30);
47
+ } catch {
48
+ return [];
49
+ }
50
+ }
51
+
52
+ export function getSuggestions(cwd = process.cwd()) {
53
+ const git = getGitSuggestions(cwd);
54
+ const templates = getTemplateSuggestions();
55
+ // git log first (most relevant to this repo), then templates
56
+ const merged = [...git, ...templates];
57
+ // dedupe
58
+ return [...new Set(merged)].slice(0, 20);
59
+ }
60
+
61
+ /**
62
+ * Returns true if the description is likely too vague to give Claude good context.
63
+ */
64
+ export function isVague(text) {
65
+ const trimmed = (text || '').trim();
66
+ if (trimmed.length < 50) return true;
67
+ const lower = trimmed.toLowerCase();
68
+ const hasVerb = INTENT_VERBS.some((v) => lower.startsWith(v) || lower.includes(` ${v} `));
69
+ return !hasVerb;
70
+ }
71
+
72
+ /**
73
+ * Expand a short description into a structured prompt.
74
+ * @param {string} text - core task description
75
+ * @param {{ area?: string, outcome?: string }} extras
76
+ * @returns {string}
77
+ */
78
+ export function expandTemplate(text, { area = '', outcome = '' } = {}) {
79
+ const base = text.trim();
80
+ const parts = [base];
81
+ if (area) parts.push(`Scope: ${area.trim()}.`);
82
+ if (outcome) parts.push(`Expected: ${outcome.trim()}.`);
83
+ return parts.join('\n');
84
+ }
package/src/lib/ui.mjs CHANGED
@@ -1,66 +1,66 @@
1
- import chalk from 'chalk';
2
- import { createInterface } from 'node:readline/promises';
3
- import { stdin, stdout } from 'node:process';
4
-
5
- export const BANNER = `
6
- ██████╗ ██╗ ██╗ ██╗ ██╗██╗████████╗
7
- ██╔══██╗██║ ██║ ██║ ██╔╝██║╚══██╔══╝
8
- ██║ ██║██║ █╗ ██║ █████╔╝ ██║ ██║
9
- ██║ ██║██║███╗██║ ██╔═██╗ ██║ ██║
10
- ██████╔╝╚███╔███╔╝ ██║ ██╗██║ ██║
11
- ╚═════╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝`;
12
-
13
- export function banner(subtitle) {
14
- console.log(chalk.cyan.bold(BANNER));
15
- if (subtitle) console.log(chalk.cyan(` ${subtitle}`));
16
- console.log();
17
- }
18
-
19
- export function header(text) {
20
- console.log();
21
- console.log(chalk.bold(`══════════════════════════════════════════`));
22
- console.log(chalk.bold(` ${text}`));
23
- console.log(chalk.bold(`══════════════════════════════════════════`));
24
- }
25
-
26
- export const log = (msg) => console.log(` ${msg}`);
27
- export const ok = (msg) => console.log(chalk.green(` ✓ ${msg}`));
28
- export const warn = (msg) => console.log(chalk.yellow(` ⚠ ${msg}`));
29
- export const err = (msg) => console.log(chalk.red(` ✗ ${msg}`));
30
- export const info = (msg) => { console.log(); console.log(chalk.cyan(`▶ ${msg}`)); };
31
- export const dry = (msg) => console.log(chalk.dim(` [dry-run] ${msg}`));
32
-
33
- export async function ask(question, defaultValue) {
34
- const rl = createInterface({ input: stdin, output: stdout });
35
- const suffix = defaultValue ? ` [${defaultValue}]` : '';
36
- try {
37
- const answer = await rl.question(chalk.bold(` ${question}${suffix}: `));
38
- return answer.trim() || defaultValue || '';
39
- } finally {
40
- rl.close();
41
- }
42
- }
43
-
44
- export async function choose(question, options, defaultValue) {
45
- console.log(chalk.bold(` ${question}`));
46
- for (const opt of options) {
47
- const marker = opt.value === defaultValue ? chalk.cyan(' [default]') : '';
48
- console.log(` ${opt.value} = ${opt.label}${marker}`);
49
- }
50
- const allowed = new Set(options.map((o) => o.value));
51
- while (true) {
52
- const answer = await ask('>', defaultValue);
53
- if (allowed.has(answer)) return answer;
54
- warn(`Invalid choice: "${answer}". Allowed: ${[...allowed].join(', ')}`);
55
- }
56
- }
57
-
58
- export async function multiSelect(question, options, defaultValues) {
59
- console.log(chalk.bold(` ${question}`));
60
- for (const opt of options) {
61
- const marker = defaultValues?.includes(opt.value) ? chalk.dim(' (included)') : '';
62
- console.log(` ${opt.key} = ${opt.label}${marker}`);
63
- }
64
- const hint = defaultValues ? defaultValues.join(',') : '';
65
- return ask('Enter numbers separated by comma', hint);
66
- }
1
+ import chalk from 'chalk';
2
+ import { createInterface } from 'node:readline/promises';
3
+ import { stdin, stdout } from 'node:process';
4
+
5
+ export const BANNER = `
6
+ ██████╗ ██╗ ██╗ ██╗ ██╗██╗████████╗
7
+ ██╔══██╗██║ ██║ ██║ ██╔╝██║╚══██╔══╝
8
+ ██║ ██║██║ █╗ ██║ █████╔╝ ██║ ██║
9
+ ██║ ██║██║███╗██║ ██╔═██╗ ██║ ██║
10
+ ██████╔╝╚███╔███╔╝ ██║ ██╗██║ ██║
11
+ ╚═════╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝`;
12
+
13
+ export function banner(subtitle) {
14
+ console.log(chalk.cyan.bold(BANNER));
15
+ if (subtitle) console.log(chalk.cyan(` ${subtitle}`));
16
+ console.log();
17
+ }
18
+
19
+ export function header(text) {
20
+ console.log();
21
+ console.log(chalk.bold(`══════════════════════════════════════════`));
22
+ console.log(chalk.bold(` ${text}`));
23
+ console.log(chalk.bold(`══════════════════════════════════════════`));
24
+ }
25
+
26
+ export const log = (msg) => console.log(` ${msg}`);
27
+ export const ok = (msg) => console.log(chalk.green(` ✓ ${msg}`));
28
+ export const warn = (msg) => console.log(chalk.yellow(` ⚠ ${msg}`));
29
+ export const err = (msg) => console.log(chalk.red(` ✗ ${msg}`));
30
+ export const info = (msg) => { console.log(); console.log(chalk.cyan(`▶ ${msg}`)); };
31
+ export const dry = (msg) => console.log(chalk.dim(` [dry-run] ${msg}`));
32
+
33
+ export async function ask(question, defaultValue) {
34
+ const rl = createInterface({ input: stdin, output: stdout });
35
+ const suffix = defaultValue ? ` [${defaultValue}]` : '';
36
+ try {
37
+ const answer = await rl.question(chalk.bold(` ${question}${suffix}: `));
38
+ return answer.trim() || defaultValue || '';
39
+ } finally {
40
+ rl.close();
41
+ }
42
+ }
43
+
44
+ export async function choose(question, options, defaultValue) {
45
+ console.log(chalk.bold(` ${question}`));
46
+ for (const opt of options) {
47
+ const marker = opt.value === defaultValue ? chalk.cyan(' [default]') : '';
48
+ console.log(` ${opt.value} = ${opt.label}${marker}`);
49
+ }
50
+ const allowed = new Set(options.map((o) => o.value));
51
+ while (true) {
52
+ const answer = await ask('>', defaultValue);
53
+ if (allowed.has(answer)) return answer;
54
+ warn(`Invalid choice: "${answer}". Allowed: ${[...allowed].join(', ')}`);
55
+ }
56
+ }
57
+
58
+ export async function multiSelect(question, options, defaultValues) {
59
+ console.log(chalk.bold(` ${question}`));
60
+ for (const opt of options) {
61
+ const marker = defaultValues?.includes(opt.value) ? chalk.dim(' (included)') : '';
62
+ console.log(` ${opt.key} = ${opt.label}${marker}`);
63
+ }
64
+ const hint = defaultValues ? defaultValues.join(',') : '';
65
+ return ask('Enter numbers separated by comma', hint);
66
+ }
@@ -1,73 +1,73 @@
1
- import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
2
- import { join } from 'node:path';
3
- import { homedir } from 'node:os';
4
-
5
- const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
6
- const REGISTRY_URL = 'https://registry.npmjs.org/dw-kit/latest';
7
- const CACHE_DIR = join(homedir(), '.dw-kit');
8
- const CACHE_FILE = join(CACHE_DIR, 'update-cache.json');
9
-
10
- function parseSemver(v) {
11
- const parts = String(v).replace(/^v/, '').split('.').map(Number);
12
- return { major: parts[0] || 0, minor: parts[1] || 0, patch: parts[2] || 0 };
13
- }
14
-
15
- function isNewer(latest, current) {
16
- const l = parseSemver(latest);
17
- const c = parseSemver(current);
18
- if (l.major !== c.major) return l.major > c.major;
19
- if (l.minor !== c.minor) return l.minor > c.minor;
20
- return l.patch > c.patch;
21
- }
22
-
23
- function readCache() {
24
- try {
25
- return JSON.parse(readFileSync(CACHE_FILE, 'utf-8'));
26
- } catch {
27
- return null;
28
- }
29
- }
30
-
31
- function writeCache(data) {
32
- try {
33
- mkdirSync(CACHE_DIR, { recursive: true });
34
- writeFileSync(CACHE_FILE, JSON.stringify(data), 'utf-8');
35
- } catch {
36
- // ignore write errors (permission, disk full, etc.)
37
- }
38
- }
39
-
40
- async function fetchLatestVersion() {
41
- const res = await fetch(REGISTRY_URL, { signal: AbortSignal.timeout(3000) });
42
- if (!res.ok) throw new Error(`HTTP ${res.status}`);
43
- const data = await res.json();
44
- return data.version;
45
- }
46
-
47
- /**
48
- * Returns latest version string if an update is available (from cache), null otherwise.
49
- * Never throws, never makes network calls.
50
- */
51
- export function getUpdateNotice(currentVersion) {
52
- if (process.env.DW_NO_UPDATE_CHECK) return null;
53
- const cache = readCache();
54
- if (!cache?.latest) return null;
55
- return isNewer(cache.latest, currentVersion) ? cache.latest : null;
56
- }
57
-
58
- /**
59
- * Fires off an async check against npm registry and updates the cache.
60
- * Non-blocking — caller should NOT await this.
61
- * Skips the check if cache is still fresh (< 24h).
62
- */
63
- export function scheduleUpdateCheck(currentVersion) {
64
- if (process.env.DW_NO_UPDATE_CHECK) return;
65
-
66
- const cache = readCache();
67
- const now = Date.now();
68
- if (cache?.checkedAt && now - cache.checkedAt < CACHE_TTL_MS) return;
69
-
70
- fetchLatestVersion()
71
- .then((latest) => writeCache({ latest, checkedAt: now, current: currentVersion }))
72
- .catch(() => {});
73
- }
1
+ import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { homedir } from 'node:os';
4
+
5
+ const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
6
+ const REGISTRY_URL = 'https://registry.npmjs.org/dw-kit/latest';
7
+ const CACHE_DIR = join(homedir(), '.dw-kit');
8
+ const CACHE_FILE = join(CACHE_DIR, 'update-cache.json');
9
+
10
+ function parseSemver(v) {
11
+ const parts = String(v).replace(/^v/, '').split('.').map(Number);
12
+ return { major: parts[0] || 0, minor: parts[1] || 0, patch: parts[2] || 0 };
13
+ }
14
+
15
+ function isNewer(latest, current) {
16
+ const l = parseSemver(latest);
17
+ const c = parseSemver(current);
18
+ if (l.major !== c.major) return l.major > c.major;
19
+ if (l.minor !== c.minor) return l.minor > c.minor;
20
+ return l.patch > c.patch;
21
+ }
22
+
23
+ function readCache() {
24
+ try {
25
+ return JSON.parse(readFileSync(CACHE_FILE, 'utf-8'));
26
+ } catch {
27
+ return null;
28
+ }
29
+ }
30
+
31
+ function writeCache(data) {
32
+ try {
33
+ mkdirSync(CACHE_DIR, { recursive: true });
34
+ writeFileSync(CACHE_FILE, JSON.stringify(data), 'utf-8');
35
+ } catch {
36
+ // ignore write errors (permission, disk full, etc.)
37
+ }
38
+ }
39
+
40
+ async function fetchLatestVersion() {
41
+ const res = await fetch(REGISTRY_URL, { signal: AbortSignal.timeout(3000) });
42
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
43
+ const data = await res.json();
44
+ return data.version;
45
+ }
46
+
47
+ /**
48
+ * Returns latest version string if an update is available (from cache), null otherwise.
49
+ * Never throws, never makes network calls.
50
+ */
51
+ export function getUpdateNotice(currentVersion) {
52
+ if (process.env.DW_NO_UPDATE_CHECK) return null;
53
+ const cache = readCache();
54
+ if (!cache?.latest) return null;
55
+ return isNewer(cache.latest, currentVersion) ? cache.latest : null;
56
+ }
57
+
58
+ /**
59
+ * Fires off an async check against npm registry and updates the cache.
60
+ * Non-blocking — caller should NOT await this.
61
+ * Skips the check if cache is still fresh (< 24h).
62
+ */
63
+ export function scheduleUpdateCheck(currentVersion) {
64
+ if (process.env.DW_NO_UPDATE_CHECK) return;
65
+
66
+ const cache = readCache();
67
+ const now = Date.now();
68
+ if (cache?.checkedAt && now - cache.checkedAt < CACHE_TTL_MS) return;
69
+
70
+ fetchLatestVersion()
71
+ .then((latest) => writeCache({ latest, checkedAt: now, current: currentVersion }))
72
+ .catch(() => {});
73
+ }
@@ -1,157 +0,0 @@
1
- {
2
- "schema_version": "1.0",
3
- "fetched_at": "2026-05-12T09:57:47.323Z",
4
- "source": "osv.dev",
5
- "ecosystem": "npm",
6
- "package_count": 13,
7
- "advisory_count": 2,
8
- "advisories": [
9
- {
10
- "id": "GHSA-q3j6-qgpj-74h6",
11
- "summary": "fast-uri vulnerable to path traversal via percent-encoded dot segments",
12
- "details": "### Impact\n\n`fast-uri` v3.1.0 and earlier decodes percent-encoded path separators (`%2F`) and dot segments (`%2E`) before applying dot-segment removal in `normalize()` and `equal()`. This makes encoded path data behave like real `/` and `..`, so distinct URIs collapse onto the same normalized path.\n\nFor example, `http://example.com/public/%2e%2e/admin` normalizes to `http://example.com/admin`, and `equal()` considers them the same URI.\n\nApplications that normalize or compare attacker-controlled URLs to enforce path-based policy can be bypassed. A path that looks confined under an allowed prefix can normalize to a different location.\n\n### Patches\n\nUpgrade to `fast-uri` >= 3.1.1.\n\n### Workarounds\n\nNone. Upgrade to the patched version.",
13
- "aliases": [
14
- "CVE-2026-6321"
15
- ],
16
- "modified": "2026-05-09T16:44:22.524341929Z",
17
- "published": "2026-05-08T17:15:09Z",
18
- "related": [
19
- "CGA-9j5f-2hwm-8hfc"
20
- ],
21
- "database_specific": {
22
- "cwe_ids": [
23
- "CWE-22"
24
- ],
25
- "github_reviewed": true,
26
- "github_reviewed_at": "2026-05-08T17:15:09Z",
27
- "severity": "HIGH",
28
- "nvd_published_at": "2026-05-04T20:16:20Z"
29
- },
30
- "references": [
31
- {
32
- "type": "WEB",
33
- "url": "https://github.com/fastify/fast-uri/security/advisories/GHSA-q3j6-qgpj-74h6"
34
- },
35
- {
36
- "type": "ADVISORY",
37
- "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-6321"
38
- },
39
- {
40
- "type": "WEB",
41
- "url": "https://cna.openjsf.org/security-advisories.html"
42
- },
43
- {
44
- "type": "PACKAGE",
45
- "url": "https://github.com/fastify/fast-uri"
46
- }
47
- ],
48
- "affected": [
49
- {
50
- "package": {
51
- "name": "fast-uri",
52
- "ecosystem": "npm",
53
- "purl": "pkg:npm/fast-uri"
54
- },
55
- "ranges": [
56
- {
57
- "type": "SEMVER",
58
- "events": [
59
- {
60
- "introduced": "0"
61
- },
62
- {
63
- "fixed": "3.1.1"
64
- }
65
- ]
66
- }
67
- ],
68
- "database_specific": {
69
- "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/05/GHSA-q3j6-qgpj-74h6/GHSA-q3j6-qgpj-74h6.json",
70
- "last_known_affected_version_range": "<= 3.1.0"
71
- }
72
- }
73
- ],
74
- "schema_version": "1.7.5",
75
- "severity": [
76
- {
77
- "type": "CVSS_V3",
78
- "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N"
79
- }
80
- ]
81
- },
82
- {
83
- "id": "GHSA-v39h-62p7-jpjc",
84
- "summary": "fast-uri vulnerable to host confusion via percent-encoded authority delimiters",
85
- "details": "### Impact\n\n`fast-uri` v3.1.1 and earlier decodes percent-encoded authority delimiters (`%40` as `@`, `%3A` as `:`) inside the host component and serializes them back as raw characters. This changes the URI structure, turning a hostname into userinfo plus a different host.\n\nFor example, `http://trusted.com%40evil.com/` normalizes to `http://trusted.com@evil.com/`, which reparses as host `evil.com` with userinfo `trusted.com`.\n\nApplications that normalize untrusted URLs before host allowlist checks, redirect validation, or outbound request routing can be steered to a different authority than the original URL appeared to contain.\n\n### Patches\n\nUpgrade to `fast-uri` >= 3.1.2.\n\n### Workarounds\n\nNone. Upgrade to the patched version.",
86
- "aliases": [
87
- "CVE-2026-6322"
88
- ],
89
- "modified": "2026-05-10T04:44:28.903255090Z",
90
- "published": "2026-05-08T19:13:01Z",
91
- "related": [
92
- "CGA-5vr9-c8qr-fqvg"
93
- ],
94
- "database_specific": {
95
- "cwe_ids": [
96
- "CWE-436"
97
- ],
98
- "github_reviewed": true,
99
- "github_reviewed_at": "2026-05-08T19:13:01Z",
100
- "severity": "HIGH",
101
- "nvd_published_at": "2026-05-05T11:16:33Z"
102
- },
103
- "references": [
104
- {
105
- "type": "WEB",
106
- "url": "https://github.com/fastify/fast-uri/security/advisories/GHSA-v39h-62p7-jpjc"
107
- },
108
- {
109
- "type": "ADVISORY",
110
- "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-6322"
111
- },
112
- {
113
- "type": "WEB",
114
- "url": "https://cna.openjsf.org/security-advisories.html"
115
- },
116
- {
117
- "type": "PACKAGE",
118
- "url": "https://github.com/fastify/fast-uri"
119
- }
120
- ],
121
- "affected": [
122
- {
123
- "package": {
124
- "name": "fast-uri",
125
- "ecosystem": "npm",
126
- "purl": "pkg:npm/fast-uri"
127
- },
128
- "ranges": [
129
- {
130
- "type": "SEMVER",
131
- "events": [
132
- {
133
- "introduced": "0"
134
- },
135
- {
136
- "fixed": "3.1.2"
137
- }
138
- ]
139
- }
140
- ],
141
- "database_specific": {
142
- "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/05/GHSA-v39h-62p7-jpjc/GHSA-v39h-62p7-jpjc.json",
143
- "last_known_affected_version_range": "<= 3.1.1"
144
- }
145
- }
146
- ],
147
- "schema_version": "1.7.5",
148
- "severity": [
149
- {
150
- "type": "CVSS_V3",
151
- "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N"
152
- }
153
- ]
154
- }
155
- ],
156
- "snapshot_sha": "sha256:0b6ca61019fb234c"
157
- }