claude-nomad 0.53.1 → 0.53.2
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/.gitleaks.toml +5 -1
- package/CHANGELOG.md +13 -0
- package/dist/nomad.mjs +107 -32
- package/package.json +1 -1
package/.gitleaks.toml
CHANGED
|
@@ -14,10 +14,14 @@ useDefault = true
|
|
|
14
14
|
# context anywhere in the repo. Scoping to `shared/projects/<logical>/.../*.jsonl`
|
|
15
15
|
# with `condition = "AND"` (matching every other block below) confines the
|
|
16
16
|
# suppression to synced transcripts: a real secret in a source file still fires.
|
|
17
|
+
# The Sonar-issue-key regex is length-bounded to the real key shape (`AY` + 17-22
|
|
18
|
+
# base64url chars = 19-24 total, matching the sibling `key:`/`rule:` block) and
|
|
19
|
+
# token-anchored with `\b` on both ends, so it cannot suppress a longer or
|
|
20
|
+
# embedded `AY`-prefixed credential as a substring (an unbounded `{20,}` could).
|
|
17
21
|
[[allowlists]]
|
|
18
22
|
description = "claude-nomad: structurally-distinguishable tool-output noise in synced session transcripts"
|
|
19
23
|
regexes = [
|
|
20
|
-
'''
|
|
24
|
+
'''\bAY[A-Za-z0-9_-]{17,22}\b''',
|
|
21
25
|
'''[\w./-]+\.[A-Za-z0-9]+:[\w-]+:\d+''',
|
|
22
26
|
'''"id"\s*:\s*"[a-f0-9]{40,64}"''',
|
|
23
27
|
'''key=[a-f0-9]{8,} [\w./-]+\.\w+:\d+''',
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.53.2](https://github.com/funkadelic/claude-nomad/compare/v0.53.1...v0.53.2) (2026-06-26)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Fixed
|
|
7
|
+
|
|
8
|
+
* **security:** close pull-side path-write and push-side scan-bypass vectors ([#339](https://github.com/funkadelic/claude-nomad/issues/339)) ([99f0320](https://github.com/funkadelic/claude-nomad/commit/99f0320d1181333fc00ea983fda7e675377def47))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Dependencies
|
|
12
|
+
|
|
13
|
+
* bump actions/checkout from 6.0.3 to 7.0.0 ([#336](https://github.com/funkadelic/claude-nomad/issues/336)) ([6df79cf](https://github.com/funkadelic/claude-nomad/commit/6df79cfcfe9bd5eaa72130d15fdcb074019822a8))
|
|
14
|
+
* bump the dev-dependencies group with 3 updates ([#337](https://github.com/funkadelic/claude-nomad/issues/337)) ([dd5a8ea](https://github.com/funkadelic/claude-nomad/commit/dd5a8eaffe70fe96f9d6f0921eaead8c3caf323b))
|
|
15
|
+
|
|
3
16
|
## [0.53.1](https://github.com/funkadelic/claude-nomad/compare/v0.53.0...v0.53.1) (2026-06-21)
|
|
4
17
|
|
|
5
18
|
|
package/dist/nomad.mjs
CHANGED
|
@@ -42,7 +42,13 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
42
42
|
));
|
|
43
43
|
|
|
44
44
|
// src/config.never-sync.ts
|
|
45
|
-
|
|
45
|
+
function isSecretFileName(name) {
|
|
46
|
+
return SECRET_FILE_PATTERNS.some((re) => re.test(name));
|
|
47
|
+
}
|
|
48
|
+
function isDeniedName(blockSet, name) {
|
|
49
|
+
return blockSet.has(name) || blockSet.has(name.toLowerCase()) || isSecretFileName(name);
|
|
50
|
+
}
|
|
51
|
+
var NEVER_SYNC, CLAUDE_EXTRA_NEVER_SYNC, SECRET_FILE_PATTERNS;
|
|
46
52
|
var init_config_never_sync = __esm({
|
|
47
53
|
"src/config.never-sync.ts"() {
|
|
48
54
|
"use strict";
|
|
@@ -72,6 +78,20 @@ var init_config_never_sync = __esm({
|
|
|
72
78
|
"sessions"
|
|
73
79
|
]);
|
|
74
80
|
CLAUDE_EXTRA_NEVER_SYNC = /* @__PURE__ */ new Set([...NEVER_SYNC, "projects"]);
|
|
81
|
+
SECRET_FILE_PATTERNS = [
|
|
82
|
+
/^\.env(\..+)?$/i,
|
|
83
|
+
// .env, .env.local, .env.production
|
|
84
|
+
/\.pem$/i,
|
|
85
|
+
/\.key$/i,
|
|
86
|
+
/\.p12$/i,
|
|
87
|
+
/\.pfx$/i,
|
|
88
|
+
/^id_(rsa|dsa|ecdsa|ed25519)$/i,
|
|
89
|
+
/^\.netrc$/i,
|
|
90
|
+
/^\.npmrc$/i,
|
|
91
|
+
/^\.pgpass$/i,
|
|
92
|
+
/^\.git-credentials$/i,
|
|
93
|
+
/^credentials$/i
|
|
94
|
+
];
|
|
75
95
|
}
|
|
76
96
|
});
|
|
77
97
|
|
|
@@ -667,6 +687,37 @@ function resolveTomlPath(repo = repoHome()) {
|
|
|
667
687
|
const bundled = fileURLToPath(new URL("../.gitleaks.toml", import.meta.url));
|
|
668
688
|
return existsSync13(bundled) ? bundled : null;
|
|
669
689
|
}
|
|
690
|
+
function assertOverlayAllowlistsScoped(overlayBody) {
|
|
691
|
+
if (OVERLAY_INLINE_ALLOWLIST_RE.test(overlayBody)) {
|
|
692
|
+
throw new NomadFatal(
|
|
693
|
+
".gitleaks.overlay.toml must declare allowlists as [[allowlists]] table blocks, not the dotted-key (allowlist.x = ...) or inline-table (allowlist = { ... }) form, which bypasses path-scope validation. Use a [[allowlists]] block with a `paths` entry."
|
|
694
|
+
);
|
|
695
|
+
}
|
|
696
|
+
let inAllowlist = false;
|
|
697
|
+
let hasPaths = false;
|
|
698
|
+
const closeBlock = () => {
|
|
699
|
+
if (inAllowlist && !hasPaths) {
|
|
700
|
+
throw new NomadFatal(
|
|
701
|
+
".gitleaks.overlay.toml allowlist must be path-scoped: every [[allowlists]] block needs a `paths` entry so it cannot suppress findings repo-wide. Anchor `paths` to the files you intend to allow (e.g. shared/projects/<logical>/...jsonl)."
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
};
|
|
705
|
+
for (const line of overlayBody.split("\n")) {
|
|
706
|
+
if (TABLE_HEADER_RE.test(line)) {
|
|
707
|
+
closeBlock();
|
|
708
|
+
inAllowlist = OVERLAY_ALLOWLIST_HEADER_RE.test(line);
|
|
709
|
+
hasPaths = false;
|
|
710
|
+
} else if (inAllowlist) {
|
|
711
|
+
if (PATHS_KEY_RE.test(line)) hasPaths = true;
|
|
712
|
+
if (CATCH_ALL_RE.test(line)) {
|
|
713
|
+
throw new NomadFatal(
|
|
714
|
+
".gitleaks.overlay.toml allowlist contains a catch-all pattern (.* or .+) that would suppress every finding. Replace it with a specific, anchored pattern."
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
closeBlock();
|
|
720
|
+
}
|
|
670
721
|
function buildOverlayTempConfig(overlayBody, bundled) {
|
|
671
722
|
const tempBody = `[extend]
|
|
672
723
|
path = ${JSON.stringify(bundled)}
|
|
@@ -701,6 +752,7 @@ function resolveTomlConfig() {
|
|
|
701
752
|
".gitleaks.overlay.toml must not contain an [extend] block; it is generated automatically. Remove the [extend] section and retry."
|
|
702
753
|
);
|
|
703
754
|
}
|
|
755
|
+
assertOverlayAllowlistsScoped(overlayBody);
|
|
704
756
|
const { configPath, tempPath } = buildOverlayTempConfig(overlayBody, bundled);
|
|
705
757
|
return { path: configPath, tempPath };
|
|
706
758
|
} catch (err) {
|
|
@@ -711,13 +763,18 @@ function resolveTomlConfig() {
|
|
|
711
763
|
return { path: bundled, tempPath: null };
|
|
712
764
|
}
|
|
713
765
|
}
|
|
714
|
-
var OVERLAY_EXTEND_RE;
|
|
766
|
+
var OVERLAY_EXTEND_RE, TABLE_HEADER_RE, OVERLAY_ALLOWLIST_HEADER_RE, OVERLAY_INLINE_ALLOWLIST_RE, PATHS_KEY_RE, CATCH_ALL_RE;
|
|
715
767
|
var init_push_gitleaks_config = __esm({
|
|
716
768
|
"src/push-gitleaks.config.ts"() {
|
|
717
769
|
"use strict";
|
|
718
770
|
init_config();
|
|
719
771
|
init_utils();
|
|
720
772
|
OVERLAY_EXTEND_RE = /^\s*(?:\[\s*extend\s*\]|extend\s*[.=])/m;
|
|
773
|
+
TABLE_HEADER_RE = /^\s*\[/;
|
|
774
|
+
OVERLAY_ALLOWLIST_HEADER_RE = /^\s*\[\[?\s*allowlists?\s*\]\]?/;
|
|
775
|
+
OVERLAY_INLINE_ALLOWLIST_RE = /^[ \t]*allowlists?[ \t]*[.=]/m;
|
|
776
|
+
PATHS_KEY_RE = /^\s*paths\s*=/;
|
|
777
|
+
CATCH_ALL_RE = /('''|"""|'|")\^?\(?\.[*+]\)?\$?\1/;
|
|
721
778
|
}
|
|
722
779
|
});
|
|
723
780
|
|
|
@@ -2700,12 +2757,31 @@ init_config();
|
|
|
2700
2757
|
|
|
2701
2758
|
// src/remap.ts
|
|
2702
2759
|
init_config_sharedDirs_guard();
|
|
2760
|
+
import { cpSync as cpSync4, existsSync as existsSync17, mkdirSync as mkdirSync4, readdirSync as readdirSync6, renameSync as renameSync3, rmSync as rmSync7, statSync as statSync4 } from "node:fs";
|
|
2761
|
+
import { join as join22, relative as relative3, sep as sep3 } from "node:path";
|
|
2762
|
+
|
|
2763
|
+
// src/extras-sync.guards.ts
|
|
2764
|
+
init_utils();
|
|
2765
|
+
init_config_sharedDirs_guard();
|
|
2766
|
+
import { isAbsolute, normalize } from "node:path";
|
|
2767
|
+
function assertSafeLocalRoot(localRoot, logical) {
|
|
2768
|
+
if (!isAbsolute(localRoot)) {
|
|
2769
|
+
throw new NomadFatal(
|
|
2770
|
+
`invalid localRoot for ${logical} in path-map.json: ${JSON.stringify(localRoot)} (must be absolute)`
|
|
2771
|
+
);
|
|
2772
|
+
}
|
|
2773
|
+
if (localRoot !== normalize(localRoot)) {
|
|
2774
|
+
throw new NomadFatal(
|
|
2775
|
+
`invalid localRoot for ${logical} in path-map.json: ${JSON.stringify(localRoot)} (must be already-normalized; no '..' or redundant segments)`
|
|
2776
|
+
);
|
|
2777
|
+
}
|
|
2778
|
+
}
|
|
2779
|
+
|
|
2780
|
+
// src/remap.ts
|
|
2703
2781
|
init_config();
|
|
2704
2782
|
init_utils();
|
|
2705
2783
|
init_utils_fs();
|
|
2706
2784
|
init_utils_json();
|
|
2707
|
-
import { cpSync as cpSync4, existsSync as existsSync17, mkdirSync as mkdirSync4, readdirSync as readdirSync6, renameSync as renameSync3, rmSync as rmSync7, statSync as statSync4 } from "node:fs";
|
|
2708
|
-
import { join as join22, relative as relative3, sep as sep3 } from "node:path";
|
|
2709
2785
|
var TMP_SUFFIX = ".nomad-tmp";
|
|
2710
2786
|
function atomicMirror(src, dst, options) {
|
|
2711
2787
|
const tmp = `${dst}${TMP_SUFFIX}`;
|
|
@@ -2760,6 +2836,7 @@ function remapPull(ts, opts = {}) {
|
|
|
2760
2836
|
unmapped++;
|
|
2761
2837
|
continue;
|
|
2762
2838
|
}
|
|
2839
|
+
assertSafeLocalRoot(localPath, logical);
|
|
2763
2840
|
const src = join22(repoProjects, logical);
|
|
2764
2841
|
if (!existsSync17(src)) continue;
|
|
2765
2842
|
const dst = join22(localProjects, encodePath(localPath));
|
|
@@ -3530,7 +3607,7 @@ import { createInterface as createInterface2 } from "node:readline/promises";
|
|
|
3530
3607
|
// src/commands.push.recovery.actions.ts
|
|
3531
3608
|
init_config();
|
|
3532
3609
|
import { readFileSync as readFileSync12 } from "node:fs";
|
|
3533
|
-
import { isAbsolute, resolve as resolve3, sep as sep5 } from "node:path";
|
|
3610
|
+
import { isAbsolute as isAbsolute2, resolve as resolve3, sep as sep5 } from "node:path";
|
|
3534
3611
|
|
|
3535
3612
|
// src/commands.push.recovery.redact.ts
|
|
3536
3613
|
init_config();
|
|
@@ -3937,7 +4014,7 @@ function makeDefaultReadLine(repo) {
|
|
|
3937
4014
|
try {
|
|
3938
4015
|
const repoRoot = resolve3(repo);
|
|
3939
4016
|
const target = resolve3(repoRoot, file);
|
|
3940
|
-
if (
|
|
4017
|
+
if (isAbsolute2(file) || target !== repoRoot && !target.startsWith(repoRoot + sep5)) {
|
|
3941
4018
|
return null;
|
|
3942
4019
|
}
|
|
3943
4020
|
const content = readFileSync12(target, "utf8");
|
|
@@ -4846,25 +4923,6 @@ function listDivergingFiles(a, b) {
|
|
|
4846
4923
|
init_config();
|
|
4847
4924
|
import { cpSync as cpSync6, existsSync as existsSync32, lstatSync as lstatSync9, readdirSync as readdirSync11, rmSync as rmSync11 } from "node:fs";
|
|
4848
4925
|
import { basename, join as join38 } from "node:path";
|
|
4849
|
-
|
|
4850
|
-
// src/extras-sync.guards.ts
|
|
4851
|
-
init_utils();
|
|
4852
|
-
init_config_sharedDirs_guard();
|
|
4853
|
-
import { isAbsolute as isAbsolute2, normalize } from "node:path";
|
|
4854
|
-
function assertSafeLocalRoot(localRoot, logical) {
|
|
4855
|
-
if (!isAbsolute2(localRoot)) {
|
|
4856
|
-
throw new NomadFatal(
|
|
4857
|
-
`invalid localRoot for ${logical} in path-map.json: ${JSON.stringify(localRoot)} (must be absolute)`
|
|
4858
|
-
);
|
|
4859
|
-
}
|
|
4860
|
-
if (localRoot !== normalize(localRoot)) {
|
|
4861
|
-
throw new NomadFatal(
|
|
4862
|
-
`invalid localRoot for ${logical} in path-map.json: ${JSON.stringify(localRoot)} (must be already-normalized; no '..' or redundant segments)`
|
|
4863
|
-
);
|
|
4864
|
-
}
|
|
4865
|
-
}
|
|
4866
|
-
|
|
4867
|
-
// src/extras-sync.core.ts
|
|
4868
4926
|
init_utils();
|
|
4869
4927
|
init_utils_json();
|
|
4870
4928
|
function loadValidatedExtras(opts) {
|
|
@@ -4902,13 +4960,28 @@ function* eachExtrasTarget(v, counts) {
|
|
|
4902
4960
|
}
|
|
4903
4961
|
}
|
|
4904
4962
|
}
|
|
4963
|
+
function stripCollidingDstSymlinks(src, dst, isExcluded) {
|
|
4964
|
+
if (!existsSync32(dst)) return;
|
|
4965
|
+
for (const name of readdirSync11(src)) {
|
|
4966
|
+
if (isExcluded(name)) continue;
|
|
4967
|
+
const dstPath = join38(dst, name);
|
|
4968
|
+
const dstStat = lstatSync9(dstPath, { throwIfNoEntry: false });
|
|
4969
|
+
if (dstStat === void 0) continue;
|
|
4970
|
+
if (dstStat.isSymbolicLink()) {
|
|
4971
|
+
rmSync11(dstPath, { recursive: true, force: true });
|
|
4972
|
+
} else if (dstStat.isDirectory() && lstatSync9(join38(src, name)).isDirectory()) {
|
|
4973
|
+
stripCollidingDstSymlinks(join38(src, name), dstPath, isExcluded);
|
|
4974
|
+
}
|
|
4975
|
+
}
|
|
4976
|
+
}
|
|
4905
4977
|
function copyExtrasOverlayFiltered(src, dst, blockSet) {
|
|
4978
|
+
stripCollidingDstSymlinks(src, dst, (name) => isDeniedName(blockSet, name));
|
|
4906
4979
|
try {
|
|
4907
4980
|
cpSync6(src, dst, {
|
|
4908
4981
|
recursive: true,
|
|
4909
4982
|
force: true,
|
|
4910
4983
|
verbatimSymlinks: true,
|
|
4911
|
-
filter: (srcEntry) => srcEntry === src || !blockSet
|
|
4984
|
+
filter: (srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename(srcEntry))
|
|
4912
4985
|
});
|
|
4913
4986
|
} catch (err) {
|
|
4914
4987
|
const e = err;
|
|
@@ -4933,12 +5006,12 @@ function copyExtrasFiltered(src, dst, blockSet) {
|
|
|
4933
5006
|
recursive: true,
|
|
4934
5007
|
force: true,
|
|
4935
5008
|
verbatimSymlinks: true,
|
|
4936
|
-
filter: (srcEntry) => srcEntry === src || !blockSet
|
|
5009
|
+
filter: (srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename(srcEntry))
|
|
4937
5010
|
});
|
|
4938
5011
|
}
|
|
4939
5012
|
function prunePreservingDenied(src, dst, blockSet) {
|
|
4940
5013
|
for (const name of readdirSync11(dst)) {
|
|
4941
|
-
if (blockSet
|
|
5014
|
+
if (isDeniedName(blockSet, name)) continue;
|
|
4942
5015
|
const dstPath = join38(dst, name);
|
|
4943
5016
|
const srcStat = lstatSync9(join38(src, name), { throwIfNoEntry: false });
|
|
4944
5017
|
if (srcStat === void 0) {
|
|
@@ -4959,11 +5032,12 @@ function copyExtrasFilteredPreserving(src, dst, blockSet) {
|
|
|
4959
5032
|
if (dstStat.isDirectory()) prunePreservingDenied(src, dst, blockSet);
|
|
4960
5033
|
else rmSync11(dst, { recursive: true, force: true });
|
|
4961
5034
|
}
|
|
5035
|
+
stripCollidingDstSymlinks(src, dst, (name) => isDeniedName(blockSet, name));
|
|
4962
5036
|
cpSync6(src, dst, {
|
|
4963
5037
|
recursive: true,
|
|
4964
5038
|
force: true,
|
|
4965
5039
|
verbatimSymlinks: true,
|
|
4966
|
-
filter: (srcEntry) => srcEntry === src || !blockSet
|
|
5040
|
+
filter: (srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename(srcEntry))
|
|
4967
5041
|
});
|
|
4968
5042
|
}
|
|
4969
5043
|
function prunePreservingBy(src, dst, isPreserved) {
|
|
@@ -4989,6 +5063,7 @@ function copyExtrasFilteredPreservingBy(src, dst, isPreserved) {
|
|
|
4989
5063
|
if (dstStat.isDirectory()) prunePreservingBy(src, dst, isPreserved);
|
|
4990
5064
|
else rmSync11(dst, { recursive: true, force: true });
|
|
4991
5065
|
}
|
|
5066
|
+
stripCollidingDstSymlinks(src, dst, isPreserved);
|
|
4992
5067
|
cpSync6(src, dst, {
|
|
4993
5068
|
recursive: true,
|
|
4994
5069
|
force: true,
|
|
@@ -5266,7 +5341,7 @@ function isGsdOwned(name) {
|
|
|
5266
5341
|
return name.startsWith(GSD_PREFIX);
|
|
5267
5342
|
}
|
|
5268
5343
|
function isSkillExcluded(name) {
|
|
5269
|
-
return isGsdOwned(name) || ALWAYS_NEVER_SYNC
|
|
5344
|
+
return isGsdOwned(name) || isDeniedName(ALWAYS_NEVER_SYNC, name);
|
|
5270
5345
|
}
|
|
5271
5346
|
function copySkillsPush(src, dst) {
|
|
5272
5347
|
const srcNames = readdirSync13(src, { encoding: "utf8" });
|
|
@@ -5925,7 +6000,7 @@ function isNeverSync(path) {
|
|
|
5925
6000
|
const blockSet = blockSetFor(segments);
|
|
5926
6001
|
const scan = segments[0] === "shared" && segments[1] === "extras" ? segments.slice(4) : segments;
|
|
5927
6002
|
for (const segment of scan) {
|
|
5928
|
-
if (blockSet
|
|
6003
|
+
if (isDeniedName(blockSet, segment)) return true;
|
|
5929
6004
|
}
|
|
5930
6005
|
return false;
|
|
5931
6006
|
}
|
|
@@ -6843,7 +6918,7 @@ function parsePushArgs(argv) {
|
|
|
6843
6918
|
// package.json
|
|
6844
6919
|
var package_default = {
|
|
6845
6920
|
name: "claude-nomad",
|
|
6846
|
-
version: "0.53.
|
|
6921
|
+
version: "0.53.2",
|
|
6847
6922
|
type: "module",
|
|
6848
6923
|
description: "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
|
|
6849
6924
|
keywords: [
|
package/package.json
CHANGED