claude-nomad 0.32.1 → 0.32.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/CHANGELOG.md +12 -0
- package/package.json +1 -1
- package/src/config.sharedDirs.guard.ts +33 -4
- package/src/extras-sync.guards.ts +1 -24
- package/src/remap.ts +3 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.32.2](https://github.com/funkadelic/claude-nomad/compare/v0.32.1...v0.32.2) (2026-05-30)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Fixed
|
|
7
|
+
|
|
8
|
+
* **remap:** reject path-traversal in path-map logical keys ([#190](https://github.com/funkadelic/claude-nomad/issues/190)) ([1526fbb](https://github.com/funkadelic/claude-nomad/commit/1526fbbbb7c6beb258d882c1c26cd45447ab226b))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
* **sonar:** source projectVersion from package.json at scan time ([#188](https://github.com/funkadelic/claude-nomad/issues/188)) ([c00dd6a](https://github.com/funkadelic/claude-nomad/commit/c00dd6a5135a8753d56cf4165cf5b9782c9bde3e))
|
|
14
|
+
|
|
3
15
|
## [0.32.1](https://github.com/funkadelic/claude-nomad/compare/v0.32.0...v0.32.1) (2026-05-30)
|
|
4
16
|
|
|
5
17
|
|
package/package.json
CHANGED
|
@@ -1,11 +1,40 @@
|
|
|
1
1
|
import { NEVER_SYNC } from './config.ts';
|
|
2
|
+
import { NomadFatal } from './utils.ts';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `logical` keys in `path-map.json` are project identifiers (e.g. `ha-acwd`,
|
|
6
|
+
* `foo`), never path fragments. A crafted key like `../escape` or `foo/bar`
|
|
7
|
+
* would escape `shared/projects/` (or `shared/extras/`) via `join()` (which
|
|
8
|
+
* normalizes `..`) and land content somewhere unexpected on the filesystem.
|
|
9
|
+
* The push allow-list catches such commits at the `git add` boundary, but the
|
|
10
|
+
* filesystem mutation has already happened by then. This check fails fast
|
|
11
|
+
* before any write. The pattern matches what every reasonable project name
|
|
12
|
+
* looks like and rejects everything else.
|
|
13
|
+
*/
|
|
14
|
+
const SAFE_LOGICAL = /^[A-Za-z0-9._-]+$/;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Throw `NomadFatal` unless `logical` is a path-separator-free project
|
|
18
|
+
* identifier (see `SAFE_LOGICAL`). Path-traversal defense-in-depth; called
|
|
19
|
+
* before any filesystem mutation by every remap and extras op that joins
|
|
20
|
+
* `logical` into a filesystem path.
|
|
21
|
+
*
|
|
22
|
+
* @param logical - A `path-map.json` projects key to validate.
|
|
23
|
+
*/
|
|
24
|
+
export function assertSafeLogical(logical: string): void {
|
|
25
|
+
if (!SAFE_LOGICAL.test(logical) || logical === '.' || logical === '..') {
|
|
26
|
+
throw new NomadFatal(
|
|
27
|
+
`invalid logical name in path-map.json: ${JSON.stringify(logical)} (must match [A-Za-z0-9._-]+; no path separators or '..')`,
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
2
31
|
|
|
3
32
|
/**
|
|
4
33
|
* Single-segment path characters allowed in a `sharedDirs` entry. Mirrors
|
|
5
|
-
* `SAFE_LOGICAL`
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
34
|
+
* `SAFE_LOGICAL` above but applied to global support directory names rather
|
|
35
|
+
* than per-project logical names. Must match `^[A-Za-z0-9._-]+$` so no path
|
|
36
|
+
* separator, no shell-special character, no leading dot that would collide
|
|
37
|
+
* with a hidden state directory.
|
|
9
38
|
*/
|
|
10
39
|
const SAFE_SEGMENT = /^[A-Za-z0-9._-]+$/;
|
|
11
40
|
|
|
@@ -2,30 +2,7 @@ import { isAbsolute, normalize } from 'node:path';
|
|
|
2
2
|
|
|
3
3
|
import { NomadFatal } from './utils.ts';
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
* `logical` keys in `path-map.json` are project identifiers (e.g. `ha-acwd`,
|
|
7
|
-
* `foo`), never path fragments. A crafted key like `../escape` or `foo/bar`
|
|
8
|
-
* would escape `shared/extras/` via `join()` (which normalizes `..`) and land
|
|
9
|
-
* content somewhere unexpected on the filesystem. The push allow-list catches
|
|
10
|
-
* such commits at the `git add` boundary, but the filesystem mutation has
|
|
11
|
-
* already happened by then. This check fails fast before any write. The
|
|
12
|
-
* pattern matches what every reasonable project name looks like and rejects
|
|
13
|
-
* everything else; tighten only if a real project needs broader characters.
|
|
14
|
-
*/
|
|
15
|
-
const SAFE_LOGICAL = /^[A-Za-z0-9._-]+$/;
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Throw `NomadFatal` unless `logical` is a path-separator-free project
|
|
19
|
-
* identifier (see `SAFE_LOGICAL`). Path-traversal defense-in-depth; called
|
|
20
|
-
* before any filesystem mutation by every extras op.
|
|
21
|
-
*/
|
|
22
|
-
export function assertSafeLogical(logical: string): void {
|
|
23
|
-
if (!SAFE_LOGICAL.test(logical) || logical === '.' || logical === '..') {
|
|
24
|
-
throw new NomadFatal(
|
|
25
|
-
`invalid logical name in path-map.json extras: ${JSON.stringify(logical)} (must match [A-Za-z0-9._-]+; no path separators or '..')`,
|
|
26
|
-
);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
5
|
+
export { assertSafeLogical } from './config.sharedDirs.guard.ts';
|
|
29
6
|
|
|
30
7
|
/**
|
|
31
8
|
* Reject `localRoot` values that contain unnormalized segments (`..`,
|
package/src/remap.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { cpSync, existsSync, mkdirSync, readdirSync, rmSync, statSync } from 'node:fs';
|
|
2
2
|
import { join, relative, sep } from 'node:path';
|
|
3
3
|
|
|
4
|
+
import { assertSafeLogical } from './config.sharedDirs.guard.ts';
|
|
4
5
|
import { CLAUDE_HOME, HOST, REPO_HOME, type PathMap } from './config.ts';
|
|
5
6
|
import { die, log } from './utils.ts';
|
|
6
7
|
import { backupBeforeWrite, backupRepoWrite } from './utils.fs.ts';
|
|
@@ -81,6 +82,7 @@ export function remapPull(
|
|
|
81
82
|
if (!dryRun) mkdirSync(localProjects, { recursive: true });
|
|
82
83
|
|
|
83
84
|
for (const [logical, hosts] of Object.entries(map.projects)) {
|
|
85
|
+
assertSafeLogical(logical);
|
|
84
86
|
const localPath = hosts[HOST];
|
|
85
87
|
if (!localPath || localPath === 'TBD') {
|
|
86
88
|
unmapped++;
|
|
@@ -126,6 +128,7 @@ function buildReverseMap(map: PathMap): Map<string, string> {
|
|
|
126
128
|
const reverse = new Map<string, string>();
|
|
127
129
|
const encodedPaths = new Map<string, string>();
|
|
128
130
|
for (const [logical, hosts] of Object.entries(map.projects)) {
|
|
131
|
+
assertSafeLogical(logical);
|
|
129
132
|
const p = hosts[HOST];
|
|
130
133
|
if (!p || p === 'TBD') continue;
|
|
131
134
|
const encoded = encodePath(p);
|