release-skill 0.1.1 → 0.1.4

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.
Files changed (108) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +2 -2
  3. package/.codex-plugin/plugin.json +3 -3
  4. package/CHANGELOG.md +89 -0
  5. package/INSTALL.md +216 -5
  6. package/INSTALL.zh-CN.md +358 -0
  7. package/README.md +411 -67
  8. package/README.zh-CN.md +377 -59
  9. package/adapters/claude/.claude-plugin/marketplace.json +1 -1
  10. package/adapters/claude/.claude-plugin/plugin.json +1 -1
  11. package/adapters/claude/bin/release-skill.bundle.mjs +79284 -0
  12. package/adapters/claude/bin/release-skill.mjs +34 -0
  13. package/adapters/claude/native/safe-write/binding.gyp +40 -0
  14. package/adapters/claude/native/safe-write/prebuilds/darwin-arm64/safe_write.node +0 -0
  15. package/adapters/claude/native/safe-write/prebuilds.json +24 -0
  16. package/adapters/claude/native/safe-write/src/safe_write.cc +2032 -0
  17. package/adapters/claude/schemas/.render-manifest.json +37 -0
  18. package/adapters/claude/schemas/approval-record.schema.json +115 -0
  19. package/adapters/claude/schemas/artifact-lock.schema.json +111 -0
  20. package/adapters/claude/schemas/artifact-plan.schema.json +52 -0
  21. package/adapters/claude/schemas/artifact-policy.schema.json +76 -0
  22. package/adapters/claude/schemas/evidence-event.schema.json +89 -0
  23. package/adapters/claude/schemas/release-plan.schema.json +860 -0
  24. package/adapters/claude/schemas/release-project.schema.json +736 -0
  25. package/adapters/claude/schemas/release-run.schema.json +342 -0
  26. package/adapters/claude/skills/release-assess/SKILL.md +5 -6
  27. package/adapters/claude/skills/release-help/SKILL.md +14 -18
  28. package/adapters/claude/skills/release-prepare/SKILL.md +16 -6
  29. package/adapters/claude/skills/release-publish/SKILL.md +7 -7
  30. package/adapters/claude/skills/release-reconcile/SKILL.md +6 -6
  31. package/adapters/claude/skills/release-setup/SKILL.md +95 -0
  32. package/adapters/claude/skills/release-verify/SKILL.md +7 -7
  33. package/adapters/codex/.codex-plugin/plugin.json +2 -2
  34. package/adapters/codex/bin/release-skill.bundle.mjs +79284 -0
  35. package/adapters/codex/bin/release-skill.mjs +34 -0
  36. package/adapters/codex/native/safe-write/binding.gyp +40 -0
  37. package/adapters/codex/native/safe-write/prebuilds/darwin-arm64/safe_write.node +0 -0
  38. package/adapters/codex/native/safe-write/prebuilds.json +24 -0
  39. package/adapters/codex/native/safe-write/src/safe_write.cc +2032 -0
  40. package/adapters/codex/schemas/.render-manifest.json +37 -0
  41. package/adapters/codex/schemas/approval-record.schema.json +115 -0
  42. package/adapters/codex/schemas/artifact-lock.schema.json +111 -0
  43. package/adapters/codex/schemas/artifact-plan.schema.json +52 -0
  44. package/adapters/codex/schemas/artifact-policy.schema.json +76 -0
  45. package/adapters/codex/schemas/evidence-event.schema.json +89 -0
  46. package/adapters/codex/schemas/release-plan.schema.json +860 -0
  47. package/adapters/codex/schemas/release-project.schema.json +736 -0
  48. package/adapters/codex/schemas/release-run.schema.json +342 -0
  49. package/adapters/codex/skills/release-assess/SKILL.md +12 -6
  50. package/adapters/codex/skills/release-help/SKILL.md +21 -18
  51. package/adapters/codex/skills/release-prepare/SKILL.md +23 -6
  52. package/adapters/codex/skills/release-publish/SKILL.md +14 -7
  53. package/adapters/codex/skills/release-reconcile/SKILL.md +13 -6
  54. package/adapters/codex/skills/release-setup/SKILL.md +102 -0
  55. package/adapters/codex/skills/release-verify/SKILL.md +14 -7
  56. package/bin/release-skill-cli.mjs +807 -0
  57. package/bin/release-skill.bundle.mjs +79284 -0
  58. package/bin/release-skill.mjs +23 -732
  59. package/native/safe-write/prebuilds/darwin-arm64/safe_write.node +0 -0
  60. package/native/safe-write/prebuilds.json +22 -2
  61. package/native/safe-write/src/safe_write.cc +11 -2
  62. package/package.json +8 -2
  63. package/references/02-project-config.md +55 -4
  64. package/references/05-evidence-and-errors.md +6 -2
  65. package/schemas/release-plan.schema.json +556 -65
  66. package/schemas/release-project.schema.json +406 -29
  67. package/schemas/release-run.schema.json +165 -18
  68. package/scripts/build-bundle.mjs +133 -0
  69. package/skills/release-assess/SKILL.md +5 -6
  70. package/skills/release-help/SKILL.md +14 -18
  71. package/skills/release-prepare/SKILL.md +16 -6
  72. package/skills/release-publish/SKILL.md +7 -7
  73. package/skills/release-reconcile/SKILL.md +6 -6
  74. package/skills/release-setup/SKILL.md +95 -0
  75. package/skills/release-verify/SKILL.md +7 -7
  76. package/skills-src/release-assess/SKILL.md +5 -6
  77. package/skills-src/release-help/SKILL.md +14 -18
  78. package/skills-src/release-prepare/SKILL.md +16 -6
  79. package/skills-src/release-publish/SKILL.md +7 -7
  80. package/skills-src/release-reconcile/SKILL.md +6 -6
  81. package/skills-src/release-setup/SKILL.md +95 -0
  82. package/skills-src/release-verify/SKILL.md +7 -7
  83. package/src/adapters/contract.mjs +3 -0
  84. package/src/adapters/git-github.mjs +84 -2
  85. package/src/adapters/npm.mjs +5 -13
  86. package/src/adapters/plugin-marketplace.mjs +132 -52
  87. package/src/adapters/push-snapshot.mjs +84 -17
  88. package/src/artifacts/policy.mjs +4 -7
  89. package/src/artifacts/safe-fs-backend-internal.mjs +69 -21
  90. package/src/commands/prepare.mjs +244 -20
  91. package/src/commands/publish.mjs +46 -0
  92. package/src/commands/reconcile.mjs +152 -0
  93. package/src/commands/setup.mjs +1525 -0
  94. package/src/commands/verify.mjs +122 -26
  95. package/src/core/approval.mjs +4 -6
  96. package/src/core/config.mjs +42 -8
  97. package/src/core/errors.mjs +4 -0
  98. package/src/core/pkg-root.mjs +22 -0
  99. package/src/core/plan.mjs +132 -4
  100. package/src/core/previous-public-baseline.mjs +21 -1
  101. package/src/core/run.mjs +4 -4
  102. package/src/core/trusted-resource.mjs +96 -0
  103. package/src/core/verification-gates.mjs +451 -0
  104. package/src/docs/version-gate.mjs +164 -0
  105. package/src/producers/build-adapters.mjs +512 -55
  106. package/src/snapshot/frozen.mjs +221 -7
  107. package/src/snapshot/public-map.mjs +7 -4
  108. package/src/snapshot/scan.mjs +2 -1
