ship-safe 9.1.2 → 9.2.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.
@@ -142,6 +142,11 @@ const PATTERNS = [
142
142
  confidence: 'medium',
143
143
  description: 'System prompt hardcoded in code. If client-side, users can extract it.',
144
144
  fix: 'Keep system prompts server-side only. Load from environment variables or config.',
145
+ // Skip clearly server-side files where defining a system prompt is correct.
146
+ // The rule is only meaningful for code that ships to a browser/client.
147
+ skipFile: (f) => /(?:^|\/)(?:cli|server|backend|api|lib|services|workers|jobs|scripts)\//.test(f.replace(/\\/g, '/'))
148
+ || /\.(?:server|api)\.(?:js|ts|mjs|cjs|tsx)$/.test(f)
149
+ || /\/api\//.test(f.replace(/\\/g, '/')),
145
150
  },
146
151
 
147
152
  // ── LLM10: Unbounded Consumption ───────────────────────────────────────────
@@ -219,12 +224,24 @@ const PATTERNS = [
219
224
  {
220
225
  rule: 'PROMPT_INJECTION_PATTERN',
221
226
  title: 'Known Prompt Injection Pattern',
222
- regex: /(?:ignore\s+(?:all\s+)?previous\s+instructions|disregard\s+(?:all\s+)?(?:previous|prior)|you\s+are\s+now\s+DAN|system\s*prompt|jailbreak|bypass\s+(?:your|the)\s+(?:rules|instructions|guidelines))/gi,
227
+ // The phrase "system prompt" is *not* an injection attack — it's how every
228
+ // LLM developer talks about prompts. Match the actual jailbreak verbs instead.
229
+ regex: /(?:ignore\s+(?:all\s+)?previous\s+instructions|disregard\s+(?:all\s+)?(?:previous|prior)|you\s+are\s+now\s+DAN|jailbreak\s+(?:the|this)|bypass\s+(?:your|the)\s+(?:rules|instructions|guidelines)|reveal\s+your\s+system\s+prompt)/gi,
223
230
  severity: 'high',
224
231
  cwe: 'CWE-77',
225
232
  owasp: 'LLM01',
226
233
  description: 'Known prompt injection pattern detected in code. Ensure this is for testing only.',
227
234
  fix: 'If in test data, add # ship-safe-ignore. If in user-facing code, add input filtering.',
235
+ // Skip files where the pattern appears intentionally: tests, red-team rules,
236
+ // detection-rule definitions, and security tool source code.
237
+ skipFile: (f) => {
238
+ const p = f.replace(/\\/g, '/');
239
+ return /__tests__\//.test(p)
240
+ || /\.(?:test|spec)\.(?:js|ts|mjs|cjs|tsx|jsx)$/.test(p)
241
+ || /(?:^|\/)(?:red-?team|llm-?redteam|prompt-?injection|memory-?poisoning|jailbreak)/.test(p)
242
+ || /\/agents\/[^/]*(?:redteam|injection|llm)/i.test(p)
243
+ || /(?:scan-playbook|threat-intel|patterns)\.(?:js|ts)$/.test(p);
244
+ },
228
245
  },
229
246
  ];
230
247
 
@@ -242,7 +259,12 @@ export class LLMRedTeam extends BaseAgent {
242
259
 
243
260
  let findings = [];
244
261
  for (const file of codeFiles) {
245
- findings = findings.concat(this.scanFileWithPatterns(file, PATTERNS));
262
+ // Honor per-pattern skipFile predicates so rules that are clearly false
263
+ // positives in known contexts (server-side prompts, redteam test data)
264
+ // never get sent to the agent for "fixing".
265
+ const applicable = PATTERNS.filter(p => !p.skipFile || !p.skipFile(file));
266
+ if (applicable.length === 0) continue;
267
+ findings = findings.concat(this.scanFileWithPatterns(file, applicable));
246
268
  }
247
269
  return findings;
248
270
  }
@@ -29,6 +29,9 @@ import { mcpCommand } from '../commands/mcp.js';
29
29
  import { remediateCommand } from '../commands/remediate.js';
30
30
  import { rotateCommand } from '../commands/rotate.js';
31
31
  import { agentCommand } from '../commands/agent.js';
32
+ import { agentFixCommand } from '../commands/agent-fix.js';
33
+ import { undoCommand } from '../commands/undo.js';
34
+ import { shellCommand } from '../commands/shell.js';
32
35
  import { depsCommand } from '../commands/deps.js';
33
36
  import { scoreCommand } from '../commands/score.js';
34
37
  import { redTeamCommand } from '../commands/red-team.js';
@@ -184,10 +187,46 @@ program
184
187
  // -----------------------------------------------------------------------------
185
188
  program
186
189
  .command('agent [path]')
