hackmyagent 0.16.4 → 0.16.7
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/dist/.integrity-manifest.json +1 -1
- package/dist/arp/crypto/hybrid-signing.d.ts +107 -0
- package/dist/arp/crypto/hybrid-signing.d.ts.map +1 -0
- package/dist/arp/crypto/hybrid-signing.js +321 -0
- package/dist/arp/crypto/hybrid-signing.js.map +1 -0
- package/dist/arp/crypto/index.d.ts +13 -0
- package/dist/arp/crypto/index.d.ts.map +1 -0
- package/dist/arp/crypto/index.js +33 -0
- package/dist/arp/crypto/index.js.map +1 -0
- package/dist/arp/crypto/manifest-loader.d.ts +117 -0
- package/dist/arp/crypto/manifest-loader.d.ts.map +1 -0
- package/dist/arp/crypto/manifest-loader.js +361 -0
- package/dist/arp/crypto/manifest-loader.js.map +1 -0
- package/dist/arp/crypto/types.d.ts +69 -0
- package/dist/arp/crypto/types.d.ts.map +1 -0
- package/dist/arp/crypto/types.js +11 -0
- package/dist/arp/crypto/types.js.map +1 -0
- package/dist/arp/index.d.ts +27 -0
- package/dist/arp/index.d.ts.map +1 -1
- package/dist/arp/index.js +94 -1
- package/dist/arp/index.js.map +1 -1
- package/dist/arp/intelligence/behavioral-risk-server.d.ts +82 -0
- package/dist/arp/intelligence/behavioral-risk-server.d.ts.map +1 -0
- package/dist/arp/intelligence/behavioral-risk-server.js +258 -0
- package/dist/arp/intelligence/behavioral-risk-server.js.map +1 -0
- package/dist/arp/intelligence/behavioral-risk.d.ts +217 -0
- package/dist/arp/intelligence/behavioral-risk.d.ts.map +1 -0
- package/dist/arp/intelligence/behavioral-risk.js +429 -0
- package/dist/arp/intelligence/behavioral-risk.js.map +1 -0
- package/dist/arp/intelligence/coordinator.d.ts +93 -2
- package/dist/arp/intelligence/coordinator.d.ts.map +1 -1
- package/dist/arp/intelligence/coordinator.js +281 -1
- package/dist/arp/intelligence/coordinator.js.map +1 -1
- package/dist/arp/intelligence/guard-anomaly.d.ts +349 -0
- package/dist/arp/intelligence/guard-anomaly.d.ts.map +1 -0
- package/dist/arp/intelligence/guard-anomaly.js +399 -0
- package/dist/arp/intelligence/guard-anomaly.js.map +1 -0
- package/dist/arp/intelligence/nanomind-l1.d.ts +37 -0
- package/dist/arp/intelligence/nanomind-l1.d.ts.map +1 -1
- package/dist/arp/intelligence/nanomind-l1.js +78 -0
- package/dist/arp/intelligence/nanomind-l1.js.map +1 -1
- package/dist/arp/intelligence/verify-classification.d.ts +124 -0
- package/dist/arp/intelligence/verify-classification.d.ts.map +1 -0
- package/dist/arp/intelligence/verify-classification.js +329 -0
- package/dist/arp/intelligence/verify-classification.js.map +1 -0
- package/dist/arp/proxy/server.d.ts +38 -8
- package/dist/arp/proxy/server.d.ts.map +1 -1
- package/dist/arp/proxy/server.js +89 -0
- package/dist/arp/proxy/server.js.map +1 -1
- package/dist/arp/types.d.ts +228 -1
- package/dist/arp/types.d.ts.map +1 -1
- package/dist/cli.js +152 -31
- package/dist/cli.js.map +1 -1
- package/dist/lifecycle/assembly-scanner.js +3 -3
- package/dist/lifecycle/assembly-scanner.js.map +1 -1
- package/dist/nanomind-core/compiler/semantic-compiler.d.ts.map +1 -1
- package/dist/nanomind-core/compiler/semantic-compiler.js +170 -10
- package/dist/nanomind-core/compiler/semantic-compiler.js.map +1 -1
- package/dist/nanomind-core/compiler/source-code-preprocessor.d.ts +64 -0
- package/dist/nanomind-core/compiler/source-code-preprocessor.d.ts.map +1 -0
- package/dist/nanomind-core/compiler/source-code-preprocessor.js +656 -0
- package/dist/nanomind-core/compiler/source-code-preprocessor.js.map +1 -0
- package/dist/nanomind-core/ingestion/artifact-parser.d.ts.map +1 -1
- package/dist/nanomind-core/ingestion/artifact-parser.js +15 -6
- package/dist/nanomind-core/ingestion/artifact-parser.js.map +1 -1
- package/dist/scanner/external-scanner.d.ts.map +1 -1
- package/dist/scanner/external-scanner.js +25 -1
- package/dist/scanner/external-scanner.js.map +1 -1
- package/package.json +3 -1
package/dist/cli.js
CHANGED
|
@@ -46,7 +46,9 @@ const program = new commander_1.Command();
|
|
|
46
46
|
program.showHelpAfterError('(run with --help for usage)');
|
|
47
47
|
// Total security check count across all scanner modules.
|
|
48
48
|
// Update when adding new checks (verify with: grep -r "checkId:" src/hardening/ | grep -o "checkId: '[^']*'" | sort -u | wc -l)
|
|
49
|
-
const CHECK_COUNT =
|
|
49
|
+
const CHECK_COUNT = 209;
|
|
50
|
+
// How long registry-cached scan data is considered fresh before `check` re-scans.
|
|
51
|
+
const STALE_SCAN_DAYS = 3;
|
|
50
52
|
// Write a string to stdout synchronously with retry for pipe backpressure.
|
|
51
53
|
// process.stdout.write() is async and gets truncated when process.exit()
|
|
52
54
|
// runs before the stream flushes. fs.writeFileSync(1, ...) can fail with
|
|
@@ -171,17 +173,21 @@ program
|
|
|
171
173
|
.description(`Check if a package, repo, or skill is safe
|
|
172
174
|
|
|
173
175
|
Accepts npm packages, GitHub repos, local paths, or skill identifiers:
|
|
174
|
-
• npm package:
|
|
175
|
-
•
|
|
176
|
+
• npm package: queries the registry; downloads + scans (${CHECK_COUNT} checks + NanoMind) if data is missing or stale (>${STALE_SCAN_DAYS}d)
|
|
177
|
+
• PyPI package: downloads + scans (${CHECK_COUNT} checks + NanoMind)
|
|
178
|
+
• GitHub repo: queries the registry; shallow clones + scans if data is missing or stale (>${STALE_SCAN_DAYS}d)
|
|
176
179
|
• Local path: runs NanoMind semantic analysis
|
|
177
180
|
• Skill identifier: verifies publisher, permissions, revocation
|
|
178
181
|
|
|
182
|
+
Use --rescan to skip the registry cache and force a fresh local scan.
|
|
183
|
+
|
|
179
184
|
Risk levels: low, medium, high, critical
|
|
180
185
|
Exit code 1 if high/critical risk detected.
|
|
181
186
|
|
|
182
187
|
Examples:
|
|
183
188
|
$ hackmyagent check express
|
|
184
189
|
$ hackmyagent check @modelcontextprotocol/server-filesystem
|
|
190
|
+
$ hackmyagent check @sentry/mcp-server --rescan
|
|
185
191
|
$ hackmyagent check modelcontextprotocol/servers
|
|
186
192
|
$ hackmyagent check https://github.com/punkpeye/awesome-mcp-servers
|
|
187
193
|
$ hackmyagent check ./my-agent/
|
|
@@ -195,6 +201,7 @@ Examples:
|
|
|
195
201
|
.option('-v, --verbose', 'Show detailed verification info')
|
|
196
202
|
.option('--json', 'Output as JSON (for scripting/CI)')
|
|
197
203
|
.option('--offline', 'Skip DNS verification (offline mode)')
|
|
204
|
+
.option('--rescan', 'Force a fresh local scan, bypassing cached registry data')
|
|
198
205
|
.action(async (skill, options) => {
|
|
199
206
|
try {
|
|
200
207
|
// Detect local file/directory paths - run NanoMind scan instead of registry lookup
|
|
@@ -245,6 +252,7 @@ Examples:
|
|
|
245
252
|
console.log(`\n ... and ${issues.length - 10} more`);
|
|
246
253
|
console.log();
|
|
247
254
|
}
|
|
255
|
+
printCheckNextSteps(resolved);
|
|
248
256
|
if (risk === 'critical' || risk === 'high')
|
|
249
257
|
process.exit(1);
|
|
250
258
|
return;
|
|
@@ -269,12 +277,17 @@ Examples:
|
|
|
269
277
|
await checkNpmPackage(skill, options);
|
|
270
278
|
return;
|
|
271
279
|
}
|
|
280
|
+
// --rescan only applies to targets that otherwise hit the registry cache.
|
|
281
|
+
// For skill identifiers we fall through to the registry lookup below.
|
|
282
|
+
if (options.rescan && !options.json) {
|
|
283
|
+
console.error(`Note: --rescan has no effect on skill identifiers; it applies to npm/PyPI/GitHub targets.`);
|
|
284
|
+
}
|
|
272
285
|
// Registry lookup path (non-local identifier) with 10s timeout
|
|
273
286
|
const checkPromise = (0, index_1.checkSkill)(skill, {
|
|
274
287
|
skipDnsVerification: options.offline,
|
|
275
288
|
});
|
|
276
289
|
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error(`Timed out verifying "${skill}" (10s). The publisher may not exist or DNS is unreachable.\n` +
|
|
277
|
-
`Try: ${
|
|
290
|
+
`Try: ${getCheckCommand()} ${skill} --offline`)), 10000));
|
|
278
291
|
const result = await Promise.race([checkPromise, timeoutPromise]);
|
|
279
292
|
if (options.json) {
|
|
280
293
|
writeJsonStdout(result);
|
|
@@ -2869,7 +2882,7 @@ Examples:
|
|
|
2869
2882
|
const customPorts = options.ports
|
|
2870
2883
|
? options.ports.split(',').map((p) => parseInt(p.trim(), 10))
|
|
2871
2884
|
: undefined;
|
|
2872
|
-
const portCount = customPorts?.length ??
|
|
2885
|
+
const portCount = customPorts?.length ?? 5;
|
|
2873
2886
|
if (!options.json) {
|
|
2874
2887
|
console.log(`\nScanning ${target} (${portCount} ports, ${timeoutMs}ms timeout)...\n`);
|
|
2875
2888
|
}
|
|
@@ -5300,9 +5313,46 @@ program
|
|
|
5300
5313
|
console.log(`${checkId}: ${explanation}`);
|
|
5301
5314
|
}
|
|
5302
5315
|
else {
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
|
|
5316
|
+
// Fallback: generate explanation from taxonomy metadata
|
|
5317
|
+
const { getAttackClass } = require('./hardening/taxonomy');
|
|
5318
|
+
const attackClass = getAttackClass(checkId);
|
|
5319
|
+
// Map check ID prefixes to human-readable category descriptions
|
|
5320
|
+
const prefixDescriptions = {
|
|
5321
|
+
'CRED': 'Credential exposure',
|
|
5322
|
+
'MCP': 'MCP server configuration',
|
|
5323
|
+
'SKILL': 'Skill package security',
|
|
5324
|
+
'GOV': 'Governance policy',
|
|
5325
|
+
'PERM': 'Permission scope',
|
|
5326
|
+
'SOUL': 'Behavioral governance (SOUL.md)',
|
|
5327
|
+
'PRIV': 'Privacy and data handling',
|
|
5328
|
+
'DATA': 'Data protection',
|
|
5329
|
+
'INJECT': 'Prompt injection defense',
|
|
5330
|
+
'ATTEST': 'Agent attestation',
|
|
5331
|
+
'SUPPLY': 'Supply chain security',
|
|
5332
|
+
'NET': 'Network security',
|
|
5333
|
+
'GIT': 'Git repository hygiene',
|
|
5334
|
+
'PROMPT': 'Prompt security',
|
|
5335
|
+
'NEMO': 'Static analysis pattern',
|
|
5336
|
+
'LIFECYCLE': 'Prompt assembly lifecycle',
|
|
5337
|
+
'AST': 'Deep code analysis',
|
|
5338
|
+
'ENCRYPT': 'Encryption and hashing',
|
|
5339
|
+
'LOG': 'Logging and audit',
|
|
5340
|
+
'AUTH': 'Authentication',
|
|
5341
|
+
'TOOL': 'Tool permission and safety',
|
|
5342
|
+
};
|
|
5343
|
+
const prefix = checkId.split('-')[0];
|
|
5344
|
+
const categoryDesc = prefixDescriptions[prefix];
|
|
5345
|
+
if (attackClass || categoryDesc) {
|
|
5346
|
+
console.log(`${checkId}: ${categoryDesc || 'Security check'}.`);
|
|
5347
|
+
if (attackClass) {
|
|
5348
|
+
console.log(` Attack class: ${attackClass}`);
|
|
5349
|
+
}
|
|
5350
|
+
console.log(`\n Run 'hackmyagent secure --verbose' to see this check in context with fix guidance.`);
|
|
5351
|
+
console.log(` Run 'hackmyagent check-metadata --json' for full check details.`);
|
|
5352
|
+
}
|
|
5353
|
+
else {
|
|
5354
|
+
console.log(`No explanation available for ${findingId}. This may not be a valid check ID.`);
|
|
5355
|
+
console.log(`\nRun 'hackmyagent check-metadata --json' to see all ${CHECK_COUNT} valid check IDs.`);
|
|
5306
5356
|
}
|
|
5307
5357
|
}
|
|
5308
5358
|
});
|
|
@@ -5765,7 +5815,6 @@ function parseGitHubTarget(target) {
|
|
|
5765
5815
|
};
|
|
5766
5816
|
}
|
|
5767
5817
|
const REGISTRY_URL = 'https://api.oa2a.org';
|
|
5768
|
-
const STALE_SCAN_DAYS = 3;
|
|
5769
5818
|
// ============================================================================
|
|
5770
5819
|
// Scan counter + contribute preference (~/.hackmyagent/config.json)
|
|
5771
5820
|
// ============================================================================
|
|
@@ -5938,6 +5987,57 @@ function displayRegistryResult(data) {
|
|
|
5938
5987
|
const days = Math.floor((Date.now() - new Date(data.lastScannedAt).getTime()) / (1000 * 60 * 60 * 24));
|
|
5939
5988
|
console.log(` Scanned: ${days === 0 ? 'today' : days + ' day(s) ago'}`);
|
|
5940
5989
|
}
|
|
5990
|
+
printCheckNextSteps(data.name);
|
|
5991
|
+
}
|
|
5992
|
+
/**
|
|
5993
|
+
* Resolve the "run a check" command string for use in user-facing hints.
|
|
5994
|
+
*
|
|
5995
|
+
* Precedence:
|
|
5996
|
+
* 1. HMA_CHECK_COMMAND env var (full command string, e.g. "opena2a check")
|
|
5997
|
+
* 2. `${CLI_PREFIX} check` — sensible default derived from how HMA was
|
|
5998
|
+
* invoked.
|
|
5999
|
+
*
|
|
6000
|
+
* Parent CLIs should set HMA_CHECK_COMMAND when their verb layout differs
|
|
6001
|
+
* from hackmyagent's, rather than trying to encode the full verb into
|
|
6002
|
+
* HMA_CLI_PREFIX (which is treated as a binary-level prefix everywhere else).
|
|
6003
|
+
*/
|
|
6004
|
+
function getCheckCommand() {
|
|
6005
|
+
const override = process.env.HMA_CHECK_COMMAND?.trim();
|
|
6006
|
+
if (override)
|
|
6007
|
+
return override;
|
|
6008
|
+
return `${CLI_PREFIX} check`;
|
|
6009
|
+
}
|
|
6010
|
+
/**
|
|
6011
|
+
* Resolve the "full project scan" hint command string.
|
|
6012
|
+
*
|
|
6013
|
+
* Precedence:
|
|
6014
|
+
* 1. HMA_FULL_SCAN_HINT env var (full command string, e.g. "opena2a review")
|
|
6015
|
+
* 2. `${CLI_PREFIX} secure <dir>` — default.
|
|
6016
|
+
*/
|
|
6017
|
+
function getFullScanHint() {
|
|
6018
|
+
const override = process.env.HMA_FULL_SCAN_HINT?.trim();
|
|
6019
|
+
if (override)
|
|
6020
|
+
return override;
|
|
6021
|
+
return `${CLI_PREFIX} secure <dir>`;
|
|
6022
|
+
}
|
|
6023
|
+
/**
|
|
6024
|
+
* Print the standard 3-line next-steps footer shown after every `check`
|
|
6025
|
+
* invocation. Lines:
|
|
6026
|
+
* 1. How to force a fresh local scan of *this* target.
|
|
6027
|
+
* 2. How to run the full project scan (respects HMA_FULL_SCAN_HINT so that
|
|
6028
|
+
* sibling CLIs like opena2a can redirect users to their own flagship
|
|
6029
|
+
* command instead of `hackmyagent secure <dir>`).
|
|
6030
|
+
* 3. Discoverability: the other target syntaxes `check` accepts.
|
|
6031
|
+
*
|
|
6032
|
+
* Suppressed in --ci so machine-readable output stays clean.
|
|
6033
|
+
*/
|
|
6034
|
+
function printCheckNextSteps(target) {
|
|
6035
|
+
if (globalCiMode)
|
|
6036
|
+
return;
|
|
6037
|
+
console.log();
|
|
6038
|
+
console.log(` ${colors.dim}Run a fresh local scan: ${getCheckCommand()} ${target} --rescan${RESET()}`);
|
|
6039
|
+
console.log(` ${colors.dim}Full project scan: ${getFullScanHint()}${RESET()}`);
|
|
6040
|
+
console.log(` ${colors.dim}Also accepts: pip:<pkg> · <owner>/<repo> · ./<dir> · @publisher/skill${RESET()}`);
|
|
5941
6041
|
console.log();
|
|
5942
6042
|
}
|
|
5943
6043
|
/**
|
|
@@ -5947,6 +6047,17 @@ function displayRegistryResult(data) {
|
|
|
5947
6047
|
async function suggestSimilarPackages(name) {
|
|
5948
6048
|
const controller = new AbortController();
|
|
5949
6049
|
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
6050
|
+
// Simple Levenshtein distance for filtering relevant suggestions
|
|
6051
|
+
function levenshtein(a, b) {
|
|
6052
|
+
const m = a.length, n = b.length;
|
|
6053
|
+
const dp = Array.from({ length: m + 1 }, (_, i) => Array.from({ length: n + 1 }, (_, j) => i === 0 ? j : j === 0 ? i : 0));
|
|
6054
|
+
for (let i = 1; i <= m; i++)
|
|
6055
|
+
for (let j = 1; j <= n; j++)
|
|
6056
|
+
dp[i][j] = a[i - 1] === b[j - 1]
|
|
6057
|
+
? dp[i - 1][j - 1]
|
|
6058
|
+
: 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
|
|
6059
|
+
return dp[m][n];
|
|
6060
|
+
}
|
|
5950
6061
|
try {
|
|
5951
6062
|
// Build search queries: the name itself, plus the unscoped name for scoped packages
|
|
5952
6063
|
const queries = [name];
|
|
@@ -5955,11 +6066,9 @@ async function suggestSimilarPackages(name) {
|
|
|
5955
6066
|
queries.push(scopeMatch[1]);
|
|
5956
6067
|
}
|
|
5957
6068
|
const seen = new Set();
|
|
5958
|
-
const
|
|
6069
|
+
const candidates = [];
|
|
5959
6070
|
for (const query of queries) {
|
|
5960
|
-
|
|
5961
|
-
break;
|
|
5962
|
-
const url = `https://registry.npmjs.org/-/v1/search?text=${encodeURIComponent(query)}&size=5`;
|
|
6071
|
+
const url = `https://registry.npmjs.org/-/v1/search?text=${encodeURIComponent(query)}&size=10`;
|
|
5963
6072
|
const res = await fetch(url, { signal: controller.signal });
|
|
5964
6073
|
if (!res.ok)
|
|
5965
6074
|
continue;
|
|
@@ -5971,12 +6080,20 @@ async function suggestSimilarPackages(name) {
|
|
|
5971
6080
|
if (pkg === name || seen.has(pkg))
|
|
5972
6081
|
continue;
|
|
5973
6082
|
seen.add(pkg);
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
6083
|
+
// Compare unscoped names for better matching
|
|
6084
|
+
const unscopedInput = name.replace(/^@[^/]+\//, '');
|
|
6085
|
+
const unscopedPkg = pkg.replace(/^@[^/]+\//, '');
|
|
6086
|
+
const dist = levenshtein(unscopedInput.toLowerCase(), unscopedPkg.toLowerCase());
|
|
6087
|
+
// Only suggest if reasonably similar (distance < half the input length + 3)
|
|
6088
|
+
const maxDist = Math.floor(unscopedInput.length / 2) + 3;
|
|
6089
|
+
if (dist <= maxDist) {
|
|
6090
|
+
candidates.push({ name: pkg, distance: dist });
|
|
6091
|
+
}
|
|
5977
6092
|
}
|
|
5978
6093
|
}
|
|
5979
|
-
return
|
|
6094
|
+
// Sort by edit distance and return top 3
|
|
6095
|
+
candidates.sort((a, b) => a.distance - b.distance);
|
|
6096
|
+
return candidates.slice(0, 3).map(c => c.name);
|
|
5980
6097
|
}
|
|
5981
6098
|
finally {
|
|
5982
6099
|
clearTimeout(timeout);
|
|
@@ -5989,8 +6106,8 @@ async function suggestSimilarPackages(name) {
|
|
|
5989
6106
|
async function checkGitHubRepo(target, options) {
|
|
5990
6107
|
const { org, repo, cloneUrl } = parseGitHubTarget(target);
|
|
5991
6108
|
const displayName = `${org}/${repo}`;
|
|
5992
|
-
// Step 1: Check registry for existing trust data
|
|
5993
|
-
if (!options.offline) {
|
|
6109
|
+
// Step 1: Check registry for existing trust data (unless --rescan forces a fresh scan)
|
|
6110
|
+
if (!options.offline && !options.rescan) {
|
|
5994
6111
|
const registryData = await queryRegistry(displayName);
|
|
5995
6112
|
if (registryData?.found && !isScanStale(registryData.lastScannedAt)) {
|
|
5996
6113
|
if (options.json) {
|
|
@@ -6007,6 +6124,9 @@ async function checkGitHubRepo(target, options) {
|
|
|
6007
6124
|
}
|
|
6008
6125
|
}
|
|
6009
6126
|
}
|
|
6127
|
+
else if (options.rescan && !options.json && !globalCiMode) {
|
|
6128
|
+
console.error(`Forcing fresh local scan (--rescan)...`);
|
|
6129
|
+
}
|
|
6010
6130
|
// Step 2: Clone and scan
|
|
6011
6131
|
const { mkdtemp, rm } = await Promise.resolve().then(() => __importStar(require('node:fs/promises')));
|
|
6012
6132
|
const { tmpdir } = await Promise.resolve().then(() => __importStar(require('node:os')));
|
|
@@ -6095,8 +6215,7 @@ async function checkGitHubRepo(target, options) {
|
|
|
6095
6215
|
queuePendingScan(displayName, result);
|
|
6096
6216
|
}
|
|
6097
6217
|
}
|
|
6098
|
-
|
|
6099
|
-
console.log();
|
|
6218
|
+
printCheckNextSteps(displayName);
|
|
6100
6219
|
if (critical.length > 0 || high.length > 0)
|
|
6101
6220
|
process.exit(1);
|
|
6102
6221
|
}
|
|
@@ -6110,7 +6229,7 @@ async function checkGitHubRepo(target, options) {
|
|
|
6110
6229
|
console.error(`Error: Cloning "${displayName}" timed out (120s). The repo may be too large.`);
|
|
6111
6230
|
console.error(`\nTry cloning manually and scanning the local path:`);
|
|
6112
6231
|
console.error(` git clone --depth 1 ${cloneUrl}`);
|
|
6113
|
-
console.error(` ${
|
|
6232
|
+
console.error(` ${getCheckCommand()} ./${repo}/`);
|
|
6114
6233
|
}
|
|
6115
6234
|
else {
|
|
6116
6235
|
console.error(`Error: ${message}`);
|
|
@@ -6239,8 +6358,9 @@ async function checkPyPiPackage(target, options) {
|
|
|
6239
6358
|
else {
|
|
6240
6359
|
console.log(`\n ${colors.green}No security issues found.${RESET()}`);
|
|
6241
6360
|
}
|
|
6242
|
-
|
|
6243
|
-
|
|
6361
|
+
// Pass the original target (with pip: / pypi: prefix preserved) so the
|
|
6362
|
+
// rescan hint stays runnable — `hackmyagent check requests` would try npm.
|
|
6363
|
+
printCheckNextSteps(target);
|
|
6244
6364
|
if (critical.length > 0 || high.length > 0)
|
|
6245
6365
|
process.exit(1);
|
|
6246
6366
|
}
|
|
@@ -6399,8 +6519,7 @@ async function checkRawUrl(url, options) {
|
|
|
6399
6519
|
queuePendingScan(displayName, result);
|
|
6400
6520
|
}
|
|
6401
6521
|
}
|
|
6402
|
-
|
|
6403
|
-
console.log();
|
|
6522
|
+
printCheckNextSteps(displayName);
|
|
6404
6523
|
if (critical.length > 0 || high.length > 0)
|
|
6405
6524
|
process.exit(1);
|
|
6406
6525
|
}
|
|
@@ -6413,7 +6532,7 @@ async function checkRawUrl(url, options) {
|
|
|
6413
6532
|
else if (message.includes('timeout') || message.includes('Timeout')) {
|
|
6414
6533
|
console.error(`Error: Fetching "${url}" timed out. The target may be too large.`);
|
|
6415
6534
|
console.error(`\nTry downloading manually and scanning the local path:`);
|
|
6416
|
-
console.error(` ${
|
|
6535
|
+
console.error(` ${getCheckCommand()} ./downloaded-dir/`);
|
|
6417
6536
|
}
|
|
6418
6537
|
else {
|
|
6419
6538
|
console.error(`Error scanning URL: ${message}`);
|
|
@@ -6425,8 +6544,8 @@ async function checkRawUrl(url, options) {
|
|
|
6425
6544
|
}
|
|
6426
6545
|
}
|
|
6427
6546
|
async function checkNpmPackage(name, options) {
|
|
6428
|
-
// Step 1: Check registry for existing trust data
|
|
6429
|
-
if (!options.offline) {
|
|
6547
|
+
// Step 1: Check registry for existing trust data (unless --rescan forces a fresh scan)
|
|
6548
|
+
if (!options.offline && !options.rescan) {
|
|
6430
6549
|
const registryData = await queryRegistry(name);
|
|
6431
6550
|
if (registryData?.found && !isScanStale(registryData.lastScannedAt)) {
|
|
6432
6551
|
// Fresh data in registry — show it
|
|
@@ -6445,6 +6564,9 @@ async function checkNpmPackage(name, options) {
|
|
|
6445
6564
|
}
|
|
6446
6565
|
}
|
|
6447
6566
|
}
|
|
6567
|
+
else if (options.rescan && !options.json && !globalCiMode) {
|
|
6568
|
+
console.error(`Forcing fresh local scan (--rescan)...`);
|
|
6569
|
+
}
|
|
6448
6570
|
// Step 2: Download and scan
|
|
6449
6571
|
const { mkdtemp, rm } = await Promise.resolve().then(() => __importStar(require('node:fs/promises')));
|
|
6450
6572
|
const { tmpdir } = await Promise.resolve().then(() => __importStar(require('node:os')));
|
|
@@ -6537,8 +6659,7 @@ async function checkNpmPackage(name, options) {
|
|
|
6537
6659
|
queuePendingScan(name, result);
|
|
6538
6660
|
}
|
|
6539
6661
|
}
|
|
6540
|
-
|
|
6541
|
-
console.log();
|
|
6662
|
+
printCheckNextSteps(name);
|
|
6542
6663
|
if (critical.length > 0 || high.length > 0)
|
|
6543
6664
|
process.exit(1);
|
|
6544
6665
|
}
|