akm-cli 0.7.5 → 0.8.0-rc.11
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/{.github/CHANGELOG.md → CHANGELOG.md} +192 -2
- package/README.md +22 -6
- package/SECURITY.md +93 -0
- package/dist/cli/config-migrate.js +144 -0
- package/dist/cli/config-validate.js +39 -0
- package/dist/cli/confirm.js +73 -0
- package/dist/cli/parse-args.js +133 -0
- package/dist/cli/shared.js +129 -0
- package/dist/cli.js +2569 -1449
- package/dist/commands/add-cli.js +279 -0
- package/dist/commands/agent-dispatch.js +110 -0
- package/dist/commands/agent-support.js +68 -0
- package/dist/commands/completions.js +3 -0
- package/dist/commands/config-cli.js +130 -534
- package/dist/commands/consolidate.js +2122 -0
- package/dist/commands/curate.js +44 -3
- package/dist/commands/db-cli.js +23 -0
- package/dist/commands/distill-promotion-policy.js +660 -0
- package/dist/commands/distill.js +1075 -77
- package/dist/commands/env.js +213 -0
- package/dist/commands/eval-cases.js +43 -0
- package/dist/commands/events.js +5 -23
- package/dist/commands/extract-cli.js +127 -0
- package/dist/commands/extract-prompt.js +204 -0
- package/dist/commands/extract.js +477 -0
- package/dist/commands/feedback-cli.js +331 -0
- package/dist/commands/graph.js +477 -0
- package/dist/commands/health.js +1302 -0
- package/dist/commands/help/help-accept.md +12 -0
- package/dist/commands/help/help-improve.md +69 -0
- package/dist/commands/help/help-proposals.md +18 -0
- package/dist/commands/help/help-propose.md +17 -0
- package/dist/commands/help/help-reject.md +11 -0
- package/dist/commands/history.js +54 -46
- package/dist/commands/improve-auto-accept.js +97 -0
- package/dist/commands/improve-cli.js +217 -0
- package/dist/commands/improve-profiles.js +166 -0
- package/dist/commands/improve-result-file.js +167 -0
- package/dist/commands/improve.js +2373 -0
- package/dist/commands/info.js +5 -2
- package/dist/commands/init.js +50 -2
- package/dist/commands/installed-stashes.js +102 -139
- package/dist/commands/knowledge.js +136 -0
- package/dist/commands/lint/agent-linter.js +49 -0
- package/dist/commands/lint/base-linter.js +479 -0
- package/dist/commands/lint/command-linter.js +49 -0
- package/dist/commands/lint/default-linter.js +16 -0
- package/dist/commands/lint/env-key-rules.js +154 -0
- package/dist/commands/lint/index.js +196 -0
- package/dist/commands/lint/knowledge-linter.js +16 -0
- package/dist/commands/lint/markdown-insertion.js +343 -0
- package/dist/commands/lint/memory-linter.js +61 -0
- package/dist/commands/lint/registry.js +36 -0
- package/dist/commands/lint/skill-linter.js +45 -0
- package/dist/commands/lint/task-linter.js +50 -0
- package/dist/commands/lint/types.js +4 -0
- package/dist/commands/lint/workflow-linter.js +56 -0
- package/dist/commands/lint.js +4 -0
- package/dist/commands/migration-help.js +5 -2
- package/dist/commands/proposal.js +67 -12
- package/dist/commands/propose.js +86 -31
- package/dist/commands/reflect.js +1091 -73
- package/dist/commands/registry-cli.js +150 -0
- package/dist/commands/registry-search.js +5 -2
- package/dist/commands/remember-cli.js +257 -0
- package/dist/commands/remember.js +69 -6
- package/dist/commands/schema-repair.js +203 -0
- package/dist/commands/search.js +115 -14
- package/dist/commands/secret.js +173 -0
- package/dist/commands/self-update.js +3 -0
- package/dist/commands/show.js +148 -25
- package/dist/commands/source-add.js +17 -45
- package/dist/commands/source-clone.js +3 -0
- package/dist/commands/source-manage.js +14 -19
- package/dist/commands/tasks.js +437 -0
- package/dist/commands/url-checker.js +42 -0
- package/dist/core/action-contributors.js +28 -0
- package/dist/core/asset-ref.js +17 -2
- package/dist/core/asset-registry.js +12 -17
- package/dist/core/asset-serialize.js +88 -0
- package/dist/core/asset-spec.js +67 -1
- package/dist/core/common.js +182 -0
- package/dist/core/concurrent.js +25 -0
- package/dist/core/config-io.js +347 -0
- package/dist/core/config-migration.js +622 -0
- package/dist/core/config-schema.js +534 -0
- package/dist/core/config-sources.js +108 -0
- package/dist/core/config-types.js +4 -0
- package/dist/core/config-walker.js +337 -0
- package/dist/core/config.js +364 -981
- package/dist/core/errors.js +42 -20
- package/dist/core/events.js +91 -138
- package/dist/core/file-lock.js +104 -0
- package/dist/core/frontmatter.js +75 -8
- package/dist/core/lesson-lint.js +3 -0
- package/dist/core/markdown.js +20 -0
- package/dist/core/memory-belief.js +62 -0
- package/dist/core/memory-contradiction-detect.js +274 -0
- package/dist/core/memory-improve.js +806 -0
- package/dist/core/parse.js +158 -0
- package/dist/core/paths.js +280 -14
- package/dist/core/proposal-quality-validators.js +380 -0
- package/dist/core/proposal-validators.js +69 -0
- package/dist/core/proposals.js +512 -42
- package/dist/core/state-db.js +1068 -0
- package/dist/core/text-truncation.js +107 -0
- package/dist/core/time.js +54 -0
- package/dist/core/tty.js +59 -0
- package/dist/core/warn.js +64 -1
- package/dist/core/write-source.js +3 -0
- package/dist/indexer/db-backup.js +391 -0
- package/dist/indexer/db-search.js +178 -256
- package/dist/indexer/db.js +975 -103
- package/dist/indexer/ensure-index.js +64 -0
- package/dist/indexer/file-context.js +3 -0
- package/dist/indexer/graph-boost.js +376 -101
- package/dist/indexer/graph-db.js +391 -0
- package/dist/indexer/graph-dedup.js +95 -0
- package/dist/indexer/graph-extraction.js +550 -124
- package/dist/indexer/index-context.js +4 -0
- package/dist/indexer/indexer.js +523 -301
- package/dist/indexer/llm-cache.js +52 -0
- package/dist/indexer/manifest.js +3 -0
- package/dist/indexer/matchers.js +167 -160
- package/dist/indexer/memory-inference.js +152 -74
- package/dist/indexer/metadata-contributors.js +29 -0
- package/dist/indexer/metadata.js +275 -196
- package/dist/indexer/path-resolver.js +92 -0
- package/dist/indexer/project-context.js +192 -0
- package/dist/indexer/ranking-contributors.js +331 -0
- package/dist/indexer/ranking.js +81 -0
- package/dist/indexer/search-fields.js +5 -9
- package/dist/indexer/search-hit-enrichers.js +111 -0
- package/dist/indexer/search-source.js +44 -10
- package/dist/indexer/semantic-status.js +6 -17
- package/dist/indexer/staleness-detect.js +447 -0
- package/dist/indexer/usage-events.js +12 -9
- package/dist/indexer/walker.js +28 -0
- package/dist/integrations/agent/builders.js +135 -0
- package/dist/integrations/agent/config.js +122 -230
- package/dist/integrations/agent/detect.js +3 -0
- package/dist/integrations/agent/index.js +7 -13
- package/dist/integrations/agent/model-aliases.js +55 -0
- package/dist/integrations/agent/profiles.js +70 -5
- package/dist/integrations/agent/prompts.js +214 -80
- package/dist/integrations/agent/runner.js +151 -0
- package/dist/integrations/agent/sdk-runner.js +126 -0
- package/dist/integrations/agent/spawn.js +118 -23
- package/dist/integrations/github.js +3 -0
- package/dist/integrations/lockfile.js +32 -69
- package/dist/integrations/session-logs/index.js +69 -0
- package/dist/integrations/session-logs/inline-refs.js +35 -0
- package/dist/integrations/session-logs/pre-filter.js +152 -0
- package/dist/integrations/session-logs/providers/claude-code.js +282 -0
- package/dist/integrations/session-logs/providers/opencode.js +258 -0
- package/dist/integrations/session-logs/types.js +4 -0
- package/dist/llm/call-ai.js +62 -0
- package/dist/llm/client.js +77 -124
- package/dist/llm/embedder.js +20 -29
- package/dist/llm/embedders/cache.js +3 -7
- package/dist/llm/embedders/local.js +42 -1
- package/dist/llm/embedders/remote.js +20 -8
- package/dist/llm/embedders/types.js +3 -7
- package/dist/llm/feature-gate.js +95 -48
- package/dist/llm/graph-extract.js +676 -70
- package/dist/llm/index-passes.js +44 -29
- package/dist/llm/memory-infer.js +77 -71
- package/dist/llm/metadata-enhance.js +42 -29
- package/dist/llm/prompts/extract-session.md +80 -0
- package/dist/llm/prompts/graph-extract-user-prompt.md +35 -0
- package/dist/output/cli-hints-full.md +292 -0
- package/dist/output/cli-hints-short.md +66 -0
- package/dist/output/cli-hints.js +7 -320
- package/dist/output/context.js +60 -8
- package/dist/output/renderers.js +300 -257
- package/dist/output/shapes/curate.js +56 -0
- package/dist/output/shapes/distill.js +10 -0
- package/dist/output/shapes/env-list.js +19 -0
- package/dist/output/shapes/events.js +11 -0
- package/dist/output/shapes/helpers.js +424 -0
- package/dist/output/shapes/history.js +7 -0
- package/dist/output/shapes/passthrough.js +102 -0
- package/dist/output/shapes/proposal-accept.js +7 -0
- package/dist/output/shapes/proposal-diff.js +7 -0
- package/dist/output/shapes/proposal-list.js +7 -0
- package/dist/output/shapes/proposal-producer.js +11 -0
- package/dist/output/shapes/proposal-reject.js +7 -0
- package/dist/output/shapes/proposal-show.js +7 -0
- package/dist/output/shapes/registry-search.js +6 -0
- package/dist/output/shapes/registry.js +30 -0
- package/dist/output/shapes/search.js +6 -0
- package/dist/output/shapes/secret-list.js +19 -0
- package/dist/output/shapes/show.js +6 -0
- package/dist/output/shapes/vault-list.js +19 -0
- package/dist/output/shapes.js +51 -516
- package/dist/output/text/add.js +6 -0
- package/dist/output/text/clone.js +6 -0
- package/dist/output/text/config.js +6 -0
- package/dist/output/text/curate.js +6 -0
- package/dist/output/text/distill.js +7 -0
- package/dist/output/text/enable-disable.js +7 -0
- package/dist/output/text/events.js +10 -0
- package/dist/output/text/feedback.js +6 -0
- package/dist/output/text/helpers.js +1039 -0
- package/dist/output/text/history.js +7 -0
- package/dist/output/text/import.js +6 -0
- package/dist/output/text/index.js +6 -0
- package/dist/output/text/info.js +6 -0
- package/dist/output/text/init.js +6 -0
- package/dist/output/text/list.js +6 -0
- package/dist/output/text/proposal-producer.js +8 -0
- package/dist/output/text/proposal.js +11 -0
- package/dist/output/text/registry-commands.js +11 -0
- package/dist/output/text/registry.js +30 -0
- package/dist/output/text/remember.js +6 -0
- package/dist/output/text/remove.js +6 -0
- package/dist/output/text/save.js +6 -0
- package/dist/output/text/search.js +6 -0
- package/dist/output/text/show.js +6 -0
- package/dist/output/text/update.js +6 -0
- package/dist/output/text/upgrade.js +6 -0
- package/dist/output/text/vault.js +16 -0
- package/dist/output/text/wiki.js +15 -0
- package/dist/output/text/workflow.js +14 -0
- package/dist/output/text.js +44 -1092
- package/dist/registry/build-index.js +3 -0
- package/dist/registry/create-provider-registry.js +3 -0
- package/dist/registry/factory.js +4 -1
- package/dist/registry/origin-resolve.js +3 -0
- package/dist/registry/providers/index.js +3 -0
- package/dist/registry/providers/skills-sh.js +71 -50
- package/dist/registry/providers/static-index.js +53 -48
- package/dist/registry/providers/types.js +3 -24
- package/dist/registry/resolve.js +11 -16
- package/dist/registry/types.js +3 -0
- package/dist/scripts/migrate-storage.js +17750 -0
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +9031 -0
- package/dist/scripts/migrations/v16-to-v17.js +141 -0
- package/dist/setup/detect.js +3 -0
- package/dist/setup/ripgrep-install.js +3 -0
- package/dist/setup/ripgrep-resolve.js +3 -0
- package/dist/setup/setup.js +775 -37
- package/dist/setup/steps.js +3 -15
- package/dist/sources/include.js +3 -0
- package/dist/sources/provider-factory.js +5 -12
- package/dist/sources/provider.js +3 -20
- package/dist/sources/providers/filesystem.js +19 -23
- package/dist/sources/providers/git.js +138 -21
- package/dist/sources/providers/index.js +3 -0
- package/dist/sources/providers/install-types.js +3 -13
- package/dist/sources/providers/npm.js +3 -4
- package/dist/sources/providers/provider-utils.js +3 -0
- package/dist/sources/providers/sync-from-ref.js +3 -11
- package/dist/sources/providers/tar-utils.js +3 -0
- package/dist/sources/providers/website.js +18 -22
- package/dist/sources/resolve.js +3 -0
- package/dist/sources/types.js +3 -0
- package/dist/sources/website-ingest.js +7 -0
- package/dist/tasks/backends/cron.js +203 -0
- package/dist/tasks/backends/exec-utils.js +28 -0
- package/dist/tasks/backends/index.js +24 -0
- package/dist/tasks/backends/launchd-template.xml +19 -0
- package/dist/tasks/backends/launchd.js +187 -0
- package/dist/tasks/backends/schtasks-template.xml +29 -0
- package/dist/tasks/backends/schtasks.js +215 -0
- package/dist/tasks/parser.js +211 -0
- package/dist/tasks/resolveAkmBin.js +87 -0
- package/dist/tasks/runner.js +458 -0
- package/dist/tasks/schedule.js +227 -0
- package/dist/tasks/schema.js +15 -0
- package/dist/tasks/validator.js +62 -0
- package/dist/version.js +3 -0
- package/dist/wiki/index-template.md +12 -0
- package/dist/wiki/ingest-workflow-template.md +54 -0
- package/dist/wiki/log-template.md +8 -0
- package/dist/wiki/schema-template.md +61 -0
- package/dist/wiki/wiki-templates.js +15 -0
- package/dist/wiki/wiki.js +13 -61
- package/dist/workflows/authoring.js +8 -25
- package/dist/workflows/cli.js +3 -0
- package/dist/workflows/db.js +140 -10
- package/dist/workflows/document-cache.js +3 -10
- package/dist/workflows/parser.js +3 -0
- package/dist/workflows/renderer.js +11 -3
- package/dist/workflows/runs.js +77 -92
- package/dist/workflows/schema.js +3 -0
- package/dist/workflows/scope-key.js +3 -0
- package/dist/workflows/validator.js +4 -8
- package/dist/workflows/workflow-template.md +24 -0
- package/docs/README.md +10 -2
- package/docs/data-and-telemetry.md +225 -0
- package/docs/migration/release-notes/0.7.0.md +1 -1
- package/docs/migration/release-notes/0.7.5.md +2 -2
- package/docs/migration/release-notes/0.8.0.md +48 -0
- package/docs/migration/v0.7-to-v0.8.md +1307 -0
- package/package.json +30 -12
- package/.github/LICENSE +0 -374
- package/dist/commands/install-audit.js +0 -381
- package/dist/commands/vault.js +0 -328
- package/dist/templates/wiki-templates.js +0 -100
|
@@ -1,381 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { filterNonEmptyStrings, toPosix } from "../core/common";
|
|
4
|
-
const DEFAULT_INSTALL_AUDIT_CONFIG = {
|
|
5
|
-
enabled: true,
|
|
6
|
-
blockOnCritical: true,
|
|
7
|
-
blockUnlistedRegistries: false,
|
|
8
|
-
registryAllowlist: [],
|
|
9
|
-
allowedFindings: [],
|
|
10
|
-
};
|
|
11
|
-
const MAX_SCANNED_FILE_BYTES = 256 * 1024;
|
|
12
|
-
const LIFECYCLE_SCRIPT_NAMES = new Set([
|
|
13
|
-
"preinstall",
|
|
14
|
-
"install",
|
|
15
|
-
"postinstall",
|
|
16
|
-
"prepublish",
|
|
17
|
-
"prepublishOnly",
|
|
18
|
-
"prepare",
|
|
19
|
-
]);
|
|
20
|
-
const TEXT_FILE_EXTENSIONS = new Set([
|
|
21
|
-
".cjs",
|
|
22
|
-
".cts",
|
|
23
|
-
".js",
|
|
24
|
-
".json",
|
|
25
|
-
".jsonc",
|
|
26
|
-
".jsx",
|
|
27
|
-
".mjs",
|
|
28
|
-
".md",
|
|
29
|
-
".ps1",
|
|
30
|
-
".py",
|
|
31
|
-
".rb",
|
|
32
|
-
".sh",
|
|
33
|
-
".toml",
|
|
34
|
-
".ts",
|
|
35
|
-
".tsx",
|
|
36
|
-
".txt",
|
|
37
|
-
".yaml",
|
|
38
|
-
".yml",
|
|
39
|
-
]);
|
|
40
|
-
const BLOCKED_PACKAGE_DIRECTORIES = new Set(["node_modules", "venv", ".venv", "site-packages"]);
|
|
41
|
-
const CONTENT_RULES = [
|
|
42
|
-
{
|
|
43
|
-
id: "prompt-ignore-previous-instructions",
|
|
44
|
-
severity: "high",
|
|
45
|
-
category: "prompt-injection",
|
|
46
|
-
message: "Contains instructions to ignore prior prompts or instructions.",
|
|
47
|
-
pattern: /\b(ignore|disregard|forget)\b[^.\n]{0,100}\b(previous|prior|earlier)\b[^.\n]{0,100}\b(instructions?|prompts?|messages?)\b/i,
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
id: "prompt-reveal-hidden-secrets",
|
|
51
|
-
severity: "critical",
|
|
52
|
-
category: "prompt-injection",
|
|
53
|
-
message: "Contains instructions to reveal hidden prompts or secrets.",
|
|
54
|
-
pattern: /\b(?:reveal|print|dump|show|output|return|exfiltrat(?:e|ion))\b[^.\n]{0,60}\b(?:your|the)\b[^.\n]{0,40}\b(system prompt|hidden instructions?|developer message|api key|token|secret|password)\b/i,
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
id: "prompt-bypass-guardrails",
|
|
58
|
-
severity: "high",
|
|
59
|
-
category: "prompt-injection",
|
|
60
|
-
message: "Contains instructions to bypass safety or security controls.",
|
|
61
|
-
pattern: /\b(bypass|disable|ignore)\b[^.\n]{0,100}\b(safety|security|guardrails|restrictions|policies)\b/i,
|
|
62
|
-
},
|
|
63
|
-
{
|
|
64
|
-
id: "remote-shell-pipe",
|
|
65
|
-
severity: "critical",
|
|
66
|
-
category: "malicious-code",
|
|
67
|
-
message: "Downloads remote content and pipes it directly into a shell.",
|
|
68
|
-
pattern: /\b(curl|wget)\b[^\n|]{0,200}\|\s*(sh|bash|zsh)\b/i,
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
id: "powershell-download-exec",
|
|
72
|
-
severity: "critical",
|
|
73
|
-
category: "malicious-code",
|
|
74
|
-
message: "Downloads remote content and executes it in PowerShell.",
|
|
75
|
-
pattern: /\b(Invoke-WebRequest|iwr|curl)\b[^\n|]{0,200}\|\s*(iex|Invoke-Expression)\b/i,
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
id: "powershell-encoded-command",
|
|
79
|
-
severity: "critical",
|
|
80
|
-
category: "malicious-code",
|
|
81
|
-
message: "Uses an encoded PowerShell command.",
|
|
82
|
-
pattern: /\bpowershell(?:\.exe)?\b[^\n]{0,120}\s-(?:enc|encodedcommand)\b/i,
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
id: "credential-exfiltration-language",
|
|
86
|
-
severity: "high",
|
|
87
|
-
category: "malicious-code",
|
|
88
|
-
message: "Contains language associated with credential or secret exfiltration.",
|
|
89
|
-
pattern: /\b(exfiltrat(?:e|ion)|harvest|steal)\b[^.\n]{0,120}\b(credentials?|tokens?|secrets?|ssh keys?|passwords?|cookies?)\b/i,
|
|
90
|
-
},
|
|
91
|
-
];
|
|
92
|
-
export function resolveInstallAuditConfig(config) {
|
|
93
|
-
const installAudit = config?.security?.installAudit;
|
|
94
|
-
const allowlist = filterNonEmptyStrings(installAudit?.registryAllowlist) ??
|
|
95
|
-
filterNonEmptyStrings(installAudit?.registryWhitelist) ??
|
|
96
|
-
[];
|
|
97
|
-
return {
|
|
98
|
-
enabled: installAudit?.enabled ?? DEFAULT_INSTALL_AUDIT_CONFIG.enabled,
|
|
99
|
-
blockOnCritical: installAudit?.blockOnCritical ?? DEFAULT_INSTALL_AUDIT_CONFIG.blockOnCritical,
|
|
100
|
-
blockUnlistedRegistries: installAudit?.blockUnlistedRegistries ?? DEFAULT_INSTALL_AUDIT_CONFIG.blockUnlistedRegistries,
|
|
101
|
-
registryAllowlist: allowlist.map((entry) => entry.trim().toLowerCase()),
|
|
102
|
-
allowedFindings: installAudit?.allowedFindings ?? DEFAULT_INSTALL_AUDIT_CONFIG.allowedFindings,
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
export function enforceRegistryInstallPolicy(registryLabels, config, ref) {
|
|
106
|
-
const resolved = resolveInstallAuditConfig(config);
|
|
107
|
-
if (!resolved.blockUnlistedRegistries)
|
|
108
|
-
return;
|
|
109
|
-
if (resolved.registryAllowlist.length === 0) {
|
|
110
|
-
throw new Error(`Install blocked for ${ref}: no registries are allowlisted. Configure security.installAudit.registryAllowlist or disable security.installAudit.blockUnlistedRegistries.`);
|
|
111
|
-
}
|
|
112
|
-
const matched = registryLabels.some((label) => resolved.registryAllowlist.includes(label.toLowerCase()));
|
|
113
|
-
if (matched)
|
|
114
|
-
return;
|
|
115
|
-
throw new Error(`Install blocked for ${ref}: registry is not allowlisted. Allowed: ${resolved.registryAllowlist.join(", ")}. Seen: ${registryLabels.join(", ")}.`);
|
|
116
|
-
}
|
|
117
|
-
export function auditInstallCandidate(input) {
|
|
118
|
-
const resolved = resolveInstallAuditConfig(input.config);
|
|
119
|
-
if (!resolved.enabled) {
|
|
120
|
-
return {
|
|
121
|
-
enabled: false,
|
|
122
|
-
passed: true,
|
|
123
|
-
blocked: false,
|
|
124
|
-
trusted: false,
|
|
125
|
-
registryLabels: [...input.registryLabels],
|
|
126
|
-
findings: [],
|
|
127
|
-
scannedFiles: 0,
|
|
128
|
-
scannedBytes: 0,
|
|
129
|
-
summary: buildSummary([]),
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
const findings = [];
|
|
133
|
-
const counters = { scannedFiles: 0, scannedBytes: 0 };
|
|
134
|
-
scanDirectory(input.rootDir, input.rootDir, findings, counters);
|
|
135
|
-
const { findings: activeFindings, waivedFindings } = splitAllowedFindings(findings, input.ref, resolved.allowedFindings);
|
|
136
|
-
const summary = buildSummary(activeFindings);
|
|
137
|
-
const blocked = !input.trustThisInstall && resolved.blockOnCritical && summary.critical > 0;
|
|
138
|
-
return {
|
|
139
|
-
enabled: true,
|
|
140
|
-
passed: activeFindings.length === 0,
|
|
141
|
-
blocked,
|
|
142
|
-
trusted: Boolean(input.trustThisInstall),
|
|
143
|
-
registryLabels: [...input.registryLabels],
|
|
144
|
-
findings: activeFindings,
|
|
145
|
-
scannedFiles: counters.scannedFiles,
|
|
146
|
-
scannedBytes: counters.scannedBytes,
|
|
147
|
-
summary,
|
|
148
|
-
...(waivedFindings.length > 0 ? { waivedFindings } : {}),
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
export function formatInstallAuditFailure(ref, report) {
|
|
152
|
-
const lines = [`Security audit failed for ${ref}.`, formatInstallAuditSummary(report)];
|
|
153
|
-
for (const finding of report.findings.slice(0, 5)) {
|
|
154
|
-
lines.push(`- [${finding.severity}] ${finding.message}${finding.file ? ` (${finding.file})` : ""}`);
|
|
155
|
-
}
|
|
156
|
-
if (report.findings.length > 5) {
|
|
157
|
-
lines.push(`- ${report.findings.length - 5} more finding(s) omitted`);
|
|
158
|
-
}
|
|
159
|
-
lines.push("Disable blocking with `security.installAudit.blockOnCritical = false`, or disable audits with `security.installAudit.enabled = false`." +
|
|
160
|
-
" Or pass --trust on a one-off 'akm add' to bypass this audit for this install only.");
|
|
161
|
-
return lines.join("\n");
|
|
162
|
-
}
|
|
163
|
-
export function formatInstallAuditSummary(report) {
|
|
164
|
-
if (!report.enabled)
|
|
165
|
-
return "Audit: disabled";
|
|
166
|
-
const severitySummary = [];
|
|
167
|
-
if (report.summary.critical > 0)
|
|
168
|
-
severitySummary.push(`${report.summary.critical} critical`);
|
|
169
|
-
if (report.summary.high > 0)
|
|
170
|
-
severitySummary.push(`${report.summary.high} high`);
|
|
171
|
-
if (report.summary.moderate > 0)
|
|
172
|
-
severitySummary.push(`${report.summary.moderate} moderate`);
|
|
173
|
-
if (report.summary.low > 0)
|
|
174
|
-
severitySummary.push(`${report.summary.low} low`);
|
|
175
|
-
const detail = severitySummary.length > 0 ? severitySummary.join(", ") : "no findings";
|
|
176
|
-
const status = report.blocked ? "blocked" : report.passed ? "passed" : report.trusted ? "trusted" : "warnings";
|
|
177
|
-
const waived = report.waivedFindings?.length ? `; waived ${report.waivedFindings.length}` : "";
|
|
178
|
-
return `Audit: ${status} (${detail}; scanned ${report.scannedFiles} file${report.scannedFiles === 1 ? "" : "s"}${waived})`;
|
|
179
|
-
}
|
|
180
|
-
export function deriveRegistryLabels(input) {
|
|
181
|
-
const labels = new Set();
|
|
182
|
-
labels.add(input.source);
|
|
183
|
-
if (input.source === "github")
|
|
184
|
-
labels.add("github.com");
|
|
185
|
-
if (input.source === "npm")
|
|
186
|
-
labels.add("npm");
|
|
187
|
-
addUrlLabels(labels, input.artifactUrl);
|
|
188
|
-
addUrlLabels(labels, input.gitUrl);
|
|
189
|
-
if (input.source === "github" && input.ref.startsWith("github:")) {
|
|
190
|
-
labels.add("github");
|
|
191
|
-
}
|
|
192
|
-
return [...labels];
|
|
193
|
-
}
|
|
194
|
-
function scanDirectory(dir, rootDir, findings, counters) {
|
|
195
|
-
let entries;
|
|
196
|
-
try {
|
|
197
|
-
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
198
|
-
}
|
|
199
|
-
catch {
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
for (const entry of entries) {
|
|
203
|
-
if (entry.name === ".git")
|
|
204
|
-
continue;
|
|
205
|
-
const fullPath = path.join(dir, entry.name);
|
|
206
|
-
if (entry.isDirectory()) {
|
|
207
|
-
if (BLOCKED_PACKAGE_DIRECTORIES.has(entry.name)) {
|
|
208
|
-
const relativePath = path.relative(rootDir, fullPath) || entry.name;
|
|
209
|
-
findings.push({
|
|
210
|
-
id: "bundled-package-directory",
|
|
211
|
-
severity: "critical",
|
|
212
|
-
category: "vendored-dependency",
|
|
213
|
-
message: `Contains bundled dependency directory "${entry.name}".`,
|
|
214
|
-
file: relativePath,
|
|
215
|
-
snippet: relativePath,
|
|
216
|
-
});
|
|
217
|
-
continue;
|
|
218
|
-
}
|
|
219
|
-
scanDirectory(fullPath, rootDir, findings, counters);
|
|
220
|
-
continue;
|
|
221
|
-
}
|
|
222
|
-
if (!entry.isFile())
|
|
223
|
-
continue;
|
|
224
|
-
scanFile(fullPath, rootDir, findings, counters);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
function scanFile(filePath, rootDir, findings, counters) {
|
|
228
|
-
const ext = path.extname(filePath).toLowerCase();
|
|
229
|
-
const basename = path.basename(filePath).toLowerCase();
|
|
230
|
-
if (basename !== "package.json" && !TEXT_FILE_EXTENSIONS.has(ext))
|
|
231
|
-
return;
|
|
232
|
-
let fileSize;
|
|
233
|
-
try {
|
|
234
|
-
fileSize = fs.statSync(filePath).size;
|
|
235
|
-
}
|
|
236
|
-
catch {
|
|
237
|
-
return;
|
|
238
|
-
}
|
|
239
|
-
const readSize = Math.min(fileSize, MAX_SCANNED_FILE_BYTES);
|
|
240
|
-
const buf = Buffer.alloc(readSize);
|
|
241
|
-
let bytesRead;
|
|
242
|
-
try {
|
|
243
|
-
const fd = fs.openSync(filePath, "r");
|
|
244
|
-
try {
|
|
245
|
-
bytesRead = fs.readSync(fd, buf, 0, readSize, 0);
|
|
246
|
-
}
|
|
247
|
-
finally {
|
|
248
|
-
fs.closeSync(fd);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
catch {
|
|
252
|
-
return;
|
|
253
|
-
}
|
|
254
|
-
if (bytesRead === 0)
|
|
255
|
-
return;
|
|
256
|
-
const bytes = buf.subarray(0, bytesRead);
|
|
257
|
-
if (bytes.includes(0))
|
|
258
|
-
return;
|
|
259
|
-
counters.scannedFiles += 1;
|
|
260
|
-
counters.scannedBytes += bytesRead;
|
|
261
|
-
const content = bytes.toString("utf8");
|
|
262
|
-
const relativePath = path.relative(rootDir, filePath) || path.basename(filePath);
|
|
263
|
-
const genericContent = basename === "package.json" ? stripPackageJsonScripts(content) : content;
|
|
264
|
-
for (const rule of CONTENT_RULES) {
|
|
265
|
-
const match = genericContent.match(rule.pattern);
|
|
266
|
-
if (!match)
|
|
267
|
-
continue;
|
|
268
|
-
findings.push({
|
|
269
|
-
id: rule.id,
|
|
270
|
-
severity: rule.severity,
|
|
271
|
-
category: rule.category,
|
|
272
|
-
message: rule.message,
|
|
273
|
-
file: relativePath,
|
|
274
|
-
snippet: clipSnippet(match[0]),
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
if (basename === "package.json") {
|
|
278
|
-
scanPackageJson(content, relativePath, findings);
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
function stripPackageJsonScripts(content) {
|
|
282
|
-
let parsed;
|
|
283
|
-
try {
|
|
284
|
-
parsed = JSON.parse(content);
|
|
285
|
-
}
|
|
286
|
-
catch {
|
|
287
|
-
return content;
|
|
288
|
-
}
|
|
289
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
|
|
290
|
-
return content;
|
|
291
|
-
const packageJson = { ...parsed };
|
|
292
|
-
delete packageJson.scripts;
|
|
293
|
-
return JSON.stringify(packageJson, null, 2);
|
|
294
|
-
}
|
|
295
|
-
function scanPackageJson(content, relativePath, findings) {
|
|
296
|
-
let parsed;
|
|
297
|
-
try {
|
|
298
|
-
parsed = JSON.parse(content);
|
|
299
|
-
}
|
|
300
|
-
catch {
|
|
301
|
-
return;
|
|
302
|
-
}
|
|
303
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
|
|
304
|
-
return;
|
|
305
|
-
const scripts = parsed.scripts;
|
|
306
|
-
if (typeof scripts !== "object" || scripts === null || Array.isArray(scripts))
|
|
307
|
-
return;
|
|
308
|
-
for (const [name, command] of Object.entries(scripts)) {
|
|
309
|
-
if (!LIFECYCLE_SCRIPT_NAMES.has(name) || typeof command !== "string")
|
|
310
|
-
continue;
|
|
311
|
-
for (const rule of CONTENT_RULES) {
|
|
312
|
-
if (!rule.pattern.test(command))
|
|
313
|
-
continue;
|
|
314
|
-
findings.push({
|
|
315
|
-
id: `lifecycle-${name}-${rule.id}`,
|
|
316
|
-
severity: rule.severity,
|
|
317
|
-
category: "install-script",
|
|
318
|
-
message: `Lifecycle script "${name}" is suspicious: ${rule.message.toLowerCase()}`,
|
|
319
|
-
file: relativePath,
|
|
320
|
-
snippet: clipSnippet(command),
|
|
321
|
-
});
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
function clipSnippet(value) {
|
|
326
|
-
const normalized = value.replace(/\s+/g, " ").trim();
|
|
327
|
-
return normalized.length <= 140 ? normalized : `${normalized.slice(0, 137)}...`;
|
|
328
|
-
}
|
|
329
|
-
function buildSummary(findings) {
|
|
330
|
-
const summary = { low: 0, moderate: 0, high: 0, critical: 0, total: findings.length };
|
|
331
|
-
for (const finding of findings) {
|
|
332
|
-
summary[finding.severity] += 1;
|
|
333
|
-
}
|
|
334
|
-
return summary;
|
|
335
|
-
}
|
|
336
|
-
function splitAllowedFindings(findings, ref, allowedFindings) {
|
|
337
|
-
const active = [];
|
|
338
|
-
const waived = [];
|
|
339
|
-
for (const finding of findings) {
|
|
340
|
-
if (matchesAllowedFinding(finding, ref, allowedFindings)) {
|
|
341
|
-
waived.push(finding);
|
|
342
|
-
continue;
|
|
343
|
-
}
|
|
344
|
-
active.push(finding);
|
|
345
|
-
}
|
|
346
|
-
return { findings: active, waivedFindings: waived };
|
|
347
|
-
}
|
|
348
|
-
function matchesAllowedFinding(finding, ref, allowedFindings) {
|
|
349
|
-
// Normalize paths so a waiver written against `scripts/setup.sh` matches
|
|
350
|
-
// a finding emitted as `./scripts/setup.sh` or `scripts//setup.sh`. On
|
|
351
|
-
// Windows we also fold case, mirroring `isWithin`'s comparison rules.
|
|
352
|
-
const findingPathNormalized = normalizeWaiverPath(finding.file);
|
|
353
|
-
return allowedFindings.some((allowed) => {
|
|
354
|
-
if (allowed.id !== finding.id)
|
|
355
|
-
return false;
|
|
356
|
-
if (allowed.ref && allowed.ref !== ref)
|
|
357
|
-
return false;
|
|
358
|
-
if (allowed.path && normalizeWaiverPath(allowed.path) !== findingPathNormalized)
|
|
359
|
-
return false;
|
|
360
|
-
return true;
|
|
361
|
-
});
|
|
362
|
-
}
|
|
363
|
-
function normalizeWaiverPath(value) {
|
|
364
|
-
if (!value)
|
|
365
|
-
return value;
|
|
366
|
-
// Strip a leading `./` and POSIX-ify after path.normalize so Windows path
|
|
367
|
-
// separators don't trigger spurious mismatches.
|
|
368
|
-
const normalized = toPosix(path.normalize(value)).replace(/^\.\/+/, "");
|
|
369
|
-
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
370
|
-
}
|
|
371
|
-
function addUrlLabels(labels, rawUrl) {
|
|
372
|
-
if (!rawUrl)
|
|
373
|
-
return;
|
|
374
|
-
try {
|
|
375
|
-
const parsed = new URL(rawUrl);
|
|
376
|
-
labels.add(parsed.hostname.toLowerCase());
|
|
377
|
-
}
|
|
378
|
-
catch {
|
|
379
|
-
// Ignore non-URL refs (for example git@host:path)
|
|
380
|
-
}
|
|
381
|
-
}
|