gitnexus 1.6.10-rc.30 → 1.6.10-rc.32
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.
|
@@ -35,10 +35,14 @@ export const TS_JS_TAINT_MODEL = {
|
|
|
35
35
|
},
|
|
36
36
|
],
|
|
37
37
|
sinks: [
|
|
38
|
-
// Command execution —
|
|
38
|
+
// Command execution — shell strings are arg 0; argv-form APIs also treat
|
|
39
|
+
// the argv array at arg 1 as command/option injection surface.
|
|
39
40
|
{ name: 'exec', kind: 'command-injection', args: [0], module: 'child_process' },
|
|
40
41
|
{ name: 'execSync', kind: 'command-injection', args: [0], module: 'child_process' },
|
|
41
|
-
{ name: 'spawn', kind: 'command-injection', args: [0], module: 'child_process' },
|
|
42
|
+
{ name: 'spawn', kind: 'command-injection', args: [0, 1], module: 'child_process' },
|
|
43
|
+
{ name: 'spawnSync', kind: 'command-injection', args: [0, 1], module: 'child_process' },
|
|
44
|
+
{ name: 'execFile', kind: 'command-injection', args: [0, 1], module: 'child_process' },
|
|
45
|
+
{ name: 'execFileSync', kind: 'command-injection', args: [0, 1], module: 'child_process' },
|
|
42
46
|
// Code evaluation. `eval` takes code at 0; `new Function(...)` treats
|
|
43
47
|
// EVERY argument as source text (params + body), so `args` is omitted
|
|
44
48
|
// (= all positions) rather than pinned to 0.
|
|
@@ -53,9 +57,37 @@ export const TS_JS_TAINT_MODEL = {
|
|
|
53
57
|
// (mysql2/pg/knex handles go by many names; receiver-conventional).
|
|
54
58
|
{ name: 'query', kind: 'sql-injection', args: [0], anyReceiver: true },
|
|
55
59
|
{ name: 'execute', kind: 'sql-injection', args: [0], anyReceiver: true },
|
|
60
|
+
// Modern DB libraries expose shorter method names with high collision
|
|
61
|
+
// rates (`map.get`, `task.run`, ...), so keep these receiver-conventional.
|
|
62
|
+
{
|
|
63
|
+
name: 'run',
|
|
64
|
+
kind: 'sql-injection',
|
|
65
|
+
args: [0],
|
|
66
|
+
receivers: ['db', 'database', 'conn', 'client', 'pool', 'stmt', 'statement', 'prepared'],
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: 'all',
|
|
70
|
+
kind: 'sql-injection',
|
|
71
|
+
args: [0],
|
|
72
|
+
receivers: ['db', 'database', 'conn', 'client', 'pool', 'stmt', 'statement', 'prepared'],
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: 'get',
|
|
76
|
+
kind: 'sql-injection',
|
|
77
|
+
args: [0],
|
|
78
|
+
receivers: ['db', 'database', 'conn', 'client', 'pool', 'stmt', 'statement', 'prepared'],
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: 'values',
|
|
82
|
+
kind: 'sql-injection',
|
|
83
|
+
args: [0],
|
|
84
|
+
receivers: ['db', 'database', 'conn', 'client', 'pool'],
|
|
85
|
+
},
|
|
86
|
+
{ name: 'raw', kind: 'sql-injection', args: [0], receivers: ['db', 'knex', 'sequelize'] },
|
|
56
87
|
// Reflected XSS — Express response writes, conventional receiver `res`.
|
|
57
88
|
{ name: 'send', kind: 'xss', args: [0], receivers: ['res'] },
|
|
58
89
|
{ name: 'write', kind: 'xss', args: [0], receivers: ['res'] },
|
|
90
|
+
{ name: 'render', kind: 'xss', args: [0, 1], receivers: ['res'] },
|
|
59
91
|
],
|
|
60
92
|
sanitizers: [
|
|
61
93
|
// URL-encoding: neutralizes markup injection AND path separators
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gitnexus",
|
|
3
|
-
"version": "1.6.10-rc.
|
|
3
|
+
"version": "1.6.10-rc.32",
|
|
4
4
|
"description": "Graph-powered code intelligence for AI agents. Index any codebase, query via MCP or CLI.",
|
|
5
5
|
"author": "Abhigyan Patwari",
|
|
6
6
|
"license": "PolyForm-Noncommercial-1.0.0",
|
|
@@ -52,7 +52,8 @@
|
|
|
52
52
|
"postinstall": "node scripts/build-tree-sitter-grammars.cjs",
|
|
53
53
|
"assert-publish-coverage": "node scripts/assert-publish-grammar-coverage.cjs",
|
|
54
54
|
"prepare": "node scripts/build.js",
|
|
55
|
-
"prepack": "node scripts/assert-publish-grammar-coverage.cjs && node scripts/build.js"
|
|
55
|
+
"prepack": "node scripts/assert-publish-grammar-coverage.cjs && node scripts/build.js",
|
|
56
|
+
"version": "node scripts/sync-plugin-manifests.mjs"
|
|
56
57
|
},
|
|
57
58
|
"dependencies": {
|
|
58
59
|
"@ladybugdb/core": "^0.18.0",
|
|
@@ -48,10 +48,11 @@ try {
|
|
|
48
48
|
|
|
49
49
|
// Per-shard watchdog, default 15 min. Sharding splits the file list by COUNT, not
|
|
50
50
|
// runtime, so the heaviest spawn suites can cluster on one shard — what this
|
|
51
|
-
// bounds is the *busiest* shard, not an even 1/n of wall-clock.
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
//
|
|
51
|
+
// bounds is the *busiest* shard, not an even 1/n of wall-clock. The busiest
|
|
52
|
+
// Windows shard has grown to the default (14m57s on the v1.6.10-rc.19 green
|
|
53
|
+
// run, one observed timeout since — #2449), so CI raises the budget to 20
|
|
54
|
+
// minutes via GITNEXUS_CROSS_PLATFORM_TIMEOUT_MINUTES; the default stays 15
|
|
55
|
+
// for local runs.
|
|
55
56
|
const DEFAULT_TIMEOUT_MIN = 15;
|
|
56
57
|
const timeoutMinutes = Number.parseInt(
|
|
57
58
|
process.env.GITNEXUS_CROSS_PLATFORM_TIMEOUT_MINUTES ?? String(DEFAULT_TIMEOUT_MIN),
|
|
@@ -67,6 +68,7 @@ console.log(
|
|
|
67
68
|
`${shardArg ? ` (${shardArg.replace('--shard=', 'shard ')})` : ''}...\n`,
|
|
68
69
|
);
|
|
69
70
|
|
|
71
|
+
const startedAt = Date.now();
|
|
70
72
|
try {
|
|
71
73
|
execFileSync('npx', ['vitest', 'run', ...ALL_CROSS_PLATFORM, ...(shardArg ? [shardArg] : [])], {
|
|
72
74
|
cwd: ROOT,
|
|
@@ -76,9 +78,22 @@ try {
|
|
|
76
78
|
});
|
|
77
79
|
} catch (err) {
|
|
78
80
|
// execFileSync sets `killed`/`signal` when the watchdog above kills vitest.
|
|
79
|
-
const e = err as {
|
|
81
|
+
const e = err as {
|
|
82
|
+
killed?: boolean;
|
|
83
|
+
signal?: NodeJS.Signals | null;
|
|
84
|
+
status?: number | null;
|
|
85
|
+
code?: string;
|
|
86
|
+
};
|
|
80
87
|
if (e.killed || e.signal) {
|
|
81
88
|
console.error(`vitest timed out after ${Math.round(timeoutMs / 60_000)} minutes`);
|
|
82
89
|
}
|
|
90
|
+
// #2449: Windows shards have died with a bare `status: null`, empty stderr
|
|
91
|
+
// and nothing to triage from. Always leave the child's exit facts behind.
|
|
92
|
+
const elapsedSec = Math.round((Date.now() - startedAt) / 1000);
|
|
93
|
+
console.error(
|
|
94
|
+
`vitest exited abnormally: status=${e.status ?? 'null'} signal=${e.signal ?? 'none'} ` +
|
|
95
|
+
`killed=${e.killed === true} spawnCode=${e.code ?? 'none'} elapsed=${elapsedSec}s ` +
|
|
96
|
+
`budget=${Math.round(timeoutMs / 60_000)}min`,
|
|
97
|
+
);
|
|
83
98
|
process.exit(1);
|
|
84
99
|
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Fail-closed version sync for the plugin manifest surfaces (#2445).
|
|
4
|
+
*
|
|
5
|
+
* `publish.yml` bumps only `gitnexus/package.json` when it cuts an RC, so
|
|
6
|
+
* every RC tag through v1.6.10-rc.28 shipped manifests frozen at the last
|
|
7
|
+
* stable version and failed its own unit suite (the cli-commands version
|
|
8
|
+
* contract). This script pins all four manifest surfaces to the package
|
|
9
|
+
* version:
|
|
10
|
+
*
|
|
11
|
+
* - gitnexus-claude-plugin/.claude-plugin/plugin.json (top-level version)
|
|
12
|
+
* - .claude-plugin/marketplace.json (plugins[gitnexus])
|
|
13
|
+
* - gitnexus-claude-plugin/.codex-plugin/plugin.json (top-level version)
|
|
14
|
+
* - .agents/plugins/marketplace.json (plugins[gitnexus])
|
|
15
|
+
*
|
|
16
|
+
* Modes:
|
|
17
|
+
* node scripts/sync-plugin-manifests.mjs rewrite stale surfaces
|
|
18
|
+
* node scripts/sync-plugin-manifests.mjs --check verify only, exit 1 on drift
|
|
19
|
+
*
|
|
20
|
+
* Fail-closed: a missing file, unparseable JSON, an absent version field, or
|
|
21
|
+
* anything other than exactly one `gitnexus` marketplace entry aborts with a
|
|
22
|
+
* non-zero exit rather than letting a release ship a partial sync.
|
|
23
|
+
*/
|
|
24
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
25
|
+
import path from 'node:path';
|
|
26
|
+
import { fileURLToPath } from 'node:url';
|
|
27
|
+
|
|
28
|
+
const MANIFEST_SURFACES = [
|
|
29
|
+
{ file: 'gitnexus-claude-plugin/.claude-plugin/plugin.json', kind: 'plugin' },
|
|
30
|
+
{ file: '.claude-plugin/marketplace.json', kind: 'marketplace' },
|
|
31
|
+
{ file: 'gitnexus-claude-plugin/.codex-plugin/plugin.json', kind: 'plugin' },
|
|
32
|
+
{ file: '.agents/plugins/marketplace.json', kind: 'marketplace' },
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
const PLUGIN_NAME = 'gitnexus';
|
|
36
|
+
|
|
37
|
+
function readJson(filePath) {
|
|
38
|
+
let raw;
|
|
39
|
+
try {
|
|
40
|
+
raw = readFileSync(filePath, 'utf8');
|
|
41
|
+
} catch (err) {
|
|
42
|
+
throw new Error(`Cannot read manifest surface ${filePath}: ${err.message}`);
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
return { raw, parsed: JSON.parse(raw) };
|
|
46
|
+
} catch (err) {
|
|
47
|
+
throw new Error(`Manifest surface ${filePath} is not valid JSON: ${err.message}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function versionTarget(manifest, kind, filePath) {
|
|
52
|
+
if (kind === 'plugin') {
|
|
53
|
+
if (typeof manifest.version !== 'string' || manifest.version.length === 0) {
|
|
54
|
+
throw new Error(`Manifest surface ${filePath} has no version field to sync`);
|
|
55
|
+
}
|
|
56
|
+
return manifest;
|
|
57
|
+
}
|
|
58
|
+
const entries = (Array.isArray(manifest.plugins) ? manifest.plugins : []).filter(
|
|
59
|
+
(plugin) => plugin?.name === PLUGIN_NAME,
|
|
60
|
+
);
|
|
61
|
+
if (entries.length !== 1) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
`Manifest surface ${filePath} must contain exactly one "${PLUGIN_NAME}" plugin entry, found ${entries.length}`,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
if (typeof entries[0].version !== 'string' || entries[0].version.length === 0) {
|
|
67
|
+
throw new Error(`Manifest surface ${filePath} has no version field to sync`);
|
|
68
|
+
}
|
|
69
|
+
return entries[0];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Sync (or with `check: true`, only inspect) every manifest surface under
|
|
74
|
+
* `rootDir`. Returns `{ version, synced, stale }` where `stale` lists the
|
|
75
|
+
* surfaces that did not match the package version when the run started.
|
|
76
|
+
*/
|
|
77
|
+
export function syncPluginManifests(rootDir, { check = false } = {}) {
|
|
78
|
+
const pkgPath = path.join(rootDir, 'gitnexus', 'package.json');
|
|
79
|
+
const version = readJson(pkgPath).parsed.version;
|
|
80
|
+
if (typeof version !== 'string' || version.length === 0) {
|
|
81
|
+
throw new Error(`No version found in ${pkgPath}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const synced = [];
|
|
85
|
+
const stale = [];
|
|
86
|
+
for (const { file, kind } of MANIFEST_SURFACES) {
|
|
87
|
+
const manifestPath = path.join(rootDir, file);
|
|
88
|
+
const { raw, parsed } = readJson(manifestPath);
|
|
89
|
+
const target = versionTarget(parsed, kind, manifestPath);
|
|
90
|
+
if (target.version === version) continue;
|
|
91
|
+
|
|
92
|
+
stale.push({ file, from: target.version });
|
|
93
|
+
if (check) continue;
|
|
94
|
+
|
|
95
|
+
// Textual surgery instead of re-serializing: JSON.stringify would refold
|
|
96
|
+
// arrays and fight prettier, turning a one-line version bump into
|
|
97
|
+
// formatting churn inside the release commit. The needle is built from
|
|
98
|
+
// the parsed current version, and anything other than exactly one
|
|
99
|
+
// occurrence aborts rather than guessing.
|
|
100
|
+
const needle = `"version": "${target.version}"`;
|
|
101
|
+
const occurrences = raw.split(needle).length - 1;
|
|
102
|
+
if (occurrences !== 1) {
|
|
103
|
+
throw new Error(
|
|
104
|
+
`Manifest surface ${manifestPath} has ${occurrences} occurrences of ${needle}; ` +
|
|
105
|
+
'expected exactly one, refusing to sync',
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
writeFileSync(manifestPath, raw.replace(needle, `"version": "${version}"`));
|
|
109
|
+
synced.push(file);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return { version, synced, stale };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const invokedDirectly =
|
|
116
|
+
process.argv[1] !== undefined && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
117
|
+
|
|
118
|
+
if (invokedDirectly) {
|
|
119
|
+
const check = process.argv.includes('--check');
|
|
120
|
+
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
121
|
+
const result = syncPluginManifests(rootDir, { check });
|
|
122
|
+
|
|
123
|
+
if (check && result.stale.length > 0) {
|
|
124
|
+
for (const { file, from } of result.stale) {
|
|
125
|
+
console.error(
|
|
126
|
+
`::error::${file} is at ${from} but gitnexus/package.json is at ${result.version}. ` +
|
|
127
|
+
'Run `node gitnexus/scripts/sync-plugin-manifests.mjs` and commit the result.',
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
for (const file of result.synced) {
|
|
134
|
+
console.log(`synced ${file} -> ${result.version}`);
|
|
135
|
+
}
|
|
136
|
+
console.log(
|
|
137
|
+
result.stale.length === 0 && result.synced.length === 0
|
|
138
|
+
? `all plugin manifests already at ${result.version}`
|
|
139
|
+
: `plugin manifests now at ${result.version}`,
|
|
140
|
+
);
|
|
141
|
+
}
|