cto-ai-cli 3.1.0 → 4.0.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/DOCS.md +352 -0
- package/README.md +192 -15
- package/dist/action/index.js +629 -83
- package/dist/api/dashboard.js +107 -23
- package/dist/api/dashboard.js.map +1 -1
- package/dist/api/server.js +108 -24
- package/dist/api/server.js.map +1 -1
- package/dist/cli/gateway.js +2925 -0
- package/dist/cli/score.js +3015 -237
- package/dist/cli/v2/index.js +133 -49
- package/dist/cli/v2/index.js.map +1 -1
- package/dist/engine/index.d.ts +85 -1
- package/dist/engine/index.js +665 -42
- package/dist/engine/index.js.map +1 -1
- package/dist/gateway/index.d.ts +281 -0
- package/dist/gateway/index.js +2803 -0
- package/dist/gateway/index.js.map +1 -0
- package/dist/govern/index.d.ts +67 -3
- package/dist/govern/index.js +462 -23
- package/dist/govern/index.js.map +1 -1
- package/dist/interact/index.js +108 -24
- package/dist/interact/index.js.map +1 -1
- package/dist/mcp/v2.js +130 -46
- package/dist/mcp/v2.js.map +1 -1
- package/package.json +3 -2
package/dist/api/server.js
CHANGED
|
@@ -800,11 +800,13 @@ async function getCachedAnalysis(projectPath, config) {
|
|
|
800
800
|
}
|
|
801
801
|
|
|
802
802
|
// src/engine/selector.ts
|
|
803
|
-
import { createHash as
|
|
803
|
+
import { createHash as createHash4 } from "crypto";
|
|
804
804
|
|
|
805
805
|
// src/govern/secrets.ts
|
|
806
806
|
import { readFile as readFile3 } from "fs/promises";
|
|
807
|
-
import {
|
|
807
|
+
import { readFileSync, existsSync as existsSync2, mkdirSync, writeFileSync } from "fs";
|
|
808
|
+
import { resolve as resolve4, relative as relative4, join as join4, dirname as dirname2 } from "path";
|
|
809
|
+
import { createHash as createHash3 } from "crypto";
|
|
808
810
|
var BUILTIN_PATTERNS = [
|
|
809
811
|
// API Keys
|
|
810
812
|
{ type: "api-key", source: `(?:api[_-]?key|apikey)\\s*[:=]\\s*['"]?([a-zA-Z0-9_\\-]{20,})['"]?`, flags: "gi", severity: "critical", description: "API Key" },
|
|
@@ -829,15 +831,66 @@ var BUILTIN_PATTERNS = [
|
|
|
829
831
|
{ type: "connection-string", source: `(?:mongodb(?:\\+srv)?|postgres(?:ql)?|mysql|redis|amqp):\\/\\/[^\\s'"]+:[^\\s'"]+@[^\\s'"]+`, flags: "gi", severity: "critical", description: "Database Connection String" },
|
|
830
832
|
{ type: "connection-string", source: `(?:DATABASE_URL|REDIS_URL|MONGODB_URI)\\s*[:=]\\s*['"]?([^\\s'"]{10,})['"]?`, flags: "gi", severity: "high", description: "Database URL" },
|
|
831
833
|
// Environment variables with secrets
|
|
832
|
-
{ type: "env-variable", source: `(?:SECRET|PRIVATE|ENCRYPTION)[_-]?(?:KEY|TOKEN|PASS)\\s*[:=]\\s*['"]?([^\\s'"]{8,})['"]?`, flags: "gi", severity: "high", description: "Secret Environment Variable" }
|
|
834
|
+
{ type: "env-variable", source: `(?:SECRET|PRIVATE|ENCRYPTION)[_-]?(?:KEY|TOKEN|PASS)\\s*[:=]\\s*['"]?([^\\s'"]{8,})['"]?`, flags: "gi", severity: "high", description: "Secret Environment Variable" },
|
|
835
|
+
// Stripe
|
|
836
|
+
{ type: "api-key", source: "sk_live_[a-zA-Z0-9]{24,}", flags: "g", severity: "critical", description: "Stripe Live Secret Key" },
|
|
837
|
+
{ type: "api-key", source: "pk_live_[a-zA-Z0-9]{24,}", flags: "g", severity: "high", description: "Stripe Live Publishable Key" },
|
|
838
|
+
{ type: "api-key", source: "rk_live_[a-zA-Z0-9]{24,}", flags: "g", severity: "critical", description: "Stripe Restricted Key" },
|
|
839
|
+
// Slack
|
|
840
|
+
{ type: "token", source: "xoxb-[0-9]{10,}-[0-9]{10,}-[a-zA-Z0-9]{24,}", flags: "g", severity: "critical", description: "Slack Bot Token" },
|
|
841
|
+
{ type: "token", source: "xoxp-[0-9]{10,}-[0-9]{10,}-[a-zA-Z0-9]{24,}", flags: "g", severity: "critical", description: "Slack User Token" },
|
|
842
|
+
{ type: "api-key", source: "https://hooks\\.slack\\.com/services/T[a-zA-Z0-9_]+/B[a-zA-Z0-9_]+/[a-zA-Z0-9_]+", flags: "g", severity: "high", description: "Slack Webhook URL" },
|
|
843
|
+
// Google
|
|
844
|
+
{ type: "api-key", source: "AIza[0-9A-Za-z_-]{35}", flags: "g", severity: "high", description: "Google API Key" },
|
|
845
|
+
{ type: "token", source: "ya29\\.[0-9A-Za-z_-]+", flags: "g", severity: "high", description: "Google OAuth Token" },
|
|
846
|
+
// Azure
|
|
847
|
+
{ type: "api-key", source: "(?:AccountKey|SharedAccessKey)\\s*=\\s*[a-zA-Z0-9+/=]{40,}", flags: "g", severity: "critical", description: "Azure Storage Key" },
|
|
848
|
+
// Twilio
|
|
849
|
+
{ type: "api-key", source: "AC[a-f0-9]{32}", flags: "g", severity: "high", description: "Twilio Account SID" },
|
|
850
|
+
// SendGrid
|
|
851
|
+
{ type: "api-key", source: "SG\\.[a-zA-Z0-9_-]{22}\\.[a-zA-Z0-9_-]{43}", flags: "g", severity: "critical", description: "SendGrid API Key" },
|
|
852
|
+
// JWT
|
|
853
|
+
{ type: "token", source: "eyJ[a-zA-Z0-9_-]{10,}\\.eyJ[a-zA-Z0-9_-]{10,}\\.[a-zA-Z0-9_-]{10,}", flags: "g", severity: "high", description: "JSON Web Token" },
|
|
854
|
+
// Datadog
|
|
855
|
+
{ type: "api-key", source: `(?:DD_API_KEY|DATADOG_API_KEY)\\s*[:=]\\s*['"]?([a-f0-9]{32})['"]?`, flags: "gi", severity: "critical", description: "Datadog API Key" },
|
|
856
|
+
{ type: "api-key", source: `(?:DD_APP_KEY|DATADOG_APP_KEY)\\s*[:=]\\s*['"]?([a-f0-9]{40})['"]?`, flags: "gi", severity: "critical", description: "Datadog App Key" },
|
|
857
|
+
// Sentry
|
|
858
|
+
{ type: "connection-string", source: "https://[a-f0-9]{32}@[a-z0-9]+\\.ingest\\.sentry\\.io/[0-9]+", flags: "g", severity: "high", description: "Sentry DSN" },
|
|
859
|
+
// Firebase
|
|
860
|
+
{ type: "api-key", source: `(?:FIREBASE_API_KEY|FIREBASE_KEY)\\s*[:=]\\s*['"]?([a-zA-Z0-9_\\-]{30,})['"]?`, flags: "gi", severity: "high", description: "Firebase API Key" },
|
|
861
|
+
{ type: "connection-string", source: `firebase[a-z]*:\\/\\/[^\\s'"]+`, flags: "gi", severity: "high", description: "Firebase URL" },
|
|
862
|
+
// Supabase
|
|
863
|
+
{ type: "api-key", source: "sbp_[a-f0-9]{40}", flags: "g", severity: "critical", description: "Supabase Service Key" },
|
|
864
|
+
{ type: "token", source: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\\.[a-zA-Z0-9_-]{20,}\\.[a-zA-Z0-9_-]{20,}", flags: "g", severity: "high", description: "Supabase Anon/Service JWT" },
|
|
865
|
+
// Vercel
|
|
866
|
+
{ type: "token", source: `(?:VERCEL_TOKEN|VERCEL_API_TOKEN)\\s*[:=]\\s*['"]?([a-zA-Z0-9]{24,})['"]?`, flags: "gi", severity: "critical", description: "Vercel Token" },
|
|
867
|
+
// Heroku
|
|
868
|
+
{ type: "api-key", source: `(?:HEROKU_API_KEY|HEROKU_TOKEN)\\s*[:=]\\s*['"]?([a-f0-9\\-]{36,})['"]?`, flags: "gi", severity: "critical", description: "Heroku API Key" },
|
|
869
|
+
// DigitalOcean
|
|
870
|
+
{ type: "token", source: "dop_v1_[a-f0-9]{64}", flags: "g", severity: "critical", description: "DigitalOcean Personal Access Token" },
|
|
871
|
+
{ type: "token", source: "doo_v1_[a-f0-9]{64}", flags: "g", severity: "critical", description: "DigitalOcean OAuth Token" },
|
|
872
|
+
// Mailgun
|
|
873
|
+
{ type: "api-key", source: "key-[a-zA-Z0-9]{32}", flags: "g", severity: "high", description: "Mailgun API Key" },
|
|
874
|
+
// PII
|
|
875
|
+
{ type: "pii", source: "\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}\\b", flags: "g", severity: "medium", description: "Email Address (PII)" },
|
|
876
|
+
{ type: "pii", source: "\\b(?!000|666|9\\d{2})(\\d{3})[-.]?(?!00)(\\d{2})[-.]?(?!0000)(\\d{4})\\b", flags: "g", severity: "high", description: "Possible SSN (PII)" }
|
|
833
877
|
];
|
|
878
|
+
var _cachedBuiltinPatterns = null;
|
|
879
|
+
function getBuiltinPatterns() {
|
|
880
|
+
if (!_cachedBuiltinPatterns) {
|
|
881
|
+
_cachedBuiltinPatterns = BUILTIN_PATTERNS.map((def) => ({
|
|
882
|
+
type: def.type,
|
|
883
|
+
pattern: new RegExp(def.source, def.flags),
|
|
884
|
+
severity: def.severity,
|
|
885
|
+
description: def.description
|
|
886
|
+
}));
|
|
887
|
+
}
|
|
888
|
+
return _cachedBuiltinPatterns;
|
|
889
|
+
}
|
|
834
890
|
function buildPatterns(customPatterns = []) {
|
|
835
|
-
const
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
severity: def.severity,
|
|
839
|
-
description: def.description
|
|
840
|
-
}));
|
|
891
|
+
const builtins = getBuiltinPatterns();
|
|
892
|
+
if (customPatterns.length === 0) return builtins;
|
|
893
|
+
const patterns = [...builtins];
|
|
841
894
|
for (const custom of customPatterns) {
|
|
842
895
|
try {
|
|
843
896
|
patterns.push({
|
|
@@ -851,7 +904,7 @@ function buildPatterns(customPatterns = []) {
|
|
|
851
904
|
}
|
|
852
905
|
return patterns;
|
|
853
906
|
}
|
|
854
|
-
function scanContentForSecrets(content, filePath, customPatterns = []) {
|
|
907
|
+
function scanContentForSecrets(content, filePath, customPatterns = [], extraPiiSafeDomains) {
|
|
855
908
|
const findings = [];
|
|
856
909
|
const lines = content.split("\n");
|
|
857
910
|
const allPatterns = buildPatterns(customPatterns);
|
|
@@ -863,6 +916,7 @@ function scanContentForSecrets(content, filePath, customPatterns = []) {
|
|
|
863
916
|
while ((match = secretPattern.pattern.exec(line)) !== null) {
|
|
864
917
|
const matchText = match[0];
|
|
865
918
|
if (isTemplateOrPlaceholder(matchText)) continue;
|
|
919
|
+
if (secretPattern.type === "pii" && isSafeEmail(matchText, extraPiiSafeDomains)) continue;
|
|
866
920
|
findings.push({
|
|
867
921
|
type: secretPattern.type,
|
|
868
922
|
file: filePath,
|
|
@@ -910,6 +964,36 @@ function isTemplateOrPlaceholder(value) {
|
|
|
910
964
|
];
|
|
911
965
|
return placeholders.some((p) => p.test(value));
|
|
912
966
|
}
|
|
967
|
+
var PII_SAFE_EMAIL_DOMAINS = /* @__PURE__ */ new Set([
|
|
968
|
+
"example.com",
|
|
969
|
+
"example.org",
|
|
970
|
+
"example.net",
|
|
971
|
+
"test.com",
|
|
972
|
+
"test.org",
|
|
973
|
+
"test.net",
|
|
974
|
+
"localhost",
|
|
975
|
+
"localhost.localdomain",
|
|
976
|
+
"email.com",
|
|
977
|
+
"mail.com",
|
|
978
|
+
"foo.com",
|
|
979
|
+
"bar.com",
|
|
980
|
+
"baz.com",
|
|
981
|
+
"acme.com",
|
|
982
|
+
"company.com",
|
|
983
|
+
"corp.com",
|
|
984
|
+
"noreply.com",
|
|
985
|
+
"no-reply.com",
|
|
986
|
+
"users.noreply.github.com",
|
|
987
|
+
"placeholder.com"
|
|
988
|
+
]);
|
|
989
|
+
function isSafeEmail(value, extraDomains) {
|
|
990
|
+
const match = value.match(/@([a-zA-Z0-9.-]+)$/);
|
|
991
|
+
if (!match) return false;
|
|
992
|
+
const domain = match[1].toLowerCase();
|
|
993
|
+
if (PII_SAFE_EMAIL_DOMAINS.has(domain)) return true;
|
|
994
|
+
if (extraDomains && extraDomains.has(domain)) return true;
|
|
995
|
+
return false;
|
|
996
|
+
}
|
|
913
997
|
function deduplicateFindings(findings) {
|
|
914
998
|
const seen = /* @__PURE__ */ new Set();
|
|
915
999
|
return findings.filter((f) => {
|
|
@@ -923,8 +1007,8 @@ function deduplicateFindings(findings) {
|
|
|
923
1007
|
// src/engine/pruner.ts
|
|
924
1008
|
import { Project as Project2, SyntaxKind as SyntaxKind2 } from "ts-morph";
|
|
925
1009
|
import { readFile as readFile4 } from "fs/promises";
|
|
926
|
-
import { existsSync as
|
|
927
|
-
import { join as
|
|
1010
|
+
import { existsSync as existsSync3 } from "fs";
|
|
1011
|
+
import { join as join5 } from "path";
|
|
928
1012
|
var TS_EXTENSIONS2 = /* @__PURE__ */ new Set(["ts", "tsx", "js", "jsx", "mts", "mjs"]);
|
|
929
1013
|
async function pruneFile(file, level) {
|
|
930
1014
|
if (level === "excluded") {
|
|
@@ -1170,9 +1254,9 @@ function addJSDoc(node, parts) {
|
|
|
1170
1254
|
function findTsConfig(filePath) {
|
|
1171
1255
|
let dir = filePath;
|
|
1172
1256
|
for (let i = 0; i < 10; i++) {
|
|
1173
|
-
dir =
|
|
1174
|
-
const candidate =
|
|
1175
|
-
if (
|
|
1257
|
+
dir = join5(dir, "..");
|
|
1258
|
+
const candidate = join5(dir, "tsconfig.json");
|
|
1259
|
+
if (existsSync3(candidate)) return candidate;
|
|
1176
1260
|
}
|
|
1177
1261
|
return void 0;
|
|
1178
1262
|
}
|
|
@@ -1439,7 +1523,7 @@ async function selectContext(input) {
|
|
|
1439
1523
|
);
|
|
1440
1524
|
const excludedRisk = excludedFiles.length > 0 ? Math.round(excludedFiles.reduce((s, f) => s + f.riskScore, 0) / excludedFiles.length) : 0;
|
|
1441
1525
|
const hashInput = selectedFiles.map((f) => `${f.relativePath}:${f.pruneLevel}`).sort().join("|") + `|budget:${budget}`;
|
|
1442
|
-
const hash =
|
|
1526
|
+
const hash = createHash4("sha256").update(hashInput).digest("hex").substring(0, 16);
|
|
1443
1527
|
return {
|
|
1444
1528
|
files: selectedFiles,
|
|
1445
1529
|
totalTokens: usedTokens,
|
|
@@ -2836,20 +2920,20 @@ function formatCost(cost) {
|
|
|
2836
2920
|
}
|
|
2837
2921
|
|
|
2838
2922
|
// src/govern/audit.ts
|
|
2839
|
-
import { randomUUID, createHash as
|
|
2923
|
+
import { randomUUID, createHash as createHash5 } from "crypto";
|
|
2840
2924
|
import { readdir as readdir3, chmod } from "fs/promises";
|
|
2841
|
-
import { join as
|
|
2925
|
+
import { join as join6 } from "path";
|
|
2842
2926
|
import { userInfo } from "os";
|
|
2843
2927
|
import { homedir } from "os";
|
|
2844
2928
|
var CTO_DIR = ".cto-ai";
|
|
2845
2929
|
var AUDIT_DIR = "audit";
|
|
2846
2930
|
var MAX_ENTRIES_PER_FILE = 500;
|
|
2847
2931
|
function getAuditDir() {
|
|
2848
|
-
return
|
|
2932
|
+
return join6(homedir(), CTO_DIR, AUDIT_DIR);
|
|
2849
2933
|
}
|
|
2850
2934
|
function getCurrentAuditFile() {
|
|
2851
2935
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0].replace(/-/g, "");
|
|
2852
|
-
return
|
|
2936
|
+
return join6(getAuditDir(), `audit_${date}.json`);
|
|
2853
2937
|
}
|
|
2854
2938
|
function computeIntegrityHash(entry) {
|
|
2855
2939
|
const payload = JSON.stringify({
|
|
@@ -2860,7 +2944,7 @@ function computeIntegrityHash(entry) {
|
|
|
2860
2944
|
projectPath: entry.projectPath,
|
|
2861
2945
|
details: entry.details
|
|
2862
2946
|
});
|
|
2863
|
-
return
|
|
2947
|
+
return createHash5("sha256").update(payload).digest("hex");
|
|
2864
2948
|
}
|
|
2865
2949
|
async function ensureDir(dirPath) {
|
|
2866
2950
|
const { mkdir } = await import("fs/promises");
|
|
@@ -2876,9 +2960,9 @@ async function readJSON(filePath) {
|
|
|
2876
2960
|
}
|
|
2877
2961
|
}
|
|
2878
2962
|
async function writeJSON(filePath, data) {
|
|
2879
|
-
const { writeFile } = await import("fs/promises");
|
|
2880
|
-
await ensureDir(
|
|
2881
|
-
await
|
|
2963
|
+
const { writeFile: writeFile2 } = await import("fs/promises");
|
|
2964
|
+
await ensureDir(join6(filePath, ".."));
|
|
2965
|
+
await writeFile2(filePath, JSON.stringify(data, null, 2), "utf-8");
|
|
2882
2966
|
}
|
|
2883
2967
|
async function logAudit(action, projectPath, details = {}) {
|
|
2884
2968
|
const auditDir = getAuditDir();
|