@vibekiln/cutline-mcp-cli 0.3.0 → 0.4.1

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.
@@ -10,7 +10,6 @@ export async function deleteRefreshToken() {
10
10
  const config = loadConfig();
11
11
  if (!config.refreshToken)
12
12
  return false;
13
- delete config.refreshToken;
14
- saveConfig(config);
13
+ saveConfig({ ...config, refreshToken: undefined });
15
14
  return true;
16
15
  }
@@ -4365,6 +4365,39 @@ var UNIVERSAL_CONSTRAINTS = [
4365
4365
  file_patterns: ["**/api/auth/**", "**/api/login*", "**/api/callback*", "**/api/checkout*", "**/middleware/**"],
4366
4366
  framework: "baseline"
4367
4367
  },
4368
+ {
4369
+ id_suffix: "sensitive_tokens_not_in_urls",
4370
+ category: "security",
4371
+ summary: "Auth/session/API tokens MUST NOT be passed in URL query parameters during redirects. Sensitive tokens in URLs leak via logs, browser history, and referrers.",
4372
+ keywords: ["token", "query-param", "callback", "returnUrl", "redirect", "referrer", "url-leakage"],
4373
+ severity: "critical",
4374
+ action: "Use Authorization headers or httpOnly cookies for token transport. Validate callback/return URLs against an allowlist and never append bearer/session tokens to redirected URLs.",
4375
+ checklist_ref: "D11",
4376
+ file_patterns: ["**/auth/**", "**/api/auth/**", "**/api/**/checkout*", "**/mcp-auth*", "**/session/**", "**/middleware/**"],
4377
+ framework: "baseline"
4378
+ },
4379
+ {
4380
+ id_suffix: "no_secrets_in_query_params",
4381
+ category: "security",
4382
+ summary: "Secrets (revalidation secrets, API secrets, webhook secrets) MUST NOT be transported via URL query parameters. Query-string secrets are leaked through logs, referrers, and browser history.",
4383
+ keywords: ["secret", "query-param", "url", "revalidate", "webhook", "referrer", "leakage"],
4384
+ severity: "critical",
4385
+ action: "Accept secrets only via headers or signed request bodies. Reject secret-bearing query params in production endpoints. Rotate any secret previously sent in URLs.",
4386
+ checklist_ref: "D12",
4387
+ file_patterns: ["**/api/**", "**/webhooks/**", "**/revalidate/**", "**/middleware/**"],
4388
+ framework: "baseline"
4389
+ },
4390
+ {
4391
+ id_suffix: "no_state_change_get_cookie_auth",
4392
+ category: "security",
4393
+ summary: "State-changing operations MUST NOT be reachable via GET when cookie authentication is accepted. GET + cookie auth creates CSRF risk.",
4394
+ keywords: ["csrf", "get", "state-change", "cookie-auth", "origin-check", "referer", "method-safety"],
4395
+ severity: "critical",
4396
+ action: "Use POST/PUT/DELETE for side effects. If GET fallback is unavoidable, enforce strict same-origin checks (Origin/Referer/sec-fetch-site) and avoid cookie-based auth fallback where possible.",
4397
+ checklist_ref: "D13",
4398
+ file_patterns: ["**/api/**", "**/auth/**", "**/checkout/**", "**/middleware/**"],
4399
+ framework: "baseline"
4400
+ },
4368
4401
  {
4369
4402
  id_suffix: "ai_cost_caps",
4370
4403
  category: "security",
@@ -5551,6 +5584,9 @@ D7. Do sensitive actions (account deletion, email change, role escalation) requi
5551
5584
  D8. Is payment/billing logic validated server-side? Can prices or quantities be tampered with client-side?
5552
5585
  D9. Are redirect URLs validated against an allowlist? Can open redirects be exploited for phishing?
5553
5586
  D10. Are webhook signatures verified before processing payment or event data?
5587
+ D11. Are auth/session/API tokens kept out of URL query params (including callback/returnUrl redirects) and transported via headers or httpOnly cookies instead?
5588
+ D12. Are secrets (revalidate/API/webhook/etc.) kept out of URL query params and accepted only via headers or signed bodies?
5589
+ D13. Are side-effecting endpoints using non-GET methods, and are cookie-auth GET fallbacks protected with strict same-origin checks?
5554
5590
 
5555
5591
  ### E. Security Rules & Infrastructure
5556
5592
  E1. For Firestore/database rules: do they enforce per-user data isolation?
@@ -5629,7 +5665,7 @@ Return a JSON object with exactly these fields:
5629
5665
  - targetUsers (string): Who uses this product, from a security perspective.
5630
5666
  - referenceClasses (string[]): Security frameworks or standards that apply (e.g., "OWASP Top 10 2021", "SOC 2 Type II").
5631
5667
  - constraints (object?): Resource constraints \u2014 team, budget_usd, deadline_days, must_ship_scope.
5632
- - checklist_summary (object): Keys are checklist IDs (A1-A8, B1-B6, C1-C7, D1-D8, E1-E4, F1-F3, G-*, H1-H3, I1-I8, J1-J6, K1-K8), values are "pass"|"fail"|"warn"|"not_applicable". This forces systematic coverage.
5668
+ - checklist_summary (object): Keys are checklist IDs (A1-A8, B1-B6, C1-C8, D1-D13, E1-E7, F1-F4, G-*, H1-H3, I1-I8, J1-J6, K1-K8), values are "pass"|"fail"|"warn"|"not_applicable". This forces systematic coverage.
5633
5669
  - compliance_signals (array of {framework: "pci_dss"|"hipaa"|"fedramp"|"gdpr_ccpa"|"owasp_llm"|"glba"|"ferpa_coppa"|"csa_ccm"|"ios_app_store", signal: string, confidence: number}?): Detected compliance framework signals. Return [] if none.
5634
5670
 
5635
5671
  Be concrete and specific. Reference file paths and line numbers where possible. If a checklist item cannot be assessed from the provided files, mark it "not_applicable" and note why. Cover ALL sections A through K.`;
@@ -5855,6 +5891,11 @@ var SECURITY_PATH_PATTERNS = [
5855
5891
  /\/permissions?/i,
5856
5892
  /\/roles?/i,
5857
5893
  /\/tokens?/i,
5894
+ /\/redirect/i,
5895
+ /\/callback/i,
5896
+ /returnurl/i,
5897
+ /\/mcp-auth/i,
5898
+ /checkout-link/i,
5858
5899
  /\/csrf/i,
5859
5900
  /\/rate-limit/i,
5860
5901
  /\/encrypt/i,
@@ -6538,6 +6579,11 @@ var SECURITY_PATH_PATTERNS2 = [
6538
6579
  /billing/i,
6539
6580
  /stripe/i,
6540
6581
  /webhook/i,
6582
+ /redirect/i,
6583
+ /callback/i,
6584
+ /returnurl/i,
6585
+ /mcp-auth/i,
6586
+ /checkout-link/i,
6541
6587
  // Scalability & reliability patterns
6542
6588
  /\/db\//i,
6543
6589
  /queries?\//i,
@@ -7047,7 +7093,7 @@ function deltaStr(current, previous) {
7047
7093
  return " (no change)";
7048
7094
  return diff > 0 ? ` (**+${diff}** since last scan)` : ` (**${diff}** since last scan)`;
7049
7095
  }
7050
- function formatAuditOutput(result, reportId, publicSiteUrl = "https://thecutline.ai", hiddenAuditDimensions = []) {
7096
+ function formatAuditOutput(result, reportId, publicSiteUrl = "https://thecutline.ai", hiddenAuditDimensions = [], fullReport = false) {
7051
7097
  const m = result.metrics;
7052
7098
  const p = result.previousMetrics;
7053
7099
  const isRescan = !!p;
@@ -7135,24 +7181,32 @@ function formatAuditOutput(result, reportId, publicSiteUrl = "https://thecutline
7135
7181
  const totalFindings = visibleFindings.length;
7136
7182
  const criticalCount = visibleFindings.filter((g) => g.severity === "critical" || g.severity === "high").length;
7137
7183
  if (totalFindings > 0) {
7138
- const topFinding = visibleFindings[0];
7139
- lines.push(``, `## #1 Finding \u2014 Fix This Now`, ``, `**[${topFinding.severity.toUpperCase()}] ${topFinding.title}**`, `*Category: ${topFinding.category}*`, ``, topFinding.description || "Address this finding to improve your readiness scores.", ``, `> Fix this issue, then re-run \`code_audit\` to see your scores improve.`);
7140
- const remaining = visibleFindings.slice(1);
7141
- if (remaining.length > 0) {
7142
- lines.push(``, `## ${remaining.length} More Finding${remaining.length > 1 ? "s" : ""} Detected`);
7143
- const teaserLimit = Math.min(remaining.length, 5);
7144
- for (let i = 0; i < teaserLimit; i++) {
7145
- lines.push(`- [${remaining[i].severity.toUpperCase()}] ${remaining[i].title}`);
7184
+ if (fullReport) {
7185
+ lines.push(``, `## Findings`, ``);
7186
+ for (const finding of visibleFindings) {
7187
+ lines.push(`**[${finding.severity.toUpperCase()}] ${finding.title}**`, `*Category: ${finding.category}*`, finding.description || "Address this finding to improve your readiness scores.", ``);
7146
7188
  }
7147
- if (remaining.length > teaserLimit) {
7148
- lines.push(`- ... and **${remaining.length - teaserLimit} more**`);
7189
+ lines.push(`> Re-run \`code_audit\` after fixes to measure score improvements.`);
7190
+ } else {
7191
+ const topFinding = visibleFindings[0];
7192
+ lines.push(``, `## #1 Finding \u2014 Fix This Now`, ``, `**[${topFinding.severity.toUpperCase()}] ${topFinding.title}**`, `*Category: ${topFinding.category}*`, ``, topFinding.description || "Address this finding to improve your readiness scores.", ``, `> Fix this issue, then re-run \`code_audit\` to see your scores improve.`);
7193
+ const remaining = visibleFindings.slice(1);
7194
+ if (remaining.length > 0) {
7195
+ lines.push(``, `## ${remaining.length} More Finding${remaining.length > 1 ? "s" : ""} Detected`);
7196
+ const teaserLimit = Math.min(remaining.length, 5);
7197
+ for (let i = 0; i < teaserLimit; i++) {
7198
+ lines.push(`- [${remaining[i].severity.toUpperCase()}] ${remaining[i].title}`);
7199
+ }
7200
+ if (remaining.length > teaserLimit) {
7201
+ lines.push(`- ... and **${remaining.length - teaserLimit} more**`);
7202
+ }
7149
7203
  }
7150
7204
  }
7151
7205
  }
7152
7206
  lines.push(``, `---`, ``);
7153
- if (totalFindings > 1) {
7207
+ if (!fullReport && totalFindings > 1) {
7154
7208
  lines.push(`### Unlock Full Analysis`, ``, `You fixed one \u2014 **${totalFindings - 1} findings** remain (${criticalCount} critical/high).`, `Upgrade to Cutline Pro for:`, `- Full details and remediation for every finding`, `- Prioritized RGR remediation plans your coding agent can execute`, `- Product-specific deep dive with feature-level constraint mapping`, `- Continuous score tracking with this audit as a baseline prior`, ``, `\u2192 Run \`cutline-mcp upgrade\` or visit **https://thecutline.ai/upgrade**`);
7155
- } else if (totalFindings === 1) {
7209
+ } else if (totalFindings >= 1) {
7156
7210
  lines.push(`### Next Steps`, ``, `Fix the finding above, then re-scan to see your scores improve.`, `When ready, run a **deep dive** for product-specific analysis \u2014`, `these generic scores will serve as a prior for your product graph.`);
7157
7211
  } else {
7158
7212
  lines.push(`### Next Steps`, ``, `No critical findings detected. Run a **deep dive** for product-specific`, `analysis with feature coverage and the generic scores as a prior.`);
@@ -8377,15 +8431,17 @@ Why AI: ${idea.whyAI}`
8377
8431
  return "engineering";
8378
8432
  return "security";
8379
8433
  };
8434
+ let hasPremiumSubscription = false;
8380
8435
  {
8381
8436
  const rateInfo = await getScanRateLimit();
8437
+ hasPremiumSubscription = rateInfo.subscription === "active" || rateInfo.subscription === "trialing";
8382
8438
  const now = /* @__PURE__ */ new Date();
8383
8439
  const resetAt = rateInfo.periodStart ? new Date(rateInfo.periodStart) : /* @__PURE__ */ new Date(0);
8384
8440
  const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);
8385
8441
  if (resetAt < monthStart) {
8386
8442
  await updateScanRateLimit({ free_scan_counter: { count: 1, reset_at: now.toISOString() } });
8387
8443
  } else if (rateInfo.scanCount >= 3) {
8388
- if (rateInfo.subscription !== "active" && rateInfo.subscription !== "trialing") {
8444
+ if (!hasPremiumSubscription) {
8389
8445
  throw new McpError(ErrorCode.InvalidRequest, "Free scan limit reached (3/month). Run `cutline-mcp upgrade` in your terminal, or visit https://thecutline.ai/upgrade");
8390
8446
  }
8391
8447
  } else {
@@ -8454,7 +8510,7 @@ Why AI: ${idea.whyAI}`
8454
8510
  console.error("[code_audit] Report persistence failed (non-fatal):", e);
8455
8511
  }
8456
8512
  return {
8457
- content: [{ type: "text", text: formatAuditOutput(result, reportId, reportSiteUrl, hiddenAuditDimensions) }]
8513
+ content: [{ type: "text", text: formatAuditOutput(result, reportId, reportSiteUrl, hiddenAuditDimensions, hasPremiumSubscription) }]
8458
8514
  };
8459
8515
  }
8460
8516
  const authCtx = await resolveAuthContext(args.auth_token);
@@ -12,6 +12,13 @@ export function saveConfig(config) {
12
12
  ensureConfigDir();
13
13
  const current = loadConfig();
14
14
  const newConfig = { ...current, ...config };
15
+ // Treat explicit undefined values as key deletion so callers can clear
16
+ // persisted fields (for example during logout).
17
+ for (const [key, value] of Object.entries(config)) {
18
+ if (value === undefined) {
19
+ delete newConfig[key];
20
+ }
21
+ }
15
22
  // Remove legacy fields that are no longer stored (e.g. firebaseApiKey)
16
23
  // to prevent stale values from causing cross-project auth mismatches
17
24
  delete newConfig.firebaseApiKey;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibekiln/cutline-mcp-cli",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "CLI and MCP servers for Cutline — authenticate, then run constraint-aware MCP servers in Cursor or any MCP client.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",