@prodcycle/prodcycle 0.6.0 → 0.6.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.
package/dist/cli.d.ts CHANGED
@@ -13,3 +13,40 @@
13
13
  * by hand.
14
14
  */
15
15
  export declare function isCiEnvironment(env?: NodeJS.ProcessEnv): boolean;
16
+ /**
17
+ * Resolve the files to scan for a `hook` invocation. Supports:
18
+ * - `--file <path>` — read that file from disk
19
+ * - stdin: `{"files": {path: content}}` (same as gate)
20
+ * - stdin: `{"file_path": "...", "content": "..."}` (single file)
21
+ * - stdin: Claude Code PostToolUse shape —
22
+ * `{"tool_input": {"file_path": "...", "content"|"new_string": "..."}}`
23
+ * When only `file_path` is given and we can read the file, we do.
24
+ */
25
+ /**
26
+ * Convert the user-supplied `--file <path>` value into a repo-relative key.
27
+ *
28
+ * The compliance API rejects absolute paths (`File path must be relative`).
29
+ * Two failure modes the naive implementation hit on macOS:
30
+ *
31
+ * 1. Absolute paths under cwd silently became cryptic 400s. Fixed by
32
+ * `path.relative(cwd, absolute)` — works on Linux.
33
+ * 2. macOS `/tmp` is a symlink to `/private/tmp`. `path.resolve()` does
34
+ * NOT follow symlinks, but `process.cwd()` returns the physical path
35
+ * via the kernel's `getcwd()`. Result: `path.resolve('/tmp/repo/x')`
36
+ * = `/tmp/repo/x` while cwd is `/private/tmp/repo`, so the relative
37
+ * path is `../../../tmp/repo/x` and the file is incorrectly rejected
38
+ * as "outside cwd" — exactly the agent-hook scenario this targets.
39
+ * Fix: realpath both sides before comparing.
40
+ *
41
+ * Pure function (no fs I/O of its own, no `process.exit`) so it's directly
42
+ * unit-testable. Caller passes the original input, the realpath of the
43
+ * resolved file (`fs.realpathSync(path.resolve(filePath))`), and the
44
+ * realpath of cwd. Tests construct realpath inputs themselves.
45
+ */
46
+ export declare function resolveHookFileKey(inputPath: string, realpathFile: string, realpathCwd: string): {
47
+ ok: true;
48
+ key: string;
49
+ } | {
50
+ ok: false;
51
+ error: string;
52
+ };
package/dist/cli.js CHANGED
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  })();
36
36
  Object.defineProperty(exports, "__esModule", { value: true });
37
37
  exports.isCiEnvironment = isCiEnvironment;
38
+ exports.resolveHookFileKey = resolveHookFileKey;
38
39
  const commander_1 = require("commander");
39
40
  const child_process_1 = require("child_process");
40
41
  const fs = __importStar(require("fs"));
@@ -237,14 +238,14 @@ program
237
238
  program
238
239
  .command('gate')
239
240
  .description('Evaluate a JSON payload of files from stdin (low-latency hook endpoint)')
240
- .option('--framework <ids>', 'Comma-separated framework IDs to evaluate', 'soc2')
241
+ .option('--framework <ids>', 'Comma-separated framework IDs to evaluate', 'soc2,hipaa,nist-csf')
241
242
  .option('--format <format>', 'Output format: json, sarif, table, prompt', 'prompt')
242
243
  .option('--output <file>', 'Write report to file')
243
244
  .option('--api-url <url>', 'Compliance API base URL (or PC_API_URL env)')
244
245
  .option('--api-key <key>', 'API key for compliance API (or PC_API_KEY env)')
