@quark-hq/quark-security 0.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Acko Technologies
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/fs.d.ts ADDED
@@ -0,0 +1,18 @@
1
+ import fs from "fs";
2
+ export declare function existsSyncSafe(root: string, candidate: string): boolean;
3
+ export declare function mkdirSyncSafe(root: string, candidate: string, options?: fs.MakeDirectoryOptions): void;
4
+ export declare function readFileSyncSafe(root: string, candidate: string, encoding?: BufferEncoding): string;
5
+ type WriteFileSyncOptions = {
6
+ encoding?: BufferEncoding;
7
+ mode?: number;
8
+ flag?: string;
9
+ };
10
+ export declare function writeFileSyncSafe(root: string, candidate: string, data: string | NodeJS.ArrayBufferView, options?: BufferEncoding | WriteFileSyncOptions): void;
11
+ export declare function rmSyncSafe(root: string, candidate: string, options?: fs.RmOptions): void;
12
+ export declare function readFileSafe(root: string, candidate: string): Promise<string>;
13
+ export declare function readdirWithFileTypesSafe(root: string, candidate: string): Promise<fs.Dirent[]>;
14
+ export declare function writeFileSafe(root: string, candidate: string, data: string): Promise<void>;
15
+ export declare function unlinkSafe(root: string, candidate: string): Promise<void>;
16
+ export declare function rmSafe(root: string, candidate: string, options?: fs.RmOptions): Promise<void>;
17
+ export {};
18
+ //# sourceMappingURL=fs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../src/fs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AASpB,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAGvE;AAED,wBAAgB,aAAa,CACzB,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,EAAE,CAAC,oBAAoB,GAClC,IAAI,CAGN;AAED,wBAAgB,gBAAgB,CAC5B,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,QAAQ,GAAE,cAAuB,GAClC,MAAM,CAGR;AAED,KAAK,oBAAoB,GAAG;IACxB,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,wBAAgB,iBAAiB,CAC7B,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,eAAe,EACrC,OAAO,CAAC,EAAE,cAAc,GAAG,oBAAoB,GAChD,IAAI,CAON;AAED,wBAAgB,UAAU,CACtB,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,EAAE,CAAC,SAAS,GACvB,IAAI,CAGN;AAGD,wBAAsB,YAAY,CAC9B,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,GAClB,OAAO,CAAC,MAAM,CAAC,CAGjB;AAED,wBAAsB,wBAAwB,CAC1C,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,GAClB,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,CAGtB;AAED,wBAAsB,aAAa,CAC/B,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAGf;AAED,wBAAsB,UAAU,CAC5B,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC,CAGf;AAED,wBAAsB,MAAM,CACxB,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,EAAE,CAAC,SAAS,GACvB,OAAO,CAAC,IAAI,CAAC,CAGf"}
package/dist/fs.js ADDED
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.existsSyncSafe = existsSyncSafe;
7
+ exports.mkdirSyncSafe = mkdirSyncSafe;
8
+ exports.readFileSyncSafe = readFileSyncSafe;
9
+ exports.writeFileSyncSafe = writeFileSyncSafe;
10
+ exports.rmSyncSafe = rmSyncSafe;
11
+ exports.readFileSafe = readFileSafe;
12
+ exports.readdirWithFileTypesSafe = readdirWithFileTypesSafe;
13
+ exports.writeFileSafe = writeFileSafe;
14
+ exports.unlinkSafe = unlinkSafe;
15
+ exports.rmSafe = rmSafe;
16
+ const fs_1 = __importDefault(require("fs"));
17
+ const promises_1 = __importDefault(require("fs/promises"));
18
+ const path_1 = __importDefault(require("path"));
19
+ const paths_1 = require("./paths");
20
+ function toSafePath(root, candidate) {
21
+ return (0, paths_1.assertPathInsideRoot)(root, path_1.default.resolve(candidate));
22
+ }
23
+ function existsSyncSafe(root, candidate) {
24
+ // bearer:disable javascript_lang_non_literal_fs_filename
25
+ return fs_1.default.existsSync(toSafePath(root, candidate));
26
+ }
27
+ function mkdirSyncSafe(root, candidate, options) {
28
+ // bearer:disable javascript_lang_non_literal_fs_filename
29
+ fs_1.default.mkdirSync(toSafePath(root, candidate), options);
30
+ }
31
+ function readFileSyncSafe(root, candidate, encoding = "utf8") {
32
+ // bearer:disable javascript_lang_non_literal_fs_filename
33
+ return fs_1.default.readFileSync(toSafePath(root, candidate), encoding);
34
+ }
35
+ function writeFileSyncSafe(root, candidate, data, options) {
36
+ const safe = toSafePath(root, candidate);
37
+ const resolved = typeof options === "string" || options === undefined
38
+ ? { encoding: options ?? "utf8" }
39
+ : options;
40
+ // bearer:disable javascript_lang_non_literal_fs_filename
41
+ fs_1.default.writeFileSync(safe, data, resolved);
42
+ }
43
+ function rmSyncSafe(root, candidate, options) {
44
+ // bearer:disable javascript_lang_non_literal_fs_filename
45
+ fs_1.default.rmSync(toSafePath(root, candidate), options);
46
+ }
47
+ async function readFileSafe(root, candidate) {
48
+ // bearer:disable javascript_lang_non_literal_fs_filename
49
+ return promises_1.default.readFile(toSafePath(root, candidate), "utf8");
50
+ }
51
+ async function readdirWithFileTypesSafe(root, candidate) {
52
+ // bearer:disable javascript_lang_non_literal_fs_filename
53
+ return promises_1.default.readdir(toSafePath(root, candidate), { withFileTypes: true });
54
+ }
55
+ async function writeFileSafe(root, candidate, data) {
56
+ // bearer:disable javascript_lang_non_literal_fs_filename
57
+ await promises_1.default.writeFile(toSafePath(root, candidate), data, "utf8");
58
+ }
59
+ async function unlinkSafe(root, candidate) {
60
+ // bearer:disable javascript_lang_non_literal_fs_filename
61
+ await promises_1.default.unlink(toSafePath(root, candidate));
62
+ }
63
+ async function rmSafe(root, candidate, options) {
64
+ // bearer:disable javascript_lang_non_literal_fs_filename
65
+ await promises_1.default.rm(toSafePath(root, candidate), options);
66
+ }
package/dist/git.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ export declare function assertSafeGitBranch(branch: string, label: string): void;
2
+ export declare function assertSafeGitRef(ref: string, label: string): void;
3
+ /**
4
+ * Path used in `git show <ref>:<path>` (repo-relative, POSIX-style segments).
5
+ */
6
+ export declare function assertSafeRepoRelativePathForGitShow(relativePath: string, label: string): void;
7
+ //# sourceMappingURL=git.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"AAuCA,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAUvE;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAUjE;AAED;;GAEG;AACH,wBAAgB,oCAAoC,CAChD,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,MAAM,GACd,IAAI,CAgBN"}
package/dist/git.js ADDED
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.assertSafeGitBranch = assertSafeGitBranch;
4
+ exports.assertSafeGitRef = assertSafeGitRef;
5
+ exports.assertSafeRepoRelativePathForGitShow = assertSafeRepoRelativePathForGitShow;
6
+ const MAX_REF_LEN = 256;
7
+ /** Git branch / simple ref names (e.g. main, feature/foo). */
8
+ const BRANCH_PATTERN = /^[a-zA-Z0-9]([a-zA-Z0-9._/-]*[a-zA-Z0-9])?$/;
9
+ /**
10
+ * Tags, full SHAs, short SHAs, refs like refs/tags/v1.0.0.
11
+ * Conservative ASCII set; rejects shell metacharacters and path tricks.
12
+ */
13
+ const GIT_REF_PATTERN = /^[a-zA-Z0-9]([a-zA-Z0-9._^~/@+-]*[a-zA-Z0-9])?$/;
14
+ function hasForbiddenSequences(s) {
15
+ return (s.includes("..") ||
16
+ s.includes("\n") ||
17
+ s.includes("\r") ||
18
+ s.includes("\0") ||
19
+ s.includes("`") ||
20
+ s.includes("$") ||
21
+ s.includes(";") ||
22
+ s.includes("|") ||
23
+ s.includes("&") ||
24
+ s.includes("(") ||
25
+ s.includes(")") ||
26
+ s.includes("<") ||
27
+ s.includes(">") ||
28
+ s.includes("*") ||
29
+ s.includes("?") ||
30
+ s.includes("[") ||
31
+ s.includes("]") ||
32
+ s.includes("\\") ||
33
+ s.includes("\"") ||
34
+ s.includes("'") ||
35
+ s.includes(" ") ||
36
+ s.includes(":") // ref:path separator; ref must not contain ':'
37
+ );
38
+ }
39
+ function assertSafeGitBranch(branch, label) {
40
+ if (!branch || branch.length > MAX_REF_LEN) {
41
+ throw new Error(`Invalid ${label}: length or empty`);
42
+ }
43
+ if (hasForbiddenSequences(branch)) {
44
+ throw new Error(`Invalid ${label}: forbidden characters`);
45
+ }
46
+ if (!BRANCH_PATTERN.test(branch)) {
47
+ throw new Error(`Invalid ${label}: format`);
48
+ }
49
+ }
50
+ function assertSafeGitRef(ref, label) {
51
+ if (!ref || ref.length > MAX_REF_LEN) {
52
+ throw new Error(`Invalid ${label}: length or empty`);
53
+ }
54
+ if (hasForbiddenSequences(ref)) {
55
+ throw new Error(`Invalid ${label}: forbidden characters`);
56
+ }
57
+ if (!GIT_REF_PATTERN.test(ref)) {
58
+ throw new Error(`Invalid ${label}: format`);
59
+ }
60
+ }
61
+ /**
62
+ * Path used in `git show <ref>:<path>` (repo-relative, POSIX-style segments).
63
+ */
64
+ function assertSafeRepoRelativePathForGitShow(relativePath, label) {
65
+ if (!relativePath || relativePath.length > 4096) {
66
+ throw new Error(`Invalid ${label}: length or empty`);
67
+ }
68
+ if (relativePath.startsWith("/") || relativePath.startsWith("\\")) {
69
+ throw new Error(`Invalid ${label}: must be relative`);
70
+ }
71
+ if (relativePath.includes("\0") || relativePath.includes(":")) {
72
+ throw new Error(`Invalid ${label}: forbidden characters`);
73
+ }
74
+ const parts = relativePath.split(/[/\\]/);
75
+ for (const p of parts) {
76
+ if (p === "..") {
77
+ throw new Error(`Invalid ${label}: must not contain '..'`);
78
+ }
79
+ }
80
+ }
@@ -0,0 +1,7 @@
1
+ export { assertPathInsideRoot, assertSafePathSegment, resolveRelativeNameUnderRoot, resolveUnderWorkspaceRoot, } from "./paths";
2
+ export { existsSyncSafe, mkdirSyncSafe, readFileSafe, readFileSyncSafe, readdirWithFileTypesSafe, rmSafe, rmSyncSafe, unlinkSafe, writeFileSafe, writeFileSyncSafe, } from "./fs";
3
+ export { validateNpmPackageName, assertValidNpmPackageName, type NpmNameValidation, } from "./npm";
4
+ export { assertSafeGitBranch, assertSafeGitRef, assertSafeRepoRelativePathForGitShow, } from "./git";
5
+ export { stripUrlCredentials, formatErrorMessage, shouldLogErrorStack, } from "./logging";
6
+ export { spawnSyncSafe, assertSpawnOk, assertSafeExecutableRef, assertSafeSpawnArg, assertSafeSpawnArgs, type SpawnSyncSafeOptions, } from "./spawn";
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,oBAAoB,EACpB,qBAAqB,EACrB,4BAA4B,EAC5B,yBAAyB,GAC5B,MAAM,SAAS,CAAC;AACjB,OAAO,EACH,cAAc,EACd,aAAa,EACb,YAAY,EACZ,gBAAgB,EAChB,wBAAwB,EACxB,MAAM,EACN,UAAU,EACV,UAAU,EACV,aAAa,EACb,iBAAiB,GACpB,MAAM,MAAM,CAAC;AACd,OAAO,EACH,sBAAsB,EACtB,yBAAyB,EACzB,KAAK,iBAAiB,GACzB,MAAM,OAAO,CAAC;AACf,OAAO,EACH,mBAAmB,EACnB,gBAAgB,EAChB,oCAAoC,GACvC,MAAM,OAAO,CAAC;AACf,OAAO,EACH,mBAAmB,EACnB,kBAAkB,EAClB,mBAAmB,GACtB,MAAM,WAAW,CAAC;AACnB,OAAO,EACH,aAAa,EACb,aAAa,EACb,uBAAuB,EACvB,kBAAkB,EAClB,mBAAmB,EACnB,KAAK,oBAAoB,GAC5B,MAAM,SAAS,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.assertSafeSpawnArgs = exports.assertSafeSpawnArg = exports.assertSafeExecutableRef = exports.assertSpawnOk = exports.spawnSyncSafe = exports.shouldLogErrorStack = exports.formatErrorMessage = exports.stripUrlCredentials = exports.assertSafeRepoRelativePathForGitShow = exports.assertSafeGitRef = exports.assertSafeGitBranch = exports.assertValidNpmPackageName = exports.validateNpmPackageName = exports.writeFileSyncSafe = exports.writeFileSafe = exports.unlinkSafe = exports.rmSyncSafe = exports.rmSafe = exports.readdirWithFileTypesSafe = exports.readFileSyncSafe = exports.readFileSafe = exports.mkdirSyncSafe = exports.existsSyncSafe = exports.resolveUnderWorkspaceRoot = exports.resolveRelativeNameUnderRoot = exports.assertSafePathSegment = exports.assertPathInsideRoot = void 0;
4
+ var paths_1 = require("./paths");
5
+ Object.defineProperty(exports, "assertPathInsideRoot", { enumerable: true, get: function () { return paths_1.assertPathInsideRoot; } });
6
+ Object.defineProperty(exports, "assertSafePathSegment", { enumerable: true, get: function () { return paths_1.assertSafePathSegment; } });
7
+ Object.defineProperty(exports, "resolveRelativeNameUnderRoot", { enumerable: true, get: function () { return paths_1.resolveRelativeNameUnderRoot; } });
8
+ Object.defineProperty(exports, "resolveUnderWorkspaceRoot", { enumerable: true, get: function () { return paths_1.resolveUnderWorkspaceRoot; } });
9
+ var fs_1 = require("./fs");
10
+ Object.defineProperty(exports, "existsSyncSafe", { enumerable: true, get: function () { return fs_1.existsSyncSafe; } });
11
+ Object.defineProperty(exports, "mkdirSyncSafe", { enumerable: true, get: function () { return fs_1.mkdirSyncSafe; } });
12
+ Object.defineProperty(exports, "readFileSafe", { enumerable: true, get: function () { return fs_1.readFileSafe; } });
13
+ Object.defineProperty(exports, "readFileSyncSafe", { enumerable: true, get: function () { return fs_1.readFileSyncSafe; } });
14
+ Object.defineProperty(exports, "readdirWithFileTypesSafe", { enumerable: true, get: function () { return fs_1.readdirWithFileTypesSafe; } });
15
+ Object.defineProperty(exports, "rmSafe", { enumerable: true, get: function () { return fs_1.rmSafe; } });
16
+ Object.defineProperty(exports, "rmSyncSafe", { enumerable: true, get: function () { return fs_1.rmSyncSafe; } });
17
+ Object.defineProperty(exports, "unlinkSafe", { enumerable: true, get: function () { return fs_1.unlinkSafe; } });
18
+ Object.defineProperty(exports, "writeFileSafe", { enumerable: true, get: function () { return fs_1.writeFileSafe; } });
19
+ Object.defineProperty(exports, "writeFileSyncSafe", { enumerable: true, get: function () { return fs_1.writeFileSyncSafe; } });
20
+ var npm_1 = require("./npm");
21
+ Object.defineProperty(exports, "validateNpmPackageName", { enumerable: true, get: function () { return npm_1.validateNpmPackageName; } });
22
+ Object.defineProperty(exports, "assertValidNpmPackageName", { enumerable: true, get: function () { return npm_1.assertValidNpmPackageName; } });
23
+ var git_1 = require("./git");
24
+ Object.defineProperty(exports, "assertSafeGitBranch", { enumerable: true, get: function () { return git_1.assertSafeGitBranch; } });
25
+ Object.defineProperty(exports, "assertSafeGitRef", { enumerable: true, get: function () { return git_1.assertSafeGitRef; } });
26
+ Object.defineProperty(exports, "assertSafeRepoRelativePathForGitShow", { enumerable: true, get: function () { return git_1.assertSafeRepoRelativePathForGitShow; } });
27
+ var logging_1 = require("./logging");
28
+ Object.defineProperty(exports, "stripUrlCredentials", { enumerable: true, get: function () { return logging_1.stripUrlCredentials; } });
29
+ Object.defineProperty(exports, "formatErrorMessage", { enumerable: true, get: function () { return logging_1.formatErrorMessage; } });
30
+ Object.defineProperty(exports, "shouldLogErrorStack", { enumerable: true, get: function () { return logging_1.shouldLogErrorStack; } });
31
+ var spawn_1 = require("./spawn");
32
+ Object.defineProperty(exports, "spawnSyncSafe", { enumerable: true, get: function () { return spawn_1.spawnSyncSafe; } });
33
+ Object.defineProperty(exports, "assertSpawnOk", { enumerable: true, get: function () { return spawn_1.assertSpawnOk; } });
34
+ Object.defineProperty(exports, "assertSafeExecutableRef", { enumerable: true, get: function () { return spawn_1.assertSafeExecutableRef; } });
35
+ Object.defineProperty(exports, "assertSafeSpawnArg", { enumerable: true, get: function () { return spawn_1.assertSafeSpawnArg; } });
36
+ Object.defineProperty(exports, "assertSafeSpawnArgs", { enumerable: true, get: function () { return spawn_1.assertSafeSpawnArgs; } });
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Removes userinfo from URLs so registry URLs with embedded credentials are not logged.
3
+ */
4
+ export declare function stripUrlCredentials(url: string): string;
5
+ /**
6
+ * Safe one-line message for console (no stack unless debug).
7
+ */
8
+ export declare function formatErrorMessage(error: unknown): string;
9
+ export declare function shouldLogErrorStack(): boolean;
10
+ //# sourceMappingURL=logging.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logging.d.ts","sourceRoot":"","sources":["../src/logging.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CASvD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAKzD;AAED,wBAAgB,mBAAmB,IAAI,OAAO,CAM7C"}
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.stripUrlCredentials = stripUrlCredentials;
4
+ exports.formatErrorMessage = formatErrorMessage;
5
+ exports.shouldLogErrorStack = shouldLogErrorStack;
6
+ /**
7
+ * Removes userinfo from URLs so registry URLs with embedded credentials are not logged.
8
+ */
9
+ function stripUrlCredentials(url) {
10
+ try {
11
+ const u = new URL(url);
12
+ u.username = "";
13
+ u.password = "";
14
+ return u.toString();
15
+ }
16
+ catch {
17
+ return url.replace(/\/\/[^@/]+@/g, "//***@");
18
+ }
19
+ }
20
+ /**
21
+ * Safe one-line message for console (no stack unless debug).
22
+ */
23
+ function formatErrorMessage(error) {
24
+ if (error instanceof Error) {
25
+ return error.message;
26
+ }
27
+ return String(error);
28
+ }
29
+ function shouldLogErrorStack() {
30
+ return (process.env.DEBUG === "1" ||
31
+ process.env.DEBUG === "true" ||
32
+ process.env.NODE_ENV === "development");
33
+ }
package/dist/npm.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ export interface NpmNameValidation {
2
+ valid: boolean;
3
+ reason: string;
4
+ }
5
+ /**
6
+ * Validates npm-style package or folder names (aligned with npm package name rules used in init).
7
+ */
8
+ export declare function validateNpmPackageName(name: string): NpmNameValidation;
9
+ export declare function assertValidNpmPackageName(name: string, label: string): void;
10
+ //# sourceMappingURL=npm.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"npm.d.ts","sourceRoot":"","sources":["../src/npm.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,iBAAiB;IAC9B,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,CAsBtE;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAK3E"}
package/dist/npm.js ADDED
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateNpmPackageName = validateNpmPackageName;
4
+ exports.assertValidNpmPackageName = assertValidNpmPackageName;
5
+ /**
6
+ * Validates npm-style package or folder names (aligned with npm package name rules used in init).
7
+ */
8
+ function validateNpmPackageName(name) {
9
+ const nameRegex = /^(?:@[\w-]+\/)?[\w.-]+$/;
10
+ if (!nameRegex.test(name)) {
11
+ return {
12
+ valid: false,
13
+ reason: "The name must be a valid npm package name (alphanumeric, dashes, dots, optional @scope/).",
14
+ };
15
+ }
16
+ if (name.trim().length === 0 ||
17
+ name.includes(" ") ||
18
+ name.toLowerCase() !== name ||
19
+ name.startsWith(".") ||
20
+ name.startsWith("_")) {
21
+ return {
22
+ valid: false,
23
+ reason: "The name contains invalid characters or formatting.",
24
+ };
25
+ }
26
+ return { valid: true, reason: "" };
27
+ }
28
+ function assertValidNpmPackageName(name, label) {
29
+ const r = validateNpmPackageName(name);
30
+ if (!r.valid) {
31
+ throw new Error(`${label}: ${r.reason}`);
32
+ }
33
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Resolves `candidate` and throws if it is not under `root` (after normalization).
3
+ * Prevents path traversal via `..` or symlinks escaping the root when combined with realpath callers.
4
+ */
5
+ export declare function assertPathInsideRoot(root: string, candidate: string): string;
6
+ /**
7
+ * Ensures a path segment used as a single folder/package name under cwd cannot escape upward.
8
+ */
9
+ /**
10
+ * Resolves a single path segment (e.g. package or project name) under root and rejects traversal.
11
+ */
12
+ export declare function resolveRelativeNameUnderRoot(root: string, name: string): string;
13
+ /**
14
+ * Resolves a workspace-relative directory (e.g. Nx `project.data.root` like `packages/foo` or `.`)
15
+ * under `workspaceRoot` and rejects traversal outside the workspace.
16
+ */
17
+ export declare function resolveUnderWorkspaceRoot(workspaceRoot: string, relativePath: string): string;
18
+ export declare function assertSafePathSegment(segment: string, label: string): void;
19
+ //# sourceMappingURL=paths.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAY5E;AAED;;GAEG;AACH;;GAEG;AACH,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAK/E;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CACrC,aAAa,EAAE,MAAM,EACrB,YAAY,EAAE,MAAM,GACrB,MAAM,CAeR;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAc1E"}
package/dist/paths.js ADDED
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.assertPathInsideRoot = assertPathInsideRoot;
7
+ exports.resolveRelativeNameUnderRoot = resolveRelativeNameUnderRoot;
8
+ exports.resolveUnderWorkspaceRoot = resolveUnderWorkspaceRoot;
9
+ exports.assertSafePathSegment = assertSafePathSegment;
10
+ const path_1 = __importDefault(require("path"));
11
+ /**
12
+ * Resolves `candidate` and throws if it is not under `root` (after normalization).
13
+ * Prevents path traversal via `..` or symlinks escaping the root when combined with realpath callers.
14
+ */
15
+ function assertPathInsideRoot(root, candidate) {
16
+ // bearer:disable javascript_lang_path_traversal
17
+ const resolvedRoot = path_1.default.resolve(root);
18
+ // bearer:disable javascript_lang_path_traversal
19
+ const resolved = path_1.default.resolve(candidate);
20
+ const rel = path_1.default.relative(resolvedRoot, resolved);
21
+ if (rel.startsWith("..") || path_1.default.isAbsolute(rel)) {
22
+ throw new Error(`Path is outside allowed root: ${candidate}`);
23
+ }
24
+ return resolved;
25
+ }
26
+ /**
27
+ * Ensures a path segment used as a single folder/package name under cwd cannot escape upward.
28
+ */
29
+ /**
30
+ * Resolves a single path segment (e.g. package or project name) under root and rejects traversal.
31
+ */
32
+ function resolveRelativeNameUnderRoot(root, name) {
33
+ assertSafePathSegment(name, "path");
34
+ // bearer:disable javascript_lang_path_traversal
35
+ const resolved = path_1.default.resolve(root, name);
36
+ return assertPathInsideRoot(root, resolved);
37
+ }
38
+ /**
39
+ * Resolves a workspace-relative directory (e.g. Nx `project.data.root` like `packages/foo` or `.`)
40
+ * under `workspaceRoot` and rejects traversal outside the workspace.
41
+ */
42
+ function resolveUnderWorkspaceRoot(workspaceRoot, relativePath) {
43
+ if (typeof relativePath !== "string" || relativePath.includes("\0")) {
44
+ throw new Error("Invalid relative path");
45
+ }
46
+ const trimmed = relativePath.trim();
47
+ if (trimmed === "" || trimmed === ".") {
48
+ return assertPathInsideRoot(workspaceRoot, path_1.default.resolve(workspaceRoot));
49
+ }
50
+ const normalized = path_1.default.normalize(trimmed);
51
+ if (normalized.split(/[/\\]/).includes("..")) {
52
+ throw new Error("Relative path must not contain '..'");
53
+ }
54
+ // bearer:disable javascript_lang_path_traversal
55
+ const joined = path_1.default.resolve(workspaceRoot, trimmed);
56
+ return assertPathInsideRoot(workspaceRoot, joined);
57
+ }
58
+ function assertSafePathSegment(segment, label) {
59
+ if (!segment || segment.trim() !== segment) {
60
+ throw new Error(`${label} must be a non-empty string without leading or trailing whitespace`);
61
+ }
62
+ if (segment.includes("\0")) {
63
+ throw new Error(`${label} contains invalid characters`);
64
+ }
65
+ if (path_1.default.isAbsolute(segment)) {
66
+ throw new Error(`${label} must be a relative name, not an absolute path`);
67
+ }
68
+ const normalized = path_1.default.normalize(segment);
69
+ if (normalized.split(path_1.default.sep).some((p) => p === "..")) {
70
+ throw new Error(`${label} must not contain '..'`);
71
+ }
72
+ }
@@ -0,0 +1,20 @@
1
+ import { SpawnSyncReturns } from "child_process";
2
+ /**
3
+ * Validates a single argv string for use with {@link spawnSyncSafe} (no shell).
4
+ */
5
+ export declare function assertSafeSpawnArg(arg: string): void;
6
+ export declare function assertSafeSpawnArgs(args: readonly string[]): string[];
7
+ export declare function assertSafeExecutableRef(file: string): void;
8
+ export declare function assertSpawnOk(result: SpawnSyncReturns<string | Buffer>, what: string): void;
9
+ export interface SpawnSyncSafeOptions {
10
+ cwd?: string;
11
+ encoding?: BufferEncoding;
12
+ stdio?: "inherit" | "pipe" | "ignore";
13
+ maxBuffer?: number;
14
+ env?: NodeJS.ProcessEnv;
15
+ }
16
+ /**
17
+ * Runs a binary with argv (no shell). Use instead of execSync(string).
18
+ */
19
+ export declare function spawnSyncSafe(file: string, args: readonly string[], options?: SpawnSyncSafeOptions): SpawnSyncReturns<string | Buffer>;
20
+ //# sourceMappingURL=spawn.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spawn.d.ts","sourceRoot":"","sources":["../src/spawn.ts"],"names":[],"mappings":"AACA,OAAO,EAAa,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAO5D;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAUpD;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAOrE;AAeD,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAqB1D;AAED,wBAAgB,aAAa,CACzB,MAAM,EAAE,gBAAgB,CAAC,MAAM,GAAG,MAAM,CAAC,EACzC,IAAI,EAAE,MAAM,GACb,IAAI,CAON;AAED,MAAM,WAAW,oBAAoB;IACjC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,KAAK,CAAC,EAAE,SAAS,GAAG,MAAM,GAAG,QAAQ,CAAC;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CAC3B;AAED;;GAEG;AACH,wBAAgB,aAAa,CACzB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,GAAE,oBAAyB,GACnC,gBAAgB,CAAC,MAAM,GAAG,MAAM,CAAC,CAmBnC"}
package/dist/spawn.js ADDED
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.assertSafeSpawnArg = assertSafeSpawnArg;
7
+ exports.assertSafeSpawnArgs = assertSafeSpawnArgs;
8
+ exports.assertSafeExecutableRef = assertSafeExecutableRef;
9
+ exports.assertSpawnOk = assertSpawnOk;
10
+ exports.spawnSyncSafe = spawnSyncSafe;
11
+ const path_1 = __importDefault(require("path"));
12
+ const child_process_1 = require("child_process");
13
+ /**
14
+ * Validates the executable passed to spawn (no shell): basename-only CLIs or paths without ".." segments.
15
+ */
16
+ const MAX_SPAWN_ARG_LENGTH = 1024 * 1024;
17
+ /**
18
+ * Validates a single argv string for use with {@link spawnSyncSafe} (no shell).
19
+ */
20
+ function assertSafeSpawnArg(arg) {
21
+ if (typeof arg !== "string") {
22
+ throw new Error("Spawn argument must be a string");
23
+ }
24
+ if (arg.includes("\0")) {
25
+ throw new Error("Spawn argument contains null byte");
26
+ }
27
+ if (arg.length > MAX_SPAWN_ARG_LENGTH) {
28
+ throw new Error("Spawn argument is too long");
29
+ }
30
+ }
31
+ function assertSafeSpawnArgs(args) {
32
+ const out = [];
33
+ for (const arg of args) {
34
+ assertSafeSpawnArg(arg);
35
+ out.push(arg);
36
+ }
37
+ return out;
38
+ }
39
+ function assertSafeCwd(cwd) {
40
+ if (cwd === undefined) {
41
+ return undefined;
42
+ }
43
+ if (typeof cwd !== "string" || cwd.includes("\0")) {
44
+ throw new Error("Working directory must be a string without null bytes");
45
+ }
46
+ if (cwd.length > 8192) {
47
+ throw new Error("Working directory path is too long");
48
+ }
49
+ return path_1.default.resolve(cwd);
50
+ }
51
+ function assertSafeExecutableRef(file) {
52
+ if (typeof file !== "string" || file.length === 0) {
53
+ throw new Error("Executable must be a non-empty string");
54
+ }
55
+ if (file.length > 4096) {
56
+ throw new Error("Executable path is too long");
57
+ }
58
+ if (/[\0\n\r]/.test(file)) {
59
+ throw new Error("Executable path contains invalid characters");
60
+ }
61
+ const hasDirSep = file.includes("/") || file.includes("\\");
62
+ if (!hasDirSep) {
63
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(file)) {
64
+ throw new Error("Invalid executable name");
65
+ }
66
+ return;
67
+ }
68
+ const normalized = path_1.default.normalize(file);
69
+ if (normalized.split(path_1.default.sep).includes("..")) {
70
+ throw new Error("Invalid executable path");
71
+ }
72
+ }
73
+ function assertSpawnOk(result, what) {
74
+ if (result.error) {
75
+ throw result.error;
76
+ }
77
+ if (result.status !== 0 && result.status !== null) {
78
+ throw new Error(`${what} failed (exit ${result.status})`);
79
+ }
80
+ }
81
+ /**
82
+ * Runs a binary with argv (no shell). Use instead of execSync(string).
83
+ */
84
+ function spawnSyncSafe(file, args, options = {}) {
85
+ assertSafeExecutableRef(file);
86
+ const validatedArgs = assertSafeSpawnArgs(args);
87
+ const stdio = options.stdio === "inherit"
88
+ ? "inherit"
89
+ : options.stdio === "ignore"
90
+ ? "ignore"
91
+ : "pipe";
92
+ const cwd = assertSafeCwd(options.cwd);
93
+ return (0, child_process_1.spawnSync)(file, validatedArgs, {
94
+ cwd,
95
+ encoding: options.encoding ?? "utf8",
96
+ stdio,
97
+ shell: false,
98
+ maxBuffer: options.maxBuffer,
99
+ env: options.env,
100
+ });
101
+ }
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@quark-hq/quark-security",
3
+ "version": "0.0.1",
4
+ "private": false,
5
+ "description": "Shared validation, path-safety, and spawn helpers for Quark CLI tooling",
6
+ "keywords": [
7
+ "quark",
8
+ "security",
9
+ "path-traversal",
10
+ "spawn",
11
+ "validation",
12
+ "typescript"
13
+ ],
14
+ "homepage": "https://github.com/ackotech/quark#readme",
15
+ "bugs": {
16
+ "url": "https://github.com/ackotech/quark/issues"
17
+ },
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/ackotech/quark.git",
21
+ "directory": "cli/quark-security"
22
+ },
23
+ "license": "MIT",
24
+ "author": "Acko Technologies",
25
+ "main": "dist/index.js",
26
+ "types": "dist/index.d.ts",
27
+ "exports": {
28
+ ".": {
29
+ "types": "./dist/index.d.ts",
30
+ "require": "./dist/index.js",
31
+ "default": "./dist/index.js"
32
+ },
33
+ "./package.json": "./package.json"
34
+ },
35
+ "files": [
36
+ "dist"
37
+ ],
38
+ "sideEffects": false,
39
+ "engines": {
40
+ "node": ">=18"
41
+ },
42
+ "publishConfig": {
43
+ "access": "public"
44
+ },
45
+ "devDependencies": {
46
+ "@types/jest": "^30.0.0",
47
+ "@types/node": "25.0.9",
48
+ "jest": "^30.2.0",
49
+ "ts-jest": "^29.4.6",
50
+ "typescript": "^5.8.2"
51
+ },
52
+ "scripts": {
53
+ "build": "tsc -p tsconfig.build.json",
54
+ "test": "jest --watch",
55
+ "coverage": "jest --coverage"
56
+ }
57
+ }