@@ -169,14 +169,66 @@ export async function verifyFrozenDirectoryStructure(directory, label = 'frozen
169
169
  await walk(directory);
170
170
  }
171
171
 
172
- export async function verifyFrozenGitRepository({ root, gitObjectDir, commit, tree, exec = execFile }) {
172
+ /**
173
+ * Verify an already-frozen Git object directory against the plan.
174
+ *
175
+ * Consumption only: this never rebuilds objects and never reads the wall
176
+ * clock. When `commitTimestamp` (the plan's `frozenSnapshot.commitTimestamp`)
177
+ * is provided, the frozen commit's real `%aI`/`%cI` dates must match it
178
+ * exactly, so a commit carrying any other author/committer time can never
179
+ * pass pre-publish verification.
180
+ */
181
+ export async function verifyFrozenGitRepository({
182
+ root,
183
+ gitObjectDir,
184
+ commit,
185
+ tree,
186
+ parentCommit,
187
+ commitTimestamp,
188
+ exec = execFile,
189
+ }) {
173
190
  const gitDir = await resolveFrozenPath(root, gitObjectDir, 'frozen git object directory');
174
191
  await verifyFrozenDirectoryStructure(gitDir, 'frozen git object directory');
175
192
  const { stdout } = await exec('git', ['--git-dir', gitDir, 'rev-parse', `${commit}^{tree}`], { shell: false });
176
193
  if (stdout.trim() !== tree) {
177
194
  throw frozenError('frozen git object tree mismatch', { commit, expectedTree: tree, observedTree: stdout.trim() });
178
195
  }
179
- return { gitDir, commit, tree };
196
+ if (commitTimestamp !== undefined) {
197
+ const expectedTimestamp = normalizeGitTimestamp(commitTimestamp, 'frozen commit timestamp');
198
+ const { stdout: datesOut } = await exec(
199
+ 'git',
200
+ ['--git-dir', gitDir, 'show', '-s', '--format=%aI%n%cI', commit],
201
+ { shell: false },
202
+ );
203
+ const [authorDate, committerDate] = datesOut.trim().split('\n');
204
+ if (authorDate !== expectedTimestamp || committerDate !== expectedTimestamp) {
205
+ throw frozenError('frozen git commit dates do not match the plan freeze timestamp', {
206
+ commit,
207
+ expectedTimestamp,
208
+ observedAuthorDate: authorDate,
209
+ observedCommitterDate: committerDate,
210
+ });
211
+ }
212
+ }
213
+ if (parentCommit) {
214
+ if (!/^[a-f0-9]{40,64}$/.test(parentCommit)) {
215
+ throw frozenError('frozen Git parent must be a full hexadecimal object id');
216
+ }
217
+ const { stdout: parentsOut } = await exec(
218
+ 'git',
219
+ ['--git-dir', gitDir, 'rev-list', '--parents', '-n', '1', commit],
220
+ { shell: false },
221
+ );
222
+ const [observedCommit, ...parents] = parentsOut.trim().split(/\s+/);
223
+ if (observedCommit !== commit || parents.length !== 1 || parents[0] !== parentCommit) {
224
+ throw frozenError('frozen Git commit parent mismatch', {
225
+ commit,
226
+ expectedParent: parentCommit,
227
+ observedParents: parents,
228
+ });
229
+ }
230
+ }
231
+ return { gitDir, commit, tree, ...(parentCommit ? { parentCommit } : {}) };
180
232
  }
181
233
 
182
234
  async function verifyGitTreeContent({ snapshotDir, repositoryDir, commit, expectedSnapshotDigest, exec }) {
@@ -224,10 +276,154 @@ async function verifyGitTreeContent({ snapshotDir, repositoryDir, commit, expect
224
276
  }
225
277
  }
226
278
 
227
- export async function buildFrozenGitRepository({ snapshotDir, repositoryDir, version, expectedSnapshotDigest, exec = execFile }) {
279
+ /**
280
+ * Strict ISO 8601 timestamp with an explicit UTC offset (Z or ±HH:MM),
281
+ * optional fractional seconds. Naive/local timestamps (no offset) are rejected
282
+ * so the frozen commit time is never interpreted in the builder's local zone.
283
+ */
284
+ const GIT_ISO8601_STRICT_RE =
285
+ /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d{1,9})?(Z|[+-]\d{2}:\d{2})$/;
286
+
287
+ /**
288
+ * Validate and canonicalize a plan freeze timestamp.
289
+ *
290
+ * The value must be a non-empty, strict ISO 8601 timestamp with an explicit UTC
291
+ * offset and a parseable calendar date. It is normalized to UTC with second
292
+ * precision and a `+00:00` offset, which is exactly the form Git stores and
293
+ * re-emits via `%aI`/`%cI`. Missing, empty, malformed, or out-of-range values
294
+ * throw a closed `GATE_FAILED` error.
295
+ *
296
+ * Normalization is idempotent: re-normalizing an already-canonical value returns
297
+ * the identical string, so prepare and the Git builder always agree byte-for-byte.
298
+ *
299
+ * @param {unknown} value - Candidate timestamp.
300
+ * @param {string} [label] - Label used in error messages.
301
+ * @returns {string} Canonical `YYYY-MM-DDTHH:MM:SS+00:00` timestamp.
302
+ * @throws {ReleaseError} GATE_FAILED when the value is not a valid timestamp.
303
+ */
304
+ export function normalizeGitTimestamp(value, label = 'freeze timestamp') {
305
+ if (typeof value !== 'string' || value.trim() === '') {
306
+ throw frozenError(`${label} must be a non-empty strict ISO 8601 timestamp string`, {
307
+ received: typeof value === 'string' ? value : typeof value,
308
+ });
309
+ }
310
+ const trimmed = value.trim();
311
+ const match = GIT_ISO8601_STRICT_RE.exec(trimmed);
312
+ if (!match) {
313
+ throw frozenError(`${label} must be strict ISO 8601 with an explicit UTC offset`, {
314
+ received: trimmed,
315
+ });
316
+ }
317
+ const [, yearStr, monthStr, dayStr, hourStr, minuteStr, secondStr, zone] = match;
318
+ const inputYear = Number(yearStr);
319
+ const inputMonth = Number(monthStr);
320
+ const inputDay = Number(dayStr);
321
+ const inputHour = Number(hourStr);
322
+ const inputMinute = Number(minuteStr);
323
+ const inputSecond = Number(secondStr);
324
+ if (inputYear < 1000 || inputYear > 9999) {
325
+ throw frozenError(`${label} year must be a four-digit ISO year`, { received: trimmed });
326
+ }
327
+ // Reject impossible calendar dates and out-of-range time fields. Date.UTC
328
+ // silently rolls values over (Feb 30 -> Mar 2), so compare the constructed
329
+ // UTC fields against the declared input fields instead of trusting parsing.
330
+ const probeMs = Date.UTC(inputYear, inputMonth - 1, inputDay, inputHour, inputMinute, inputSecond);
331
+ const probe = new Date(probeMs);
332
+ if (
333
+ probe.getUTCFullYear() !== inputYear || probe.getUTCMonth() !== inputMonth - 1 ||
334
+ probe.getUTCDate() !== inputDay || probe.getUTCHours() !== inputHour ||
335
+ probe.getUTCMinutes() !== inputMinute || probe.getUTCSeconds() !== inputSecond
336
+ ) {
337
+ throw frozenError(`${label} is not a valid calendar date`, { received: trimmed });
338
+ }
339
+ let offsetMs = 0;
340
+ if (zone !== 'Z') {
341
+ const sign = zone.startsWith('-') ? -1 : 1;
342
+ const zoneHours = Number(zone.slice(1, 3));
343
+ const zoneMinutes = Number(zone.slice(4, 6));
344
+ if (zoneHours > 23 || zoneMinutes > 59) {
345
+ throw frozenError(`${label} has an invalid UTC offset`, { received: trimmed });
346
+ }
347
+ offsetMs = sign * (zoneHours * 60 + zoneMinutes) * 60_000;
348
+ }
349
+ const date = new Date(probeMs - offsetMs);
350
+ if (date.getUTCFullYear() < 1000 || date.getUTCFullYear() > 9999) {
351
+ throw frozenError(`${label} normalizes outside the four-digit ISO year range`, { received: trimmed });
352
+ }
353
+ const pad = (num) => String(num).padStart(2, '0');
354
+ return `${date.getUTCFullYear()}-${pad(date.getUTCMonth() + 1)}-${pad(date.getUTCDate())}` +
355
+ `T${pad(date.getUTCHours())}:${pad(date.getUTCMinutes())}:${pad(date.getUTCSeconds())}+00:00`;
356
+ }
357
+
358
+ function publicGitRemote({ repo, githubHost = 'github.com' }) {
359
+ if (typeof repo !== 'string' || !/^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/.test(repo)) {
360
+ throw frozenError('Git parent repo must use owner/name format');
361
+ }
362
+ if (
363
+ typeof githubHost !== 'string' || !/^[A-Za-z0-9.-]+$/.test(githubHost) ||
364
+ githubHost.startsWith('.') || githubHost.endsWith('.') || githubHost.includes('..')
365
+ ) {
366
+ throw frozenError('Git parent githubHost must be a valid hostname');
367
+ }
368
+ return `https://${githubHost}/${repo}.git`;
369
+ }
370
+
371
+ /**
372
+ * Build the frozen release Git objects for a sealed public snapshot.
373
+ *
374
+ * `commitTimestamp` is the plan freeze timestamp sampled exactly once during
375
+ * production prepare. It is strictly validated BEFORE any filesystem or Git
376
+ * write (missing, empty, malformed, or impossible values fail closed), then
377
+ * written verbatim to both GIT_AUTHOR_DATE and GIT_COMMITTER_DATE, so the
378
+ * frozen commit's `%aI` and `%cI` always equal the plan's
379
+ * `frozenSnapshot.commitTimestamp` byte-for-byte. Identical inputs therefore
380
+ * yield identical tree and commit identifiers, and a different freeze
381
+ * timestamp alone yields a different commit with an identical tree.
382
+ */
383
+ export async function buildFrozenGitRepository({
384
+ snapshotDir,
385
+ repositoryDir,
386
+ version,
387
+ expectedSnapshotDigest,
388
+ parent,
389
+ commitTimestamp,
390
+ exec = execFile,
391
+ }) {
392
+ // Fail closed before creating the object directory or running any Git
393
+ // command: a frozen release commit may never inherit the builder's wall
394
+ // clock, an ancient constant, or a value Git would reinterpret.
395
+ const canonicalTimestamp = normalizeGitTimestamp(commitTimestamp, 'frozen commit timestamp');
228
396
  if (!expectedSnapshotDigest) throw frozenError('Git object build requires the sealed snapshot digest');
229
397
  await mkdir(repositoryDir, { recursive: true });
230
398
  await exec('git', ['init', '--bare', repositoryDir], { shell: false });
399
+ let parentCommit = null;
400
+ if (parent) {
401
+ if (!parent.ref || typeof parent.ref !== 'string') {
402
+ throw frozenError('Git parent ref must be a non-empty string');
403
+ }
404
+ if (!parent.commit || !/^[a-f0-9]{40,64}$/.test(parent.commit)) {
405
+ throw frozenError('Git parent commit must be a full hexadecimal object id');
406
+ }
407
+ const remoteUrl = publicGitRemote(parent);
408
+ await exec(
409
+ 'git',
410
+ ['--git-dir', repositoryDir, 'fetch', '--no-tags', remoteUrl, parent.ref],
411
+ { shell: false },
412
+ );
413
+ const { stdout: fetchedOut } = await exec(
414
+ 'git',
415
+ ['--git-dir', repositoryDir, 'rev-parse', 'FETCH_HEAD^{commit}'],
416
+ { shell: false },
417
+ );
418
+ if (fetchedOut.trim() !== parent.commit) {
419
+ throw frozenError('fetched Git parent does not match the observed public baseline', {
420
+ expectedCommit: parent.commit,
421
+ observedCommit: fetchedOut.trim(),
422
+ ref: parent.ref,
423
+ });
424
+ }
425
+ parentCommit = parent.commit;
426
+ }
231
427
  const indexPath = join(repositoryDir, 'release-index');
232
428
  const env = { ...process.env, GIT_INDEX_FILE: indexPath };
233
429
  await exec('git', [
@@ -246,17 +442,35 @@ export async function buildFrozenGitRepository({ snapshotDir, repositoryDir, ver
246
442
  GIT_AUTHOR_EMAIL: 'release-skill@localhost',
247
443
  GIT_COMMITTER_NAME: 'release-skill',
248
444
  GIT_COMMITTER_EMAIL: 'release-skill@localhost',
249
- GIT_AUTHOR_DATE: '2000-01-01T00:00:00Z',
250
- GIT_COMMITTER_DATE: '2000-01-01T00:00:00Z',
445
+ GIT_AUTHOR_DATE: canonicalTimestamp,
446
+ GIT_COMMITTER_DATE: canonicalTimestamp,
251
447
  };
448
+ const commitArgs = ['--git-dir', repositoryDir, 'commit-tree', tree];
449
+ if (parentCommit) commitArgs.push('-p', parentCommit);
450
+ commitArgs.push('-m', `Release ${version}`);
252
451
  const { stdout: commitOut } = await exec(
253
452
  'git',
254
- ['--git-dir', repositoryDir, 'commit-tree', tree, '-m', `Release ${version}`],
453
+ commitArgs,
255
454
  { env: commitEnv, shell: false },
256
455
  );
257
456
  const commit = commitOut.trim();
258
457
  await verifyGitTreeContent({ snapshotDir, repositoryDir, commit, expectedSnapshotDigest, exec });
259
- return { tree, commit };
458
+ if (parentCommit) {
459
+ const { stdout: parentsOut } = await exec(
460
+ 'git',
461
+ ['--git-dir', repositoryDir, 'rev-list', '--parents', '-n', '1', commit],
462
+ { shell: false },
463
+ );
464
+ const [observedCommit, ...parents] = parentsOut.trim().split(/\s+/);
465
+ if (observedCommit !== commit || parents.length !== 1 || parents[0] !== parentCommit) {
466
+ throw frozenError('derived Git commit does not have the exact planned parent', {
467
+ commit,
468
+ parentCommit,
469
+ observedParents: parents,
470
+ });
471
+ }
472
+ }
473
+ return { tree, commit, ...(parentCommit ? { parentCommit } : {}) };
260
474
  }
261
475
 
262
476
  function contentEntries(entries) {
@@ -281,6 +281,7 @@ export function normalizePublicMappings({ unit } = {}) {
281
281
  return Object.freeze(
282
282
  unit.publicFiles.map((mapping) => {
283
283
  const from = canonicalPublicPath(mapping.from).path;
284
+ const sourceScope = mapping.sourceScope ?? 'unit';
284
285
  if (isReservedReleaseControlPath(from)) {
285
286
  throw new ReleaseError(
286
287
  PUBLIC_PATH_FORBIDDEN,
@@ -291,7 +292,7 @@ export function normalizePublicMappings({ unit } = {}) {
291
292
  const sourceRelative = posix.relative(sourcePrefix, from);
292
293
 
293
294
  // Reject `from` paths that escape `unit.source`.
294
- if (sourceRelative.startsWith('../') || sourceRelative === '..') {
295
+ if (sourceScope === 'unit' && (sourceRelative.startsWith('../') || sourceRelative === '..')) {
295
296
  throw new ReleaseError(
296
297
  PUBLIC_PATH_FORBIDDEN,
297
298
  `public file "${mapping.from}" is outside unit source "${unit.source}"`,
@@ -301,6 +302,7 @@ export function normalizePublicMappings({ unit } = {}) {
301
302
 
302
303
  return Object.freeze({
303
304
  from,
305
+ sourceScope,
304
306
  sourceRelative,
305
307
  to: canonicalPublicPath(mapping.to).path,
306
308
  mode: mapping.mode,
@@ -576,7 +578,7 @@ export async function buildPublicStaging({
576
578
  continue;
577
579
  }
578
580
  // Also check real containment inside unit root
579
- if (!isContained(realUnitRoot, realSrc)) {
581
+ if (mapping.sourceScope === 'unit' && !isContained(realUnitRoot, realSrc)) {
580
582
  preflightRecords.push({
581
583
  ok: false,
582
584
  err: new ReleaseError(
@@ -988,7 +990,8 @@ export async function buildPublicStaging({
988
990
  );
989
991
  }
990
992
 
991
- // Verify realpath of the handle is inside both realSourceRoot and realUnitRoot.
993
+ // Verify realpath of the handle is inside the workspace and, for the
994
+ // default unit scope, inside the unit root as well.
992
995
  // This catches a race between preflight and open where a component
993
996
  // was replaced with a symlink.
994
997
  const handleRealPath = await fs.realpath(srcPath, { stage: 'source-open-realpath' });
@@ -999,7 +1002,7 @@ export async function buildPublicStaging({
999
1002
  { from: mapping.from, realPath: handleRealPath },
1000
1003
  );
1001
1004
  }
1002
- if (!isContained(realUnitRoot, handleRealPath)) {
1005
+ if (mapping.sourceScope === 'unit' && !isContained(realUnitRoot, handleRealPath)) {
1003
1006
  throw new ReleaseError(
1004
1007
  SNAPSHOT_FIDELITY_FAILED,
1005
1008
  `source escapes unitRoot containment after open: "${mapping.from}"`,
@@ -49,7 +49,8 @@ const BINARY_PROBE_SIZE = 8192;
49
49
  */
50
50
  const ABSOLUTE_PATH_PATTERNS = [
51
51
  /\/(?:Users|home)\/[A-Za-z0-9_-][A-Za-z0-9._-]*(?:\/[^\s"'`<>]*)?/,
52
- /\/(?:root|tmp)\/[A-Za-z0-9_-][A-Za-z0-9._-]*(?:\/[^\s"'`<>]*)?/,
52
+ /\/root\/[A-Za-z0-9_-][A-Za-z0-9._-]*(?:\/[^\s"'`<>]*)?/,
53
+ /\/tmp\/(?=[^\s"'`<>]*(?:secret|token|credential|password|private[-_]?key|id_rsa))[^\s"'`<>]+/i,
53
54
  /(?:^|[^A-Za-z0-9_])[A-Za-z]:\\[A-Za-z0-9_$-][A-Za-z0-9._$-]*(?:\\[^\s"'`<>]*)?/,
54
55
  ];
55
56