245
246
  .action(async (opts) => {
246
247
  try {
247
- const frameworks = parseList(opts.framework) ?? ['soc2'];
248
+ const frameworks = parseList(opts.framework) ?? ['soc2', 'hipaa', 'nist-csf'];
248
249
  const format = (opts.format ?? 'prompt');
249
250
  const stdin = await readStdin();
250
251
  if (!stdin.trim()) {
@@ -331,7 +332,7 @@ program
331
332
  program
332
333
  .command('hook')
333
334
  .description('Run as coding-agent post-edit hook (reads stdin or --file)')
334
- .option('--framework <ids>', 'Comma-separated framework IDs to evaluate', 'soc2')
335
+ .option('--framework <ids>', 'Comma-separated framework IDs to evaluate', 'soc2,hipaa,nist-csf')
335
336
  .option('--format <format>', 'Output format: json, sarif, table, prompt', 'prompt')
336
337
  .option('--file <path>', 'Scan this file from disk (alternative to reading content from stdin)')
337
338
  .option('--fail-on <levels>', 'Severities that cause non-zero exit', 'critical,high')
@@ -340,7 +341,7 @@ program
340
341
  .option('--api-key <key>', 'API key for compliance API (or PC_API_KEY env)')
341
342
  .action(async (opts) => {
342
343
  try {
343
- const frameworks = parseList(opts.framework) ?? ['soc2'];
344
+ const frameworks = parseList(opts.framework) ?? ['soc2', 'hipaa', 'nist-csf'];
344
345
  const format = (opts.format ?? 'prompt');
345
346
  const files = await collectHookFiles(opts.file);
346
347
  if (!files || Object.keys(files).length === 0) {
@@ -371,6 +372,43 @@ program
371
372
  * `{"tool_input": {"file_path": "...", "content"|"new_string": "..."}}`
372
373
  * When only `file_path` is given and we can read the file, we do.
373
374
  */
375
+ /**
376
+ * Convert the user-supplied `--file <path>` value into a repo-relative key.
377
+ *
378
+ * The compliance API rejects absolute paths (`File path must be relative`).
379
+ * Two failure modes the naive implementation hit on macOS:
380
+ *
381
+ * 1. Absolute paths under cwd silently became cryptic 400s. Fixed by
382
+ * `path.relative(cwd, absolute)` — works on Linux.
383
+ * 2. macOS `/tmp` is a symlink to `/private/tmp`. `path.resolve()` does
384
+ * NOT follow symlinks, but `process.cwd()` returns the physical path
385
+ * via the kernel's `getcwd()`. Result: `path.resolve('/tmp/repo/x')`
386
+ * = `/tmp/repo/x` while cwd is `/private/tmp/repo`, so the relative
387
+ * path is `../../../tmp/repo/x` and the file is incorrectly rejected
388
+ * as "outside cwd" — exactly the agent-hook scenario this targets.
389
+ * Fix: realpath both sides before comparing.
390
+ *
391
+ * Pure function (no fs I/O of its own, no `process.exit`) so it's directly
392
+ * unit-testable. Caller passes the original input, the realpath of the
393
+ * resolved file (`fs.realpathSync(path.resolve(filePath))`), and the
394
+ * realpath of cwd. Tests construct realpath inputs themselves.
395
+ */
396
+ function resolveHookFileKey(inputPath, realpathFile, realpathCwd) {
397
+ if (!path.isAbsolute(inputPath)) {
398
+ // Relative input passes through verbatim — no symlink ambiguity.
399
+ return { ok: true, key: inputPath };
400
+ }
401
+ const relative = path.relative(realpathCwd, realpathFile);
402
+ if (relative.startsWith('..') || path.isAbsolute(relative)) {
403
+ return {
404
+ ok: false,
405
+ error: `hook: --file ${inputPath} is outside the current directory ` +
406
+ `(${realpathCwd}). Pass a path relative to the repo root, or ` +
407
+ `cd into the repo first.`,
408
+ };
409
+ }
410
+ return { ok: true, key: relative };
411
+ }
374
412
  async function collectHookFiles(filePath) {
375
413
  if (filePath) {
376
414
  const absolute = path.resolve(filePath);
@@ -379,7 +417,17 @@ async function collectHookFiles(filePath) {
379
417
  process.exit(2);
380
418
  }
381
419
  const content = fs.readFileSync(absolute, 'utf8');
382
- return { [filePath]: content };
420
+ // Realpath both sides so the macOS `/tmp → /private/tmp` symlink doesn't
421
+ // make a valid agent-hook path (e.g. `/tmp/repo/main.tf`) appear outside
422
+ // cwd. See `resolveHookFileKey` JSDoc for the full rationale.
423
+ const realpathFile = fs.realpathSync(absolute);
424
+ const realpathCwd = fs.realpathSync(process.cwd());
425
+ const resolved = resolveHookFileKey(filePath, realpathFile, realpathCwd);
426
+ if (!resolved.ok) {
427
+ console.error(resolved.error);
428
+ process.exit(2);
429
+ }
430
+ return { [resolved.key]: content };
383
431
  }
384
432
  const stdin = await readStdin();
385
433
  if (!stdin.trim()) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prodcycle/prodcycle",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "Multi-framework policy-as-code compliance scanner for infrastructure and application code.",
5
5
  "homepage": "https://docs.prodcycle.com",
6
6
  "repository": {