@knowttl/artium-axi 0.0.13 → 0.0.15
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/README.md +8 -3
- package/bin/artium-axi.mjs +525 -48
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -98,7 +98,9 @@ Replace the synthetic username with your account; login uses a hidden terminal p
|
|
|
98
98
|
For `PASSWORD_CHANGE_REQUIRED`, run `artium-axi passwd`, then log in again.
|
|
99
99
|
For SSO, use `artium-axi login --sso` and approve only the browser code matching your own terminal.
|
|
100
100
|
After ordinary login, later commands, installed hooks and skills use [saved connection settings](docs/client-reference.md#client-settings).
|
|
101
|
-
After a server upgrade, run `artium-axi update
|
|
101
|
+
After a server upgrade, run `artium-axi update` if your client has that command, then `artium-axi --version`.
|
|
102
|
+
For clients without `update`, repeat the verified download and checksum check above to install the server's current client.
|
|
103
|
+
For `CLI_OUTDATED`, the server uses the client version header to select the update hint or safe download guidance; the capability boundary is defined by `firstVersionWithUpdate` in [the version contract](src/shared/version.ts).
|
|
102
104
|
The updater uses those connection settings, verifies the server download’s SHA-256, replaces the running client file and keeps the previous client beside it as `<client-file>.bak`.
|
|
103
105
|
The client file and its directory must be writable.
|
|
104
106
|
The Falcon read requires an enabled tool, a role grant and API permissions; choose the example from an available tool's detail view.
|
|
@@ -178,7 +180,7 @@ See [client input handling](docs/client-reference.md#command-surface-and-subcomm
|
|
|
178
180
|
| Azure DevOps | Reviewed mutations with administrator `allowWrites: true`; raw `api` remains GET-only. |
|
|
179
181
|
| AWX | Launch/cancel/relaunch, workflow approval/denial, project/inventory-source sync and notification tests with `allowWrites: true` and upstream `--confirm`; configuration, deletion and security writes stay blocked. |
|
|
180
182
|
| Tenable One | Business writes and export-job creation with administrator `writes.allowWrites: true`, an explicit operations allow-list, native preview, `--execute`, exact `--confirm` and durable journaling; downloads and local configuration stay blocked. |
|
|
181
|
-
| Hybrid Analysis | Sandbox submissions, YARA create/update/delete
|
|
183
|
+
| Hybrid Analysis | Sandbox submissions, YARA create/update/delete, consuming hunting matches and the four reviewed archive downloads require administrator `writes.allowWrites: true`, independently selected operations, native preview, `--execute`, exact `--confirm` and durable journaling. Downloads are limited to `hash-download-bin`, `report-download-bin`, `report-download-dropped-files` and `report-download-memory`, through the [authenticated CLI verified-save path](docs/client-reference.md#hybrid-analysis-archive-saving) after the [operator prerequisites](docs/runbook.md#scope-and-prerequisites) pass. All operations default off; enabling other writes never enables downloads or grants upstream entitlement. The browser provides warnings, command guidance and metadata/save status only. Ordinary tool-run downloads, local configuration and credential/origin overrides are blocked. |
|
|
182
184
|
|
|
183
185
|
Hybrid Analysis quick scans transmit the supplied sample or URL as read exceptions without a write gate; sandbox submission previews disclose that samples and reports can become visible to the community.
|
|
184
186
|
Consuming hunting matches marks every returned match delivered; reconcile ambiguous outcomes using the audit ID, durable native journal and returned read-back guidance before retrying.
|
|
@@ -239,6 +241,7 @@ This summary follows the [CLI help registry](src/cli/help.ts); `artium-axi --hel
|
|
|
239
241
|
| `tools` / `tools show <tool>` | Granted enabled tools / pinned help and first call |
|
|
240
242
|
| `doctor` | Server, TLS, token, version and cached health; five-second request deadlines |
|
|
241
243
|
| `update` | Update the [server-downloaded client](#verified-client) |
|
|
244
|
+
| `download` | Preview and explicitly verify/save administrator-selected Hybrid Analysis archives through the local CLI. Run `artium-axi download --help` for guards and warnings. |
|
|
242
245
|
| `setup --host <host>` | Install managed skills and optional Claude hook |
|
|
243
246
|
| `<tool> <args>` | Run any of the nine managed tools above |
|
|
244
247
|
| `--managed-session --help` | Private-pipe session protocol |
|
|
@@ -248,7 +251,9 @@ The [full client reference](docs/client-reference.md#command-surface-and-subcomm
|
|
|
248
251
|
|
|
249
252
|
### TOON output and errors
|
|
250
253
|
Discovery and Artium errors use TOON on stdout; progress uses stderr.
|
|
251
|
-
Upstream stdout, stderr and exit status pass through,
|
|
254
|
+
Upstream stdout, stderr and exit status pass through, subject to managed suggestion adaptation and hosted status filtering.
|
|
255
|
+
Hosted home and authentication status omit server executable and configuration paths; secret references report only their source kind, `env` or `file`.
|
|
256
|
+
Malformed or truncated hosted status output is withheld with `INVALID_RESPONSE`.
|
|
252
257
|
Artium exits 0 for success, 2 for usage/configuration errors and 1 for authentication/server/transport failures.
|
|
253
258
|
See the generated [error catalogue](docs/errors.md) for actionable codes and retry guidance.
|
|
254
259
|
|
package/bin/artium-axi.mjs
CHANGED
|
@@ -6,9 +6,9 @@ var __export = (target, all) => {
|
|
|
6
6
|
};
|
|
7
7
|
|
|
8
8
|
// src/cli/main.ts
|
|
9
|
-
import { randomUUID as
|
|
9
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
10
10
|
import { homedir as homedir3 } from "node:os";
|
|
11
|
-
import { resolve as
|
|
11
|
+
import { resolve as resolve4, relative, sep, isAbsolute as isAbsolute2 } from "node:path";
|
|
12
12
|
|
|
13
13
|
// node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/classic/external.js
|
|
14
14
|
var external_exports = {};
|
|
@@ -5036,7 +5036,7 @@ var recursive = /* @__PURE__ */ new WeakMap();
|
|
|
5036
5036
|
var NONE = 0;
|
|
5037
5037
|
var ASSUMED = 1;
|
|
5038
5038
|
var PROVEN = 2;
|
|
5039
|
-
function isRecursive(inst, stack,
|
|
5039
|
+
function isRecursive(inst, stack, resolve5) {
|
|
5040
5040
|
const cached2 = recursive.get(inst);
|
|
5041
5041
|
if (cached2 !== void 0)
|
|
5042
5042
|
return cached2 ? PROVEN : NONE;
|
|
@@ -5046,7 +5046,7 @@ function isRecursive(inst, stack, resolve4) {
|
|
|
5046
5046
|
let result = NONE;
|
|
5047
5047
|
const check2 = (child) => {
|
|
5048
5048
|
if (result !== PROVEN && child?._zod) {
|
|
5049
|
-
const answer = isRecursive(child, stack,
|
|
5049
|
+
const answer = isRecursive(child, stack, resolve5);
|
|
5050
5050
|
if (answer > result)
|
|
5051
5051
|
result = answer;
|
|
5052
5052
|
}
|
|
@@ -5057,7 +5057,7 @@ function isRecursive(inst, stack, resolve4) {
|
|
|
5057
5057
|
const desc = Object.getOwnPropertyDescriptor(sh, key);
|
|
5058
5058
|
if (spread && !desc.enumerable)
|
|
5059
5059
|
continue;
|
|
5060
|
-
const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack,
|
|
5060
|
+
const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve5) : NONE;
|
|
5061
5061
|
if (child > answer)
|
|
5062
5062
|
answer = child;
|
|
5063
5063
|
}
|
|
@@ -5121,7 +5121,7 @@ function isRecursive(inst, stack, resolve4) {
|
|
|
5121
5121
|
break;
|
|
5122
5122
|
// `$ZodLazy` caches its inner on the def, so a resolved edge is followed exactly
|
|
5123
5123
|
case "lazy": {
|
|
5124
|
-
const inner = def._cachedInner ?? (
|
|
5124
|
+
const inner = def._cachedInner ?? (resolve5 ? inst._zod.innerType : void 0);
|
|
5125
5125
|
merge2(inner ? isRecursive(inner, stack, false) : ASSUMED);
|
|
5126
5126
|
break;
|
|
5127
5127
|
}
|
|
@@ -19679,11 +19679,12 @@ function date4(params) {
|
|
|
19679
19679
|
}
|
|
19680
19680
|
|
|
19681
19681
|
// src/shared/errors.ts
|
|
19682
|
+
var cliOutdatedDownloadNext = "Download the current CLI from /cli/artium-axi.mjs on this server, verify it with the SHA-256 checksum at /cli/artium-axi.mjs.sha256, and install it as artium-axi.";
|
|
19682
19683
|
var definitions = {
|
|
19683
19684
|
STORE_SCHEMA_INVALID: { status: 503, message: "The store schema is unsupported by this image.", next: "Use the matching image, or restore the pre-upgrade backup into a new volume with its matching image and key." },
|
|
19684
19685
|
BACKUP_FAILED: { status: 500, message: "Database backup did not complete.", next: "Check the source store and a writable backup directory; choose a new output file." },
|
|
19685
19686
|
RESTORE_FAILED: { status: 500, message: "Database restore did not complete.", next: "Stop the destination server and use an empty new data volume, an intact backup and its matching master key." },
|
|
19686
|
-
CLI_OUTDATED: { status: 426, message: "The CLI version is too old for this server.", next:
|
|
19687
|
+
CLI_OUTDATED: { status: 426, message: "The CLI version is too old for this server.", next: cliOutdatedDownloadNext },
|
|
19687
19688
|
CERTIFICATE_INVALID: { status: 503, message: "The server certificate could not be verified.", next: "Check certificate trust or set ARTIUM_AXI_CA_FILE to the trusted CA file." },
|
|
19688
19689
|
ADMIN_REQUIRED: { status: 403, message: "Administrator capability is required.", next: "Ask an administrator to perform this operation." },
|
|
19689
19690
|
REVISION_CONFLICT: { status: 409, message: "The record changed since it was read.", next: "Read the current record revision and retry your edit." },
|
|
@@ -19719,6 +19720,9 @@ var definitions = {
|
|
|
19719
19720
|
PASSWORD_CHANGE_REQUIRED: { status: 409, message: "Change your temporary password before using tools.", next: "Run artium-axi passwd --password-stdin." },
|
|
19720
19721
|
SERVER_UNAVAILABLE: { status: 503, message: "The CLI could not receive a server response.", next: "Check the server address and certificate trust; confirm the previous call outcome before retrying." },
|
|
19721
19722
|
INVALID_RESPONSE: { status: 500, message: "The server returned an invalid response.", next: "Check the server URL and API version." },
|
|
19723
|
+
KEY_LEVEL_RESTRICTED: { status: 403, message: "HTTP 403: restricted key cannot download.", next: "Request vetted-account download access. Administrator write settings cannot grant upstream entitlement. No other key or route was tried." },
|
|
19724
|
+
PERMISSION_DENIED: { status: 403, message: "HTTP 403: download denied; key level unknown.", next: "Check upstream entitlement and request account vetting where required. Administrator write settings cannot grant upstream entitlement. No other key or route was tried." },
|
|
19725
|
+
ARTIFACT_SAVE_FAILED: { status: 400, message: "The archive was not verified and saved.", next: "Check the dedicated private destination directory. Start a fresh preview and confirmation; interrupted transfers cannot be retried." },
|
|
19722
19726
|
DEV_AUTH_INVALID: { status: 503, message: "Development authentication settings are unsupported.", next: "Remove ARTIUM_AXI_DEV and ARTIUM_AXI_DEV_TOKEN_FILE and use local sign-in." },
|
|
19723
19727
|
PROXY_HEADERS_UNTRUSTED: { status: 400, message: "Forwarding headers came from an untrusted address.", next: "Connect through the configured trusted proxy or remove forwarding headers." },
|
|
19724
19728
|
NOT_FOUND: { status: 404, message: "The endpoint does not exist.", next: "Use a supported API endpoint and tool name." },
|
|
@@ -19781,6 +19785,103 @@ var inputLimits = {
|
|
|
19781
19785
|
totalFileBytes: 4 * 1024 * 1024,
|
|
19782
19786
|
files: 8
|
|
19783
19787
|
};
|
|
19788
|
+
var transferLimits = {
|
|
19789
|
+
artifactBytes: 250 * 1024 * 1024,
|
|
19790
|
+
reservationsPerUser: 1,
|
|
19791
|
+
reservations: 4,
|
|
19792
|
+
reservedBytes: 1024 * 1024 * 1024,
|
|
19793
|
+
lifetimeMs: 10 * 60 * 1e3
|
|
19794
|
+
};
|
|
19795
|
+
var transferWarning = "Live malware: this archive may contain executable malware. Save only; do not extract or execute. Password protection is unverified; bytes are saved exactly as provided.";
|
|
19796
|
+
var sha256Schema = external_exports.string().regex(/^[a-f0-9]{64}$/);
|
|
19797
|
+
var sampleSha256Schema = external_exports.string().regex(/^[a-f0-9]{64}$/i);
|
|
19798
|
+
var reportIdSchema = external_exports.string().regex(/^(?:[A-Za-z0-9_-]{1,256}|[a-f0-9]{64}:\d+)$/i);
|
|
19799
|
+
var transferOwnerIdSchema = external_exports.string().regex(/^[A-Za-z0-9_-]{1,256}$/);
|
|
19800
|
+
var transferSelectionSchema = external_exports.discriminatedUnion("operation", [
|
|
19801
|
+
external_exports.strictObject({ operation: external_exports.literal("hash-download-bin"), selector: sampleSha256Schema, type: external_exports.literal("bin") }),
|
|
19802
|
+
external_exports.strictObject({ operation: external_exports.literal("report-download-bin"), selector: reportIdSchema, type: external_exports.literal("bin") }),
|
|
19803
|
+
external_exports.strictObject({ operation: external_exports.literal("report-download-dropped-files"), selector: reportIdSchema, type: external_exports.literal("dropped-files") }),
|
|
19804
|
+
external_exports.strictObject({ operation: external_exports.literal("report-download-memory"), selector: reportIdSchema, type: external_exports.literal("memory") })
|
|
19805
|
+
]);
|
|
19806
|
+
var transferPrepareSchema = external_exports.strictObject({ selection: transferSelectionSchema, targetDigest: sha256Schema });
|
|
19807
|
+
var transferIdSchema = external_exports.uuid();
|
|
19808
|
+
var transferScopeSchema = external_exports.strictObject({
|
|
19809
|
+
transferId: transferIdSchema,
|
|
19810
|
+
userId: transferOwnerIdSchema,
|
|
19811
|
+
sessionFamilyId: transferOwnerIdSchema,
|
|
19812
|
+
credentialRevision: external_exports.number().int().positive(),
|
|
19813
|
+
selection: transferSelectionSchema,
|
|
19814
|
+
targetDigest: sha256Schema,
|
|
19815
|
+
preparedAt: external_exports.number().int().nonnegative(),
|
|
19816
|
+
expiresAt: external_exports.number().int().nonnegative()
|
|
19817
|
+
}).refine((scope) => scope.expiresAt === scope.preparedAt + transferLimits.lifetimeMs);
|
|
19818
|
+
var transferPreviewSchema = external_exports.strictObject({
|
|
19819
|
+
transferId: transferIdSchema,
|
|
19820
|
+
expiresAt: external_exports.number().int(),
|
|
19821
|
+
confirm: external_exports.string().min(1).max(64 * 1024),
|
|
19822
|
+
warnings: external_exports.array(external_exports.string()),
|
|
19823
|
+
targetDigest: sha256Schema
|
|
19824
|
+
});
|
|
19825
|
+
var transferExecuteSchema = external_exports.strictObject({
|
|
19826
|
+
transferId: transferIdSchema,
|
|
19827
|
+
selection: transferSelectionSchema,
|
|
19828
|
+
targetDigest: sha256Schema,
|
|
19829
|
+
execute: external_exports.literal(true),
|
|
19830
|
+
confirm: external_exports.string().min(1).max(64 * 1024)
|
|
19831
|
+
});
|
|
19832
|
+
var transferRetrieveSchema = transferPrepareSchema.extend({ transferId: transferIdSchema });
|
|
19833
|
+
var transferOutcomeSchema = external_exports.enum([
|
|
19834
|
+
"preview",
|
|
19835
|
+
"denied-not-sent",
|
|
19836
|
+
"native-outcome-unknown",
|
|
19837
|
+
"upstream-failure",
|
|
19838
|
+
"ready",
|
|
19839
|
+
"delivery-completed",
|
|
19840
|
+
"delivery-interrupted",
|
|
19841
|
+
"expired",
|
|
19842
|
+
"deleted",
|
|
19843
|
+
"interrupted"
|
|
19844
|
+
]);
|
|
19845
|
+
var transferSaveOutcomeSchema = external_exports.enum(["unconfirmed", "verified-save", "save-failed"]);
|
|
19846
|
+
var transferArchiveSchema = external_exports.strictObject({
|
|
19847
|
+
archiveBytes: external_exports.number().int().nonnegative().max(transferLimits.artifactBytes),
|
|
19848
|
+
archiveSha256: sha256Schema
|
|
19849
|
+
});
|
|
19850
|
+
var transferReceiptSchema = transferRetrieveSchema.extend({
|
|
19851
|
+
archive: transferArchiveSchema,
|
|
19852
|
+
saveOutcome: external_exports.enum(["verified-save", "save-failed"])
|
|
19853
|
+
});
|
|
19854
|
+
var transferEvidenceSchema = external_exports.strictObject({
|
|
19855
|
+
outcome: transferOutcomeSchema,
|
|
19856
|
+
archive: transferArchiveSchema.nullable(),
|
|
19857
|
+
saveOutcome: transferSaveOutcomeSchema
|
|
19858
|
+
}).refine((evidence) => !["ready", "delivery-completed"].includes(evidence.outcome) || evidence.archive !== null).refine((evidence) => !["preview", "denied-not-sent", "native-outcome-unknown", "upstream-failure"].includes(evidence.outcome) || evidence.archive === null).refine((evidence) => evidence.saveOutcome !== "verified-save" || evidence.archive !== null);
|
|
19859
|
+
var transferStatusSchema = transferEvidenceSchema.safeExtend({ transferId: transferIdSchema, expiresAt: external_exports.number().int() });
|
|
19860
|
+
var transferRoutes = {
|
|
19861
|
+
prepare: { method: "POST", path: "/v1/transfers/hybrid/prepare", input: transferPrepareSchema, response: transferPreviewSchema },
|
|
19862
|
+
execute: { method: "POST", path: "/v1/transfers/hybrid/execute", input: transferExecuteSchema, response: transferStatusSchema },
|
|
19863
|
+
retrieve: { method: "POST", path: "/v1/transfers/hybrid/retrieve", input: transferRetrieveSchema },
|
|
19864
|
+
receipt: { method: "POST", path: "/v1/transfers/hybrid/receipt", input: transferReceiptSchema },
|
|
19865
|
+
cancel: { method: "POST", path: "/v1/transfers/hybrid/cancel", input: transferRetrieveSchema },
|
|
19866
|
+
status: { method: "POST", path: "/v1/transfers/hybrid/status", input: transferRetrieveSchema, response: transferStatusSchema }
|
|
19867
|
+
};
|
|
19868
|
+
var transferAuditSchema = external_exports.strictObject({
|
|
19869
|
+
transfer_id: transferIdSchema,
|
|
19870
|
+
session_family_id: transferOwnerIdSchema,
|
|
19871
|
+
transfer_operation: transferSelectionSchema.options[0].shape.operation.or(transferSelectionSchema.options[1].shape.operation).or(transferSelectionSchema.options[2].shape.operation).or(transferSelectionSchema.options[3].shape.operation),
|
|
19872
|
+
artifact_type: external_exports.enum(["bin", "dropped-files", "memory"]),
|
|
19873
|
+
artifact_selector: sha256Schema.or(reportIdSchema),
|
|
19874
|
+
requested_target_digest: sha256Schema,
|
|
19875
|
+
archive_bytes: external_exports.number().int().nonnegative().max(transferLimits.artifactBytes).nullable(),
|
|
19876
|
+
archive_sha256: sha256Schema.nullable(),
|
|
19877
|
+
native_audit_id: external_exports.uuid().nullable(),
|
|
19878
|
+
native_journal_result: external_exports.enum(["SUCCESS", "FAILED", "NOT_SENT", "OUTCOME_UNKNOWN"]).nullable(),
|
|
19879
|
+
client_save_outcome: transferSaveOutcomeSchema
|
|
19880
|
+
}).refine((fields) => fields.archive_bytes === null === (fields.archive_sha256 === null)).refine((fields) => transferSelectionSchema.safeParse({
|
|
19881
|
+
operation: fields.transfer_operation,
|
|
19882
|
+
type: fields.artifact_type,
|
|
19883
|
+
selector: fields.artifact_selector
|
|
19884
|
+
}).success);
|
|
19784
19885
|
var byteLength = (value) => new TextEncoder().encode(value).length;
|
|
19785
19886
|
var passwordSchema = external_exports.string().min(1).refine((value) => byteLength(value) <= 512, "Expected at most 512 UTF-8 bytes");
|
|
19786
19887
|
var passwordChangeSchema = external_exports.strictObject({ newPassword: external_exports.string(), currentPassword: external_exports.string().optional() });
|
|
@@ -19998,7 +20099,8 @@ var auditFiltersSchema = external_exports.strictObject({
|
|
|
19998
20099
|
event: external_exports.string().min(1).max(100).optional(),
|
|
19999
20100
|
outcome: external_exports.string().min(1).max(100).optional(),
|
|
20000
20101
|
from: external_exports.iso.datetime().optional(),
|
|
20001
|
-
to: external_exports.iso.datetime().optional()
|
|
20102
|
+
to: external_exports.iso.datetime().optional(),
|
|
20103
|
+
errorCode: external_exports.string().min(1).max(100).optional()
|
|
20002
20104
|
});
|
|
20003
20105
|
var orderedAuditRange = (input2) => !input2.from || !input2.to || Date.parse(input2.from) <= Date.parse(input2.to);
|
|
20004
20106
|
var adminAuditQuerySchema = auditFiltersSchema.extend({
|
|
@@ -20036,7 +20138,18 @@ var adminAuditRecordSchema = external_exports.strictObject({
|
|
|
20036
20138
|
error_code: external_exports.string().nullable(),
|
|
20037
20139
|
authentication_method: external_exports.string().nullable(),
|
|
20038
20140
|
provider_id: external_exports.string().nullable(),
|
|
20039
|
-
subject: external_exports.string().nullable()
|
|
20141
|
+
subject: external_exports.string().nullable(),
|
|
20142
|
+
transfer_id: transferIdSchema.nullable(),
|
|
20143
|
+
session_family_id: external_exports.string().nullable(),
|
|
20144
|
+
transfer_operation: external_exports.string().nullable(),
|
|
20145
|
+
artifact_type: external_exports.string().nullable(),
|
|
20146
|
+
artifact_selector: external_exports.string().nullable(),
|
|
20147
|
+
requested_target_digest: sha256Schema.nullable(),
|
|
20148
|
+
archive_bytes: external_exports.number().int().nullable(),
|
|
20149
|
+
archive_sha256: sha256Schema.nullable(),
|
|
20150
|
+
native_audit_id: external_exports.string().nullable(),
|
|
20151
|
+
native_journal_result: external_exports.string().nullable(),
|
|
20152
|
+
client_save_outcome: transferSaveOutcomeSchema.nullable()
|
|
20040
20153
|
});
|
|
20041
20154
|
var adminAuditRoute = {
|
|
20042
20155
|
method: "GET",
|
|
@@ -20051,13 +20164,11 @@ var falconRtrSettingsSchema = external_exports.strictObject({
|
|
|
20051
20164
|
commands: external_exports.array(external_exports.enum([
|
|
20052
20165
|
"cat",
|
|
20053
20166
|
"cd",
|
|
20054
|
-
"clear",
|
|
20055
20167
|
"csrutil",
|
|
20056
20168
|
"env",
|
|
20057
20169
|
"eventlog",
|
|
20058
20170
|
"filehash",
|
|
20059
20171
|
"getsid",
|
|
20060
|
-
"history",
|
|
20061
20172
|
"ifconfig",
|
|
20062
20173
|
"ipconfig",
|
|
20063
20174
|
"ls",
|
|
@@ -20119,7 +20230,7 @@ var diagnosticSchema = external_exports.strictObject({
|
|
|
20119
20230
|
credentialRevision: external_exports.number().int().nullable().optional()
|
|
20120
20231
|
});
|
|
20121
20232
|
var healthSchema = external_exports.strictObject({
|
|
20122
|
-
status: external_exports.enum(["unconfigured", "unknown", "ok", "error", "disabled"]),
|
|
20233
|
+
status: external_exports.enum(["unconfigured", "unknown", "checking", "ok", "error", "disabled"]),
|
|
20123
20234
|
diagnostic: diagnosticSchema.optional(),
|
|
20124
20235
|
readOnly: external_exports.boolean().default(true),
|
|
20125
20236
|
checkedAt: external_exports.number().int().nullable(),
|
|
@@ -20577,7 +20688,7 @@ var toolsResponseSchema = external_exports.strictObject({
|
|
|
20577
20688
|
name: toolSchema,
|
|
20578
20689
|
version: external_exports.string(),
|
|
20579
20690
|
readOnly: external_exports.boolean().default(true),
|
|
20580
|
-
status: external_exports.enum(["unconfigured", "unknown", "ok", "error", "disabled"])
|
|
20691
|
+
status: external_exports.enum(["unconfigured", "unknown", "checking", "ok", "error", "disabled"])
|
|
20581
20692
|
}))
|
|
20582
20693
|
});
|
|
20583
20694
|
var inputFileSchema = external_exports.strictObject({
|
|
@@ -20634,7 +20745,7 @@ import { resolve } from "node:path";
|
|
|
20634
20745
|
// package.json
|
|
20635
20746
|
var package_default = {
|
|
20636
20747
|
name: "artium-axi",
|
|
20637
|
-
version: "0.0.
|
|
20748
|
+
version: "0.0.15",
|
|
20638
20749
|
private: true,
|
|
20639
20750
|
license: "MIT",
|
|
20640
20751
|
type: "module",
|
|
@@ -20823,8 +20934,8 @@ var SessionStore = class {
|
|
|
20823
20934
|
while (true) {
|
|
20824
20935
|
const watcher = watch(this.directory);
|
|
20825
20936
|
let timer;
|
|
20826
|
-
const changed = new Promise((
|
|
20827
|
-
watcher.once("change", () =>
|
|
20937
|
+
const changed = new Promise((resolve5, reject) => {
|
|
20938
|
+
watcher.once("change", () => resolve5());
|
|
20828
20939
|
watcher.once("error", () => reject(new ArtiumError("SESSION_STORAGE_UNAVAILABLE")));
|
|
20829
20940
|
timer = setTimeout(() => reject(new ArtiumError("SESSION_BUSY")), Math.max(0, deadline - Date.now()));
|
|
20830
20941
|
});
|
|
@@ -20872,6 +20983,10 @@ var Client = class {
|
|
|
20872
20983
|
}
|
|
20873
20984
|
managed;
|
|
20874
20985
|
requestDeadlineMs;
|
|
20986
|
+
cancellation = new AbortController();
|
|
20987
|
+
cancel() {
|
|
20988
|
+
this.cancellation.abort();
|
|
20989
|
+
}
|
|
20875
20990
|
serverUrl;
|
|
20876
20991
|
serverUrlSource;
|
|
20877
20992
|
caFile;
|
|
@@ -20881,7 +20996,7 @@ var Client = class {
|
|
|
20881
20996
|
return this.storedSessions;
|
|
20882
20997
|
}
|
|
20883
20998
|
certificateVerified = false;
|
|
20884
|
-
async request(path, method, input2, token, bytes = false) {
|
|
20999
|
+
async request(path, method, input2, token, bytes = false, headers = {}) {
|
|
20885
21000
|
this.certificateVerified = false;
|
|
20886
21001
|
let ca;
|
|
20887
21002
|
try {
|
|
@@ -20893,13 +21008,13 @@ var Client = class {
|
|
|
20893
21008
|
const controller = new AbortController();
|
|
20894
21009
|
const deadline = setTimeout(() => controller.abort(), this.requestDeadlineMs);
|
|
20895
21010
|
const url2 = new URL(`${this.serverUrl}${path}`);
|
|
20896
|
-
const result = await new Promise((
|
|
21011
|
+
const result = await new Promise((resolve5, reject) => {
|
|
20897
21012
|
const request = (url2.protocol === "https:" ? httpsRequest : httpRequest)(url2, {
|
|
20898
21013
|
method,
|
|
20899
21014
|
ca,
|
|
20900
21015
|
rejectUnauthorized: true,
|
|
20901
|
-
signal: controller.signal,
|
|
20902
|
-
headers: { "x-artium-cli-version": cliVersion, ...token ? { authorization: `Bearer ${token}` } : {}, ...body ? { "content-type": "application/json" } : {} }
|
|
21016
|
+
signal: AbortSignal.any([controller.signal, this.cancellation.signal]),
|
|
21017
|
+
headers: { ...headers, "x-artium-cli-version": cliVersion, ...token ? { authorization: `Bearer ${token}` } : {}, ...body ? { "content-type": "application/json" } : {} }
|
|
20903
21018
|
}, (response) => {
|
|
20904
21019
|
if (response.statusCode >= 300 && response.statusCode < 400) {
|
|
20905
21020
|
response.destroy();
|
|
@@ -20911,7 +21026,7 @@ var Client = class {
|
|
|
20911
21026
|
response.on("error", () => reject(new ArtiumError("SERVER_UNAVAILABLE")));
|
|
20912
21027
|
response.on("end", () => {
|
|
20913
21028
|
try {
|
|
20914
|
-
|
|
21029
|
+
resolve5({
|
|
20915
21030
|
status: response.statusCode,
|
|
20916
21031
|
body: bytes ? Buffer.concat(chunks) : response.statusCode === 204 ? void 0 : JSON.parse(Buffer.concat(chunks).toString("utf8")),
|
|
20917
21032
|
serverVersion: typeof response.headers["x-artium-cli-version"] === "string" ? response.headers["x-artium-cli-version"] : void 0
|
|
@@ -20975,6 +21090,70 @@ var Client = class {
|
|
|
20975
21090
|
if (previous) this.reload(previous);
|
|
20976
21091
|
return result;
|
|
20977
21092
|
}
|
|
21093
|
+
// Transfers never refresh/replay a request, including an ambiguous execute or
|
|
21094
|
+
// a retrieval whose response was interrupted before headers arrived.
|
|
21095
|
+
async transferSession(work) {
|
|
21096
|
+
if (this.managed) return this.managed.once(work);
|
|
21097
|
+
const session = this.sessions.read();
|
|
21098
|
+
if (!session) throw new ArtiumError("TOKEN_INVALID");
|
|
21099
|
+
if (session.status !== "authenticated") throw new ArtiumError("PASSWORD_CHANGE_REQUIRED");
|
|
21100
|
+
return work(session.accessToken);
|
|
21101
|
+
}
|
|
21102
|
+
async artifact(path, input2, token, ticket, expiresAt, consume) {
|
|
21103
|
+
let ca;
|
|
21104
|
+
try {
|
|
21105
|
+
if (this.caFile) ca = [...rootCertificates, readFileSync2(this.caFile, "utf8")];
|
|
21106
|
+
} catch {
|
|
21107
|
+
throw new ArtiumError("CLIENT_CONFIG_INVALID");
|
|
21108
|
+
}
|
|
21109
|
+
const url2 = new URL(`${this.serverUrl}${path}`);
|
|
21110
|
+
const controller = new AbortController();
|
|
21111
|
+
const signal = AbortSignal.any([controller.signal, this.cancellation.signal]);
|
|
21112
|
+
const deadline = setTimeout(() => controller.abort(), Math.max(0, expiresAt - Date.now()));
|
|
21113
|
+
try {
|
|
21114
|
+
await new Promise((resolve5, reject) => {
|
|
21115
|
+
let incoming;
|
|
21116
|
+
const request = (url2.protocol === "https:" ? httpsRequest : httpRequest)(url2, {
|
|
21117
|
+
method: "POST",
|
|
21118
|
+
ca,
|
|
21119
|
+
rejectUnauthorized: true,
|
|
21120
|
+
signal,
|
|
21121
|
+
headers: {
|
|
21122
|
+
authorization: `Bearer ${token}`,
|
|
21123
|
+
"x-artium-cli-version": cliVersion,
|
|
21124
|
+
"x-artium-reauth-ticket": ticket,
|
|
21125
|
+
"accept-encoding": "identity",
|
|
21126
|
+
"content-type": "application/json"
|
|
21127
|
+
}
|
|
21128
|
+
}, (response) => {
|
|
21129
|
+
incoming = response;
|
|
21130
|
+
void (async () => {
|
|
21131
|
+
if (response.statusCode !== 200) {
|
|
21132
|
+
const chunks = [];
|
|
21133
|
+
let size = 0;
|
|
21134
|
+
for await (const chunk of response) {
|
|
21135
|
+
size += chunk.length;
|
|
21136
|
+
if (size > 64 * 1024) throw new ArtiumError("INVALID_RESPONSE");
|
|
21137
|
+
chunks.push(chunk);
|
|
21138
|
+
}
|
|
21139
|
+
const failure2 = errorResponseSchema.safeParse(JSON.parse(Buffer.concat(chunks).toString("utf8")));
|
|
21140
|
+
if (failure2.success) throw new ArtiumError(failure2.data.error.code, failure2.data.error.message, failure2.data.error.next);
|
|
21141
|
+
throw new ArtiumError("INVALID_RESPONSE");
|
|
21142
|
+
}
|
|
21143
|
+
await consume(response, signal);
|
|
21144
|
+
})().then(resolve5, reject).finally(() => response.destroy());
|
|
21145
|
+
});
|
|
21146
|
+
request.on("error", () => {
|
|
21147
|
+
const error62 = new ArtiumError("SERVER_UNAVAILABLE");
|
|
21148
|
+
if (incoming) incoming.destroy(error62);
|
|
21149
|
+
else reject(error62);
|
|
21150
|
+
});
|
|
21151
|
+
request.end(JSON.stringify(input2));
|
|
21152
|
+
});
|
|
21153
|
+
} finally {
|
|
21154
|
+
clearTimeout(deadline);
|
|
21155
|
+
}
|
|
21156
|
+
}
|
|
20978
21157
|
// The caller holds the session lock, including the reload after acquisition.
|
|
20979
21158
|
async refresh(previous) {
|
|
20980
21159
|
const current = this.reload(previous);
|
|
@@ -21023,13 +21202,13 @@ async function readPassword(stdin, environmentVariable) {
|
|
|
21023
21202
|
process.stdin.setRawMode(true);
|
|
21024
21203
|
process.stdin.resume();
|
|
21025
21204
|
try {
|
|
21026
|
-
return await new Promise((
|
|
21205
|
+
return await new Promise((resolve5, reject) => {
|
|
21027
21206
|
let password = "";
|
|
21028
21207
|
const keypress = (text2, key) => {
|
|
21029
21208
|
if (key.name === "return" || key.ctrl && key.name === "c") {
|
|
21030
21209
|
process.stdin.off("keypress", keypress);
|
|
21031
21210
|
if (key.ctrl) reject(new ArtiumError("CANCELLED"));
|
|
21032
|
-
else
|
|
21211
|
+
else resolve5(password);
|
|
21033
21212
|
} else if (key.name === "backspace") password = Array.from(password).slice(0, -1).join("");
|
|
21034
21213
|
else if (text2 && !key.ctrl && !new RegExp("\\p{Cc}", "u").test(text2)) password += text2;
|
|
21035
21214
|
};
|
|
@@ -21048,13 +21227,13 @@ var falcon_axi_default = {
|
|
|
21048
21227
|
generatedBy: "scripts/falcon-inventory.mjs",
|
|
21049
21228
|
tool: "falcon-axi",
|
|
21050
21229
|
repository: "https://github.com/knowttl/falcon-axi.git",
|
|
21051
|
-
tag: "v0.7.
|
|
21052
|
-
commit: "
|
|
21053
|
-
version: "0.7.
|
|
21230
|
+
tag: "v0.7.2",
|
|
21231
|
+
commit: "96392a6b2afeb86d5422e3ff55e5a3d5e8af769f",
|
|
21232
|
+
version: "0.7.2",
|
|
21054
21233
|
distribution: {
|
|
21055
|
-
filename: "falcon_axi-0.7.
|
|
21056
|
-
sha256: "
|
|
21057
|
-
provenance: "https://pypi.org/integrity/falcon-axi/0.7.
|
|
21234
|
+
filename: "falcon_axi-0.7.2-py3-none-any.whl",
|
|
21235
|
+
sha256: "8b87c8047f782a6d390324a26179def0f77e6c7c5bf4212012d5802a249132b6",
|
|
21236
|
+
provenance: "https://pypi.org/integrity/falcon-axi/0.7.2/falcon_axi-0.7.2-py3-none-any.whl/provenance"
|
|
21058
21237
|
},
|
|
21059
21238
|
parser: {
|
|
21060
21239
|
path: "falcon_axi/cli.py",
|
|
@@ -41176,7 +41355,11 @@ var hybrid_axi_default = {
|
|
|
41176
41355
|
nativeWriteSettings: {
|
|
41177
41356
|
supportedOperations: [
|
|
41178
41357
|
"convertQuickScan",
|
|
41358
|
+
"hash-download-bin",
|
|
41179
41359
|
"huntingMatches",
|
|
41360
|
+
"report-download-bin",
|
|
41361
|
+
"report-download-dropped-files",
|
|
41362
|
+
"report-download-memory",
|
|
41180
41363
|
"submitDroppedFile",
|
|
41181
41364
|
"submitFile",
|
|
41182
41365
|
"submitUrl",
|
|
@@ -41208,7 +41391,7 @@ var hybrid_axi_default = {
|
|
|
41208
41391
|
inputs: {
|
|
41209
41392
|
stdin: "body-file - only",
|
|
41210
41393
|
files: "uploaded leaves only",
|
|
41211
|
-
downloads: "
|
|
41394
|
+
downloads: "artium-axi download only; authenticated one-shot CLI verified save"
|
|
41212
41395
|
},
|
|
41213
41396
|
nativeWrites: "native preview, operation allow-list, execute, exact confirm and durable journal"
|
|
41214
41397
|
},
|
|
@@ -41231,7 +41414,7 @@ var hybrid_axi_default = {
|
|
|
41231
41414
|
example: [
|
|
41232
41415
|
"quota"
|
|
41233
41416
|
],
|
|
41234
|
-
help: "hybrid-axi quota\nArtium manages the API key and fixed Hybrid Analysis origin. Submissions, YARA mutations
|
|
41417
|
+
help: "hybrid-axi quota\nArtium manages the API key and fixed Hybrid Analysis origin. Submissions, YARA mutations, consuming matches and the four reviewed archive downloads are off by default and selected independently; administrator-enabled operations still require native preview, --execute, exact --confirm and journal. Quick scans transmit the supplied sample or URL as read exceptions. File flags accept uploaded leaves; body-file also accepts explicit stdin (-). Use artium-axi download --help for authenticated one-shot CLI verified saving and live-malware warnings.\n"
|
|
41235
41418
|
},
|
|
41236
41419
|
suggestionFields: [
|
|
41237
41420
|
"help"
|
|
@@ -43450,7 +43633,7 @@ function managedFlagRanges(command, tool) {
|
|
|
43450
43633
|
function hostedHelp(help, tool, flags, commands2) {
|
|
43451
43634
|
if (tool === "hybrid-axi") {
|
|
43452
43635
|
help = help.split("\n").filter((line) => !/HYBRID_AXI_|SecretRefs|hybrid-axi (?:setup|config|profile)|--(?:config|profile|origin|output)\b/.test(line)).join("\n");
|
|
43453
|
-
const guidance = "Artium manages the API key and fixed origin. Submissions, YARA mutations
|
|
43636
|
+
const guidance = "Artium manages the API key and fixed origin. Submissions, YARA mutations, consuming matches and four reviewed archive downloads require independently selected administrator-enabled operations, native preview, execute, exact confirmation and journal. Quick scans transmit the supplied sample or URL. Use artium-axi download --help for authenticated one-shot CLI verified saving and live-malware warnings.";
|
|
43454
43637
|
if (commands2 && !help.includes(guidance)) help = help.trimEnd() + "\n" + guidance + "\n";
|
|
43455
43638
|
}
|
|
43456
43639
|
if (tool === "tenable-axi") {
|
|
@@ -43702,12 +43885,19 @@ async function doctor(createClient = (deadline) => new Client(process.env, void
|
|
|
43702
43885
|
try {
|
|
43703
43886
|
const parsed = toolsResponseSchema.safeParse(await client.authenticated("/v1/tools", "GET"));
|
|
43704
43887
|
if (!parsed.success) throw new ArtiumError("INVALID_RESPONSE");
|
|
43705
|
-
for (const tool of parsed.data.tools)
|
|
43706
|
-
tool.
|
|
43707
|
-
|
|
43708
|
-
|
|
43709
|
-
|
|
43710
|
-
|
|
43888
|
+
for (const tool of parsed.data.tools) {
|
|
43889
|
+
if (tool.status === "checking") {
|
|
43890
|
+
write(`pass: ${tool.name} (checking)
|
|
43891
|
+
`);
|
|
43892
|
+
continue;
|
|
43893
|
+
}
|
|
43894
|
+
line(
|
|
43895
|
+
tool.name,
|
|
43896
|
+
tool.status === "ok",
|
|
43897
|
+
tool.status,
|
|
43898
|
+
new ArtiumError(tool.status === "unconfigured" ? "CREDENTIAL_NOT_CONFIGURED" : tool.status === "error" ? "TOOL_FAILED" : tool.status === "disabled" ? "TOOL_DISABLED" : "SERVER_UNAVAILABLE")
|
|
43899
|
+
);
|
|
43900
|
+
}
|
|
43711
43901
|
} catch (error62) {
|
|
43712
43902
|
const cause = error62 instanceof ArtiumError ? error62 : new ArtiumError("INVALID_RESPONSE");
|
|
43713
43903
|
line("tools", false, cause.code, cause);
|
|
@@ -43718,7 +43908,7 @@ async function doctor(createClient = (deadline) => new Client(process.env, void
|
|
|
43718
43908
|
|
|
43719
43909
|
// src/cli/help.ts
|
|
43720
43910
|
var commands = {
|
|
43721
|
-
home: { usage: "artium-axi [tools [show <tool>]|whoami|login|passwd|logout|doctor|setup|update|<tool> <args>]", purpose: "Show your identity, roles and available tools.", flags: ["--version (default false; print the client package version and exit)"], example: "artium-axi" },
|
|
43911
|
+
home: { usage: "artium-axi [tools [show <tool>]|whoami|login|passwd|logout|doctor|setup|update|download|<tool> <args>]", purpose: "Show your identity, roles and available tools.", flags: ["--version (default false; print the client package version and exit)"], example: "artium-axi" },
|
|
43722
43912
|
setup: {
|
|
43723
43913
|
usage: "artium-axi setup --host <claude|codex|opencode|pi> [--hook] [--force]",
|
|
43724
43914
|
purpose: "Install skills for your signed-in account\u2019s granted tools and an optional Claude Code session hook.",
|
|
@@ -43743,6 +43933,24 @@ var commands = {
|
|
|
43743
43933
|
logout: { usage: "artium-axi logout", purpose: "Remove local tokens and revoke the server session.", flags: [], example: "artium-axi logout" },
|
|
43744
43934
|
doctor: { usage: "artium-axi doctor", purpose: "Check the server, certificate, token, CLI version and cached tool health.", flags: [], example: "artium-axi doctor" },
|
|
43745
43935
|
update: { usage: "artium-axi update", purpose: "Download and verify the server\u2019s client, replace this file and keep a .bak backup. npm installations must use npm install -g @knowttl/artium-axi.", flags: [], example: "artium-axi update" },
|
|
43936
|
+
download: {
|
|
43937
|
+
usage: "artium-axi download --operation <operation> --selector <hash|report-id> --type <bin|dropped-files|memory> --destination <absolute-new-path> [--transfer <id> --execute --confirm <exact-confirm>] [--password-stdin|--sso]",
|
|
43938
|
+
purpose: "Preview or explicitly save a hosted Hybrid Analysis archive. Operations remain unavailable until adapter enablement.",
|
|
43939
|
+
flags: [
|
|
43940
|
+
"--operation <hash-download-bin|report-download-bin|report-download-dropped-files|report-download-memory> (required)",
|
|
43941
|
+
"--selector <hash|report-id> (required)",
|
|
43942
|
+
"--type <bin|dropped-files|memory> (required)",
|
|
43943
|
+
"--destination <absolute-new-path> (required; Linux private 0700 directory, no symlink components or overwrite)",
|
|
43944
|
+
"--transfer <id> (required for execution; preview transfer ID)",
|
|
43945
|
+
"--execute (default false)",
|
|
43946
|
+
"--confirm <exact-confirm> (required for execution; unchanged native preview string)",
|
|
43947
|
+
"--password-stdin (default false; otherwise terminal password)",
|
|
43948
|
+
"--sso (default false; browser identity confirmation)",
|
|
43949
|
+
transferWarning,
|
|
43950
|
+
`Limit ${transferLimits.artifactBytes / 1024 ** 2} MiB; ${transferLimits.reservationsPerUser} reservation per user, ${transferLimits.reservations} globally, ${transferLimits.reservedBytes / 1024 ** 3} GiB total artifact capacity; absolute expiry ${transferLimits.lifetimeMs / 6e4} minutes after preview preparation, including generation and retrieval. No resume or automatic retry. Missing receipt is unconfirmed.`
|
|
43951
|
+
],
|
|
43952
|
+
example: "artium-axi download --operation report-download-bin --selector synthetic-report --type bin --destination /controlled/artifacts/new-archive.bin --sso"
|
|
43953
|
+
},
|
|
43746
43954
|
"falcon-axi": {
|
|
43747
43955
|
usage: "artium-axi falcon-axi [<args>]",
|
|
43748
43956
|
purpose: "Run Falcon commands with your managed credentials.",
|
|
@@ -43782,7 +43990,7 @@ var commands = {
|
|
|
43782
43990
|
"hybrid-axi": {
|
|
43783
43991
|
usage: "artium-axi hybrid-axi [<args>]",
|
|
43784
43992
|
purpose: "Investigate Hybrid Analysis with a managed API key.",
|
|
43785
|
-
flags: ["Submissions, YARA mutations
|
|
43993
|
+
flags: ["Submissions, YARA mutations, consuming matches and four reviewed archive downloads are off by default and selected independently by an administrator. Native preview, --execute and exact --confirm remain required. Use artium-axi download --help for authenticated CLI verified saving.", transferWarning],
|
|
43786
43994
|
example: "artium-axi hybrid-axi quota"
|
|
43787
43995
|
},
|
|
43788
43996
|
"az-axi": {
|
|
@@ -43855,7 +44063,7 @@ function validateArguments(args, prefixHelp) {
|
|
|
43855
44063
|
start = 2;
|
|
43856
44064
|
}
|
|
43857
44065
|
const entry = commands[level];
|
|
43858
|
-
const validFlags = ["--help", ...entry.flags.map((flag) => flag.split(" ")[0])];
|
|
44066
|
+
const validFlags = ["--help", ...entry.flags.filter((flag) => flag.startsWith("--")).map((flag) => flag.split(" ")[0])];
|
|
43859
44067
|
const choices = `Valid flags for ${level}: ${validFlags.join(", ")}.` + (level === "tools" ? " Valid subcommands: show <tool>." : level === "tools show" ? ` Valid tools: ${toolSchema.options.join(", ")}.` : "");
|
|
43860
44068
|
let tool = false;
|
|
43861
44069
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -43867,8 +44075,9 @@ function validateArguments(args, prefixHelp) {
|
|
|
43867
44075
|
} else {
|
|
43868
44076
|
if (!validFlags.includes(token) || seen.has(token)) fail(token, level, choices);
|
|
43869
44077
|
seen.add(token);
|
|
43870
|
-
if (token === "--username" || token === "--host") {
|
|
44078
|
+
if (token === "--username" || token === "--host" || command === "download" && ["--operation", "--selector", "--type", "--destination", "--transfer", "--confirm"].includes(token)) {
|
|
43871
44079
|
const value = args[index + 1];
|
|
44080
|
+
if (!value && command === "download") fail(value, `${level} ${token}`, `Supply a value for ${token}. Use ${entry.usage}. ${choices}`);
|
|
43872
44081
|
if (!value) fail(value, `${level} ${token}`, `Supply ${token} ${token === "--host" ? "<claude|codex|opencode|pi>" : "<username>"}. ${choices}`);
|
|
43873
44082
|
if (token === "--host" && !["claude", "codex", "opencode", "pi"].includes(value)) fail(value, `${level} --host`, `Valid hosts: claude, codex, opencode, pi. Use ${entry.usage}.`);
|
|
43874
44083
|
index++;
|
|
@@ -43878,6 +44087,7 @@ function validateArguments(args, prefixHelp) {
|
|
|
43878
44087
|
if (level === "tools show" && !tool && !seen.has("--help") && !prefixHelp) fail(void 0, level, choices);
|
|
43879
44088
|
}
|
|
43880
44089
|
function helpCommand(args, prefixHelp = false) {
|
|
44090
|
+
if (args[0] === "download" && args.includes("--help")) return "download";
|
|
43881
44091
|
if (args[0] === "setup" && args.includes("--help")) {
|
|
43882
44092
|
const path2 = args.filter((arg) => arg !== "--help");
|
|
43883
44093
|
for (let index = 1; index < path2.length; index++) {
|
|
@@ -44281,6 +44491,10 @@ var ManagedSession = class _ManagedSession {
|
|
|
44281
44491
|
checkServer(serverUrl) {
|
|
44282
44492
|
if (this.session && this.session.serverUrl.replace(/\/$/, "") !== serverUrl) throw new ArtiumError("SESSION_SERVER_MISMATCH");
|
|
44283
44493
|
}
|
|
44494
|
+
async once(request) {
|
|
44495
|
+
if (!this.session) throw new ArtiumError("TOKEN_INVALID");
|
|
44496
|
+
return request(this.session.accessToken);
|
|
44497
|
+
}
|
|
44284
44498
|
async authenticated(request, refresh) {
|
|
44285
44499
|
if (!this.session) throw new ArtiumError("TOKEN_INVALID");
|
|
44286
44500
|
try {
|
|
@@ -44303,9 +44517,9 @@ var ManagedSession = class _ManagedSession {
|
|
|
44303
44517
|
if (session.status === "authenticated") this.protect(session.accessToken, session.refreshToken);
|
|
44304
44518
|
else this.protect(session.passwordChangeToken);
|
|
44305
44519
|
try {
|
|
44306
|
-
await new Promise((
|
|
44520
|
+
await new Promise((resolve5, reject) => {
|
|
44307
44521
|
this.channel.write(`${JSON.stringify({ serverUrl, ...session })}
|
|
44308
|
-
`, (error62) => error62 ? reject(error62) :
|
|
44522
|
+
`, (error62) => error62 ? reject(error62) : resolve5());
|
|
44309
44523
|
});
|
|
44310
44524
|
} catch {
|
|
44311
44525
|
throw new ArtiumError(
|
|
@@ -44318,6 +44532,12 @@ var ManagedSession = class _ManagedSession {
|
|
|
44318
44532
|
write(stream, text2) {
|
|
44319
44533
|
this.output[stream] += text2;
|
|
44320
44534
|
}
|
|
44535
|
+
// Identity approval and pre-execution notices must reach the caller before
|
|
44536
|
+
// the pending operation. Tool output retains the buffered flush contract.
|
|
44537
|
+
notice(text2) {
|
|
44538
|
+
if (new SecretScanner(this.secrets).scan("stdout", Buffer.from(text2))) throw new ArtiumError("SECRET_IN_OUTPUT");
|
|
44539
|
+
process.stdout.write(text2);
|
|
44540
|
+
}
|
|
44321
44541
|
flush() {
|
|
44322
44542
|
this.channel.destroy();
|
|
44323
44543
|
const scanner = new SecretScanner(this.secrets);
|
|
@@ -44359,6 +44579,255 @@ async function update() {
|
|
|
44359
44579
|
return "status: updated\nnext: artium-axi --version\n";
|
|
44360
44580
|
}
|
|
44361
44581
|
|
|
44582
|
+
// src/cli/download.ts
|
|
44583
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
44584
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
44585
|
+
|
|
44586
|
+
// src/cli/artifact-save.ts
|
|
44587
|
+
import { createHash as createHash3, randomUUID as randomUUID4 } from "node:crypto";
|
|
44588
|
+
import { constants } from "node:fs";
|
|
44589
|
+
import { link, open as open2, unlink } from "node:fs/promises";
|
|
44590
|
+
import { basename as basename2, dirname as dirname4, isAbsolute, resolve as resolve3 } from "node:path";
|
|
44591
|
+
async function privateParent(destination) {
|
|
44592
|
+
let parent = await open2("/", constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
|
|
44593
|
+
try {
|
|
44594
|
+
for (const component of dirname4(destination).split("/").filter(Boolean)) {
|
|
44595
|
+
const child = await open2(`/proc/self/fd/${parent.fd}/${component}`, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
|
|
44596
|
+
await parent.close();
|
|
44597
|
+
parent = child;
|
|
44598
|
+
}
|
|
44599
|
+
const info = await parent.stat();
|
|
44600
|
+
if (info.uid !== process.getuid?.() || (info.mode & 511) !== 448) throw new ArtiumError("ARTIFACT_SAVE_FAILED");
|
|
44601
|
+
return parent;
|
|
44602
|
+
} catch (error62) {
|
|
44603
|
+
await parent.close();
|
|
44604
|
+
throw error62;
|
|
44605
|
+
}
|
|
44606
|
+
}
|
|
44607
|
+
async function artifactDestination(destination) {
|
|
44608
|
+
if (process.platform !== "linux" || !isAbsolute(destination) || resolve3(destination) !== destination || basename2(destination) === "") {
|
|
44609
|
+
throw new ArtiumError("ARTIFACT_SAVE_FAILED");
|
|
44610
|
+
}
|
|
44611
|
+
let parent;
|
|
44612
|
+
try {
|
|
44613
|
+
parent = await privateParent(destination);
|
|
44614
|
+
} catch {
|
|
44615
|
+
throw new ArtiumError("ARTIFACT_SAVE_FAILED");
|
|
44616
|
+
}
|
|
44617
|
+
let temporary;
|
|
44618
|
+
let file2;
|
|
44619
|
+
let closed = false;
|
|
44620
|
+
try {
|
|
44621
|
+
const info = await parent.stat();
|
|
44622
|
+
const target = `/proc/self/fd/${parent.fd}/${basename2(destination)}`;
|
|
44623
|
+
try {
|
|
44624
|
+
const existing = await open2(target, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
44625
|
+
await existing.close();
|
|
44626
|
+
throw new ArtiumError("ARTIFACT_SAVE_FAILED");
|
|
44627
|
+
} catch (error62) {
|
|
44628
|
+
if (error62.code !== "ENOENT") throw error62;
|
|
44629
|
+
}
|
|
44630
|
+
return {
|
|
44631
|
+
async save(response, archive, signal) {
|
|
44632
|
+
signal.throwIfAborted();
|
|
44633
|
+
if (response.headers["content-type"] !== "application/octet-stream" || response.headers["content-length"] !== String(archive.archiveBytes) || response.headers["x-artium-archive-sha256"] !== archive.archiveSha256 || response.headers["content-encoding"] || response.headers["content-range"]) throw new ArtiumError("INVALID_RESPONSE");
|
|
44634
|
+
temporary = `/proc/self/fd/${parent.fd}/.artium-${randomUUID4()}.tmp`;
|
|
44635
|
+
file2 = await open2(temporary, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 384);
|
|
44636
|
+
const hash2 = createHash3("sha256");
|
|
44637
|
+
let size = 0;
|
|
44638
|
+
for await (const chunk of response) {
|
|
44639
|
+
signal.throwIfAborted();
|
|
44640
|
+
size += chunk.length;
|
|
44641
|
+
if (size > archive.archiveBytes) throw new ArtiumError("INVALID_RESPONSE");
|
|
44642
|
+
hash2.update(chunk);
|
|
44643
|
+
await file2.writeFile(chunk);
|
|
44644
|
+
}
|
|
44645
|
+
if (size !== archive.archiveBytes || hash2.digest("hex") !== archive.archiveSha256) throw new ArtiumError("INVALID_RESPONSE");
|
|
44646
|
+
await file2.sync();
|
|
44647
|
+
await file2.close();
|
|
44648
|
+
file2 = void 0;
|
|
44649
|
+
const current = await privateParent(destination);
|
|
44650
|
+
try {
|
|
44651
|
+
const actual = await current.stat();
|
|
44652
|
+
if (info.dev !== actual.dev || info.ino !== actual.ino) throw new ArtiumError("ARTIFACT_SAVE_FAILED");
|
|
44653
|
+
} finally {
|
|
44654
|
+
await current.close();
|
|
44655
|
+
}
|
|
44656
|
+
signal.throwIfAborted();
|
|
44657
|
+
await link(temporary, target);
|
|
44658
|
+
},
|
|
44659
|
+
async close() {
|
|
44660
|
+
if (closed) return;
|
|
44661
|
+
closed = true;
|
|
44662
|
+
try {
|
|
44663
|
+
await file2?.close();
|
|
44664
|
+
} finally {
|
|
44665
|
+
try {
|
|
44666
|
+
if (temporary) await unlink(temporary);
|
|
44667
|
+
} finally {
|
|
44668
|
+
await parent.close();
|
|
44669
|
+
}
|
|
44670
|
+
}
|
|
44671
|
+
}
|
|
44672
|
+
};
|
|
44673
|
+
} catch {
|
|
44674
|
+
await parent.close();
|
|
44675
|
+
throw new ArtiumError("ARTIFACT_SAVE_FAILED");
|
|
44676
|
+
}
|
|
44677
|
+
}
|
|
44678
|
+
|
|
44679
|
+
// src/cli/download.ts
|
|
44680
|
+
async function download(args, output3, managed2) {
|
|
44681
|
+
const announce = (value) => {
|
|
44682
|
+
const text2 = encode3(value) + "\n";
|
|
44683
|
+
if (managed2) managed2.notice(text2);
|
|
44684
|
+
else output3(text2);
|
|
44685
|
+
};
|
|
44686
|
+
announce({ warning: transferWarning });
|
|
44687
|
+
const flags = /* @__PURE__ */ new Map();
|
|
44688
|
+
for (let index = 0; index < args.length; index++) {
|
|
44689
|
+
const name = args[index];
|
|
44690
|
+
if (["--execute", "--password-stdin", "--sso"].includes(name)) flags.set(name, "true");
|
|
44691
|
+
else flags.set(name, args[++index]);
|
|
44692
|
+
}
|
|
44693
|
+
const destination = flags.get("--destination") ?? "";
|
|
44694
|
+
const input2 = transferPrepareSchema.safeParse({
|
|
44695
|
+
selection: {
|
|
44696
|
+
operation: flags.get("--operation"),
|
|
44697
|
+
selector: flags.get("--selector"),
|
|
44698
|
+
type: flags.get("--type")
|
|
44699
|
+
},
|
|
44700
|
+
targetDigest: createHash4("sha256").update(destination).digest("hex")
|
|
44701
|
+
});
|
|
44702
|
+
if (!input2.success || !destination || flags.has("--sso") && flags.has("--password-stdin")) throw new ArtiumError("INVALID_INPUT");
|
|
44703
|
+
const execute = flags.has("--execute");
|
|
44704
|
+
if (execute !== flags.has("--transfer") || execute !== flags.has("--confirm")) throw new ArtiumError("INVALID_INPUT");
|
|
44705
|
+
let execution;
|
|
44706
|
+
if (execute) {
|
|
44707
|
+
const parsed = transferExecuteSchema.safeParse({
|
|
44708
|
+
...input2.data,
|
|
44709
|
+
transferId: flags.get("--transfer"),
|
|
44710
|
+
execute: true,
|
|
44711
|
+
confirm: flags.get("--confirm")
|
|
44712
|
+
});
|
|
44713
|
+
if (!parsed.success) throw new ArtiumError("INVALID_INPUT");
|
|
44714
|
+
execution = parsed.data;
|
|
44715
|
+
}
|
|
44716
|
+
const client = new Client(process.env, managed2);
|
|
44717
|
+
const local = await artifactDestination(destination);
|
|
44718
|
+
const interrupt = () => client.cancel();
|
|
44719
|
+
process.once("SIGINT", interrupt);
|
|
44720
|
+
process.once("SIGTERM", interrupt);
|
|
44721
|
+
try {
|
|
44722
|
+
await client.transferSession(async (token) => {
|
|
44723
|
+
let proof;
|
|
44724
|
+
if (flags.has("--sso")) {
|
|
44725
|
+
const start = deviceStartResponseSchema.parse(await client.request("/v1/auth/reauth", "POST", {}, token));
|
|
44726
|
+
if (start.verificationUri !== client.serverUrl + "/device") throw new ArtiumError("INVALID_RESPONSE");
|
|
44727
|
+
managed2?.protect(start.deviceCode);
|
|
44728
|
+
announce({ status: "approval-required", open: start.verificationUri, code: start.userCode, expiresAt: start.expiresAt });
|
|
44729
|
+
while (Date.now() < start.expiresAt) {
|
|
44730
|
+
try {
|
|
44731
|
+
proof = ssoReauthResponseSchema.parse(await client.request("/v1/auth/reauth", "POST", { deviceCode: start.deviceCode }, token));
|
|
44732
|
+
if (reauthResponseSchema.safeParse(proof).success) break;
|
|
44733
|
+
} catch (error62) {
|
|
44734
|
+
if (!(error62 instanceof ArtiumError) || error62.code !== "DEVICE_POLL_LIMITED") throw error62;
|
|
44735
|
+
}
|
|
44736
|
+
await delay(Math.min(start.intervalMs, Math.max(0, start.expiresAt - Date.now())));
|
|
44737
|
+
}
|
|
44738
|
+
} else {
|
|
44739
|
+
const password = await readPassword(flags.has("--password-stdin"));
|
|
44740
|
+
managed2?.protect(password);
|
|
44741
|
+
proof = await client.request("/v1/auth/reauth", "POST", { password }, token);
|
|
44742
|
+
}
|
|
44743
|
+
const { ticket } = reauthResponseSchema.parse(proof);
|
|
44744
|
+
managed2?.protect(ticket);
|
|
44745
|
+
const request = (path, body) => client.request(
|
|
44746
|
+
path,
|
|
44747
|
+
"POST",
|
|
44748
|
+
body,
|
|
44749
|
+
token,
|
|
44750
|
+
false,
|
|
44751
|
+
{ "x-artium-reauth-ticket": ticket }
|
|
44752
|
+
);
|
|
44753
|
+
if (!execution) {
|
|
44754
|
+
const preview = transferPreviewSchema.parse(await request(transferRoutes.prepare.path, input2.data));
|
|
44755
|
+
if (preview.targetDigest !== input2.data.targetDigest) throw new ArtiumError("INVALID_RESPONSE");
|
|
44756
|
+
announce({
|
|
44757
|
+
...input2.data.selection,
|
|
44758
|
+
destination,
|
|
44759
|
+
limits: transferLimits,
|
|
44760
|
+
...preview,
|
|
44761
|
+
saveOutcome: "unconfirmed",
|
|
44762
|
+
next: "Repeat this exact selection and destination with --transfer <transferId> --execute --confirm <exact confirm> and recent identity confirmation. No automatic retries."
|
|
44763
|
+
});
|
|
44764
|
+
return;
|
|
44765
|
+
}
|
|
44766
|
+
const retrieval = { ...input2.data, transferId: execution.transferId };
|
|
44767
|
+
let archive;
|
|
44768
|
+
let saved = false;
|
|
44769
|
+
let receipt = "unconfirmed";
|
|
44770
|
+
try {
|
|
44771
|
+
const pending = transferStatusSchema.parse(await request(transferRoutes.status.path, retrieval));
|
|
44772
|
+
if (pending.transferId !== execution.transferId || pending.outcome !== "preview" || pending.archive !== null) throw new ArtiumError("INVALID_RESPONSE");
|
|
44773
|
+
announce({
|
|
44774
|
+
...input2.data.selection,
|
|
44775
|
+
destination,
|
|
44776
|
+
limits: transferLimits,
|
|
44777
|
+
expiresAt: pending.expiresAt,
|
|
44778
|
+
saveOutcome: "unconfirmed"
|
|
44779
|
+
});
|
|
44780
|
+
const status = transferStatusSchema.parse(await request(transferRoutes.execute.path, execution));
|
|
44781
|
+
if (status.transferId !== execution.transferId || status.expiresAt !== pending.expiresAt || status.outcome !== "ready" || !status.archive) throw new ArtiumError("INVALID_RESPONSE");
|
|
44782
|
+
archive = status.archive;
|
|
44783
|
+
await client.artifact(
|
|
44784
|
+
transferRoutes.retrieve.path,
|
|
44785
|
+
retrieval,
|
|
44786
|
+
token,
|
|
44787
|
+
ticket,
|
|
44788
|
+
status.expiresAt,
|
|
44789
|
+
(response, signal) => local.save(response, archive, signal)
|
|
44790
|
+
);
|
|
44791
|
+
saved = true;
|
|
44792
|
+
await local.close();
|
|
44793
|
+
try {
|
|
44794
|
+
await request(transferRoutes.receipt.path, { ...retrieval, archive, saveOutcome: "verified-save" });
|
|
44795
|
+
receipt = "confirmed";
|
|
44796
|
+
} catch {
|
|
44797
|
+
}
|
|
44798
|
+
} catch (error62) {
|
|
44799
|
+
if (archive && !saved) {
|
|
44800
|
+
try {
|
|
44801
|
+
await request(transferRoutes.receipt.path, { ...retrieval, archive, saveOutcome: "save-failed" });
|
|
44802
|
+
} catch {
|
|
44803
|
+
}
|
|
44804
|
+
}
|
|
44805
|
+
try {
|
|
44806
|
+
await request(transferRoutes.cancel.path, retrieval);
|
|
44807
|
+
} catch {
|
|
44808
|
+
}
|
|
44809
|
+
throw error62;
|
|
44810
|
+
} finally {
|
|
44811
|
+
output3(encode3({
|
|
44812
|
+
...input2.data.selection,
|
|
44813
|
+
destination,
|
|
44814
|
+
archiveBytes: archive?.archiveBytes ?? null,
|
|
44815
|
+
archiveSha256: archive?.archiveSha256 ?? null,
|
|
44816
|
+
saveOutcome: saved ? "verified-save" : "save-failed",
|
|
44817
|
+
receipt
|
|
44818
|
+
}) + "\n");
|
|
44819
|
+
}
|
|
44820
|
+
});
|
|
44821
|
+
} catch (error62) {
|
|
44822
|
+
if (error62 instanceof ZodError) throw new ArtiumError("INVALID_RESPONSE");
|
|
44823
|
+
throw error62;
|
|
44824
|
+
} finally {
|
|
44825
|
+
process.off("SIGINT", interrupt);
|
|
44826
|
+
process.off("SIGTERM", interrupt);
|
|
44827
|
+
await local.close();
|
|
44828
|
+
}
|
|
44829
|
+
}
|
|
44830
|
+
|
|
44362
44831
|
// src/cli/main.ts
|
|
44363
44832
|
var managed;
|
|
44364
44833
|
for (const stream of [process.stdout, process.stderr]) {
|
|
@@ -44408,6 +44877,10 @@ async function main(args) {
|
|
|
44408
44877
|
output2("stdout", await update());
|
|
44409
44878
|
return;
|
|
44410
44879
|
}
|
|
44880
|
+
if (command === "download") {
|
|
44881
|
+
await download(args.slice(1), (text2) => output2("stdout", text2), managed);
|
|
44882
|
+
return;
|
|
44883
|
+
}
|
|
44411
44884
|
if (command === "setup") {
|
|
44412
44885
|
output2("stdout", await setup(args.slice(1)));
|
|
44413
44886
|
return;
|
|
@@ -44527,9 +45000,9 @@ remoteRevocation: ${verified ? "verified" : "unverified"}
|
|
|
44527
45000
|
if (home) {
|
|
44528
45001
|
const tools = toolsResponseSchema.safeParse(await client.authenticated("/v1/tools", "GET", void 0, homeSession));
|
|
44529
45002
|
if (!tools.success) throw new ArtiumError("INVALID_RESPONSE");
|
|
44530
|
-
const executable =
|
|
45003
|
+
const executable = resolve4(process.argv[1]);
|
|
44531
45004
|
const local = relative(homedir3(), executable);
|
|
44532
|
-
const bin = local && !
|
|
45005
|
+
const bin = local && !isAbsolute2(local) && !local.startsWith(`..${sep}`) && local !== ".." ? `~${sep}${local}` : executable;
|
|
44533
45006
|
view = `bin: ${JSON.stringify(bin)}
|
|
44534
45007
|
description: Run managed upstream tools through one local sign-in.
|
|
44535
45008
|
${view}${toolsView(tools.data.tools)}`;
|
|
@@ -44556,16 +45029,20 @@ ${view}${toolsView(tools.data.tools)}`;
|
|
|
44556
45029
|
}
|
|
44557
45030
|
}
|
|
44558
45031
|
function printError(response) {
|
|
45032
|
+
if (process.argv[2] === "download" || process.argv[2] === "--managed-session" && process.argv[3] === "download") {
|
|
45033
|
+
output2("stdout", `warning: ${JSON.stringify(transferWarning)}
|
|
45034
|
+
`);
|
|
45035
|
+
}
|
|
44559
45036
|
output2("stdout", `error:
|
|
44560
45037
|
${Object.entries(response.error).map(([key, value]) => ` ${key}: ${JSON.stringify(value)}`).join("\n")}
|
|
44561
45038
|
`);
|
|
44562
45039
|
process.exitCode = errors[response.error.code].exitCode;
|
|
44563
45040
|
}
|
|
44564
|
-
main(process.argv.slice(2)).catch((error62) => printError(errorResponse(error62 instanceof ArtiumError ? error62 : new ArtiumError("INTERNAL_ERROR"),
|
|
45041
|
+
main(process.argv.slice(2)).catch((error62) => printError(errorResponse(error62 instanceof ArtiumError ? error62 : new ArtiumError("INTERNAL_ERROR"), randomUUID5()))).then(() => {
|
|
44565
45042
|
try {
|
|
44566
45043
|
managed?.flush();
|
|
44567
45044
|
} catch {
|
|
44568
45045
|
managed = void 0;
|
|
44569
|
-
printError(errorResponse(new ArtiumError("SECRET_IN_OUTPUT"),
|
|
45046
|
+
printError(errorResponse(new ArtiumError("SECRET_IN_OUTPUT"), randomUUID5()));
|
|
44570
45047
|
}
|
|
44571
45048
|
});
|