187
- .description('AI-powered security audit: scan, classify with Claude, auto-remediate confirmed secrets')
188
- .option('--dry-run', 'Show classification and plan without writing any files')
189
- .option('--model <model>', `Claude model to use (default: ${DEFAULT_MODEL})`)
190
- .action(agentCommand);
190
+ .description('Interactive security agent: scan, plan each fix, ask before changing, verify the fix worked')
191
+ .option('--plan-only', 'Generate plans for review but never write changes')
192
+ .option('--severity <level>', 'Minimum severity to fix (critical|high|medium|low)', 'low')
193
+ .option('--provider <name>', 'LLM provider: deepseek-flash | deepseek | openai | kimi | anthropic')
194
+ .option('--model <model>', 'Specific model name to use')
195
+ .option('--think', 'Enable extended thinking (GPT-5.5 reasoning_effort:high, Claude extended thinking)')
196
+ .option('--allow-dirty', 'Allow running with uncommitted changes in the working tree')
197
+ .option('--branch [name]', 'Create a branch and commit one fix per file (default name: ship-safe/fixes-<timestamp>)')
198
+ .option('--pr', 'After fixing, push the branch and open a pull request via gh CLI (requires --branch)')
199
+ .option('--yolo', 'Auto-accept every plan without prompting (use with caution; pairs well with --branch)')
200
+ .option('--auto-low', 'Auto-accept plans marked risk:low; prompt for medium/high')
201
+ .option('--sandbox', 'Verify each fix in a Docker sandbox (not yet implemented)')
202
+ .option('--legacy', 'Use the legacy non-interactive Claude-only agent')
203
+ .action((targetPath, options) => {
204
+ if (options.legacy) {
205
+ return agentCommand(targetPath, options);
206
+ }
207
+ return agentFixCommand(targetPath, options);
208
+ });
209
+
210
+ // -----------------------------------------------------------------------------
211
+ // UNDO COMMAND
212
+ // -----------------------------------------------------------------------------
213
+ program
214
+ .command('undo [path]')
215
+ .description('Revert the last fix applied by `ship-safe agent` (or all fixes with --all)')
216
+ .option('--all', 'Revert every fix in the log instead of just the last one')
217
+ .option('--dry-run', 'Show what would be reverted without writing anything')
218
+ .action(undoCommand);
219
+
220
+ // -----------------------------------------------------------------------------
221
+ // SHELL COMMAND
222
+ // -----------------------------------------------------------------------------
223
+ program
224
+ .command('shell [path]')
225
+ .description('Interactive REPL: scan, fix, ask questions — all in one session')
226
+ .option('--provider <name>', 'LLM provider: deepseek-flash | deepseek | openai | kimi | anthropic')
227
+ .option('--model <model>', 'Specific model name to use')
228
+ .option('--think', 'Enable extended thinking mode')
229
+ .action(shellCommand);
191
230
 
192
231
  // -----------------------------------------------------------------------------
193
232
  // DEPS COMMAND
@@ -226,6 +265,7 @@ program
226
265
  .option('--baseline', 'Only show findings not in the baseline')
227
266
  .option('--pdf [file]', 'Generate PDF report (requires Chrome/Chromium)')
228
267
  .option('--deep', 'LLM-powered taint analysis for critical/high findings')
268
+ .option('--think', 'Enable extended thinking mode (GPT-5.5 reasoning_effort:high, Claude extended thinking)')
229
269
  .option('--local', 'Use local Ollama model for deep analysis (default: llama3.2)')
230
270
  .option('--model <model>', 'LLM model to use for deep/AI analysis')
231
271
  .option('--provider <name>', 'LLM provider: anthropic, openai, google, ollama, groq, together, mistral, cohere, deepseek, xai, kimi, lmstudio')
@@ -266,7 +306,8 @@ program
266
306
  .option('--no-deps', 'Skip dependency audit')
267
307
  .option('--no-ai', 'Skip AI classification')
268
308
  .option('--deep', 'LLM-powered taint analysis for critical/high findings')
269
- .option('--swarm', 'Use Kimi K2.6 native 300-agent swarm instead of local agent execution (requires MOONSHOT_API_KEY)')
309
+ .option('--swarm', 'Use AI swarm mode 23 parallel agents via DeepSeek V4 Flash or Kimi K2.6 (requires DEEPSEEK_API_KEY or MOONSHOT_API_KEY)')
310
+ .option('--think', 'Enable extended thinking mode (GPT-5.5 reasoning_effort:high, Claude extended thinking)')
270
311
  .option('--local', 'Use local Ollama model for deep analysis (default: llama3.2)')
271
312
  .option('--model <model>', 'LLM model for deep analysis')
272
313
  .option('--provider <name>', 'LLM provider: anthropic, openai, google, ollama, groq, together, mistral, cohere, deepseek, xai, kimi, lmstudio')
@@ -619,8 +660,13 @@ How it works:
619
660
  // PARSE AND RUN
620
661
  // -----------------------------------------------------------------------------
621
662
 
622
- // Show help if no command provided
623
- if (process.argv.length === 2) {
663
+ // No command + interactive TTY → drop into the REPL.
664
+ // Help banner is still available via `--help` and shown when stdin is piped.
665
+ if (process.argv.length === 2 && process.stdin.isTTY) {
666
+ // Await shell before exiting; do NOT fall through to program.parse() or it
667
+ // will print the help banner concurrently with the REPL banner.
668
+ shellCommand('.', {}).then(() => process.exit(0)).catch(() => process.exit(1));
669
+ } else if (process.argv.length === 2) {
624
670
  console.log(banner);
625
671
  console.log(chalk.yellow('\nQuick start:\n'));
626
672
  console.log(chalk.cyan.bold(' v9.0 — Agent Studio, Teams & Findings'));
@@ -663,6 +709,6 @@ if (process.argv.length === 2) {
663
709
  console.log(chalk.white('\n npx ship-safe --help ') + chalk.gray('# Show all options'));
664
710
  console.log();
665
711
  process.exit(0);
712
+ } else {
713
+ program.parse();
666
714
  }
667
-
668
- program.parse();