@knowttl/artium-axi 0.0.14 → 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 +519 -40
- 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",
|
|
@@ -20117,7 +20230,7 @@ var diagnosticSchema = external_exports.strictObject({
|
|
|
20117
20230
|
credentialRevision: external_exports.number().int().nullable().optional()
|
|
20118
20231
|
});
|
|
20119
20232
|
var healthSchema = external_exports.strictObject({
|
|
20120
|
-
status: external_exports.enum(["unconfigured", "unknown", "ok", "error", "disabled"]),
|
|
20233
|
+
status: external_exports.enum(["unconfigured", "unknown", "checking", "ok", "error", "disabled"]),
|
|
20121
20234
|
diagnostic: diagnosticSchema.optional(),
|
|
20122
20235
|
readOnly: external_exports.boolean().default(true),
|
|
20123
20236
|
checkedAt: external_exports.number().int().nullable(),
|
|
@@ -20575,7 +20688,7 @@ var toolsResponseSchema = external_exports.strictObject({
|
|
|
20575
20688
|
name: toolSchema,
|
|
20576
20689
|
version: external_exports.string(),
|
|
20577
20690
|
readOnly: external_exports.boolean().default(true),
|
|
20578
|
-
status: external_exports.enum(["unconfigured", "unknown", "ok", "error", "disabled"])
|
|
20691
|
+
status: external_exports.enum(["unconfigured", "unknown", "checking", "ok", "error", "disabled"])
|
|
20579
20692
|
}))
|
|
20580
20693
|
});
|
|
20581
20694
|
var inputFileSchema = external_exports.strictObject({
|
|
@@ -20632,7 +20745,7 @@ import { resolve } from "node:path";
|
|
|
20632
20745
|
// package.json
|
|
20633
20746
|
var package_default = {
|
|
20634
20747
|
name: "artium-axi",
|
|
20635
|
-
version: "0.0.
|
|
20748
|
+
version: "0.0.15",
|
|
20636
20749
|
private: true,
|
|
20637
20750
|
license: "MIT",
|
|
20638
20751
|
type: "module",
|
|
@@ -20821,8 +20934,8 @@ var SessionStore = class {
|
|
|
20821
20934
|
while (true) {
|
|
20822
20935
|
const watcher = watch(this.directory);
|
|
20823
20936
|
let timer;
|
|
20824
|
-
const changed = new Promise((
|
|
20825
|
-
watcher.once("change", () =>
|
|
20937
|
+
const changed = new Promise((resolve5, reject) => {
|
|
20938
|
+
watcher.once("change", () => resolve5());
|
|
20826
20939
|
watcher.once("error", () => reject(new ArtiumError("SESSION_STORAGE_UNAVAILABLE")));
|
|
20827
20940
|
timer = setTimeout(() => reject(new ArtiumError("SESSION_BUSY")), Math.max(0, deadline - Date.now()));
|
|
20828
20941
|
});
|
|
@@ -20870,6 +20983,10 @@ var Client = class {
|
|
|
20870
20983
|
}
|
|
20871
20984
|
managed;
|
|
20872
20985
|
requestDeadlineMs;
|
|
20986
|
+
cancellation = new AbortController();
|
|
20987
|
+
cancel() {
|
|
20988
|
+
this.cancellation.abort();
|
|
20989
|
+
}
|
|
20873
20990
|
serverUrl;
|
|
20874
20991
|
serverUrlSource;
|
|
20875
20992
|
caFile;
|
|
@@ -20879,7 +20996,7 @@ var Client = class {
|
|
|
20879
20996
|
return this.storedSessions;
|
|
20880
20997
|
}
|
|
20881
20998
|
certificateVerified = false;
|
|
20882
|
-
async request(path, method, input2, token, bytes = false) {
|
|
20999
|
+
async request(path, method, input2, token, bytes = false, headers = {}) {
|
|
20883
21000
|
this.certificateVerified = false;
|
|
20884
21001
|
let ca;
|
|
20885
21002
|
try {
|
|
@@ -20891,13 +21008,13 @@ var Client = class {
|
|
|
20891
21008
|
const controller = new AbortController();
|
|
20892
21009
|
const deadline = setTimeout(() => controller.abort(), this.requestDeadlineMs);
|
|
20893
21010
|
const url2 = new URL(`${this.serverUrl}${path}`);
|
|
20894
|
-
const result = await new Promise((
|
|
21011
|
+
const result = await new Promise((resolve5, reject) => {
|
|
20895
21012
|
const request = (url2.protocol === "https:" ? httpsRequest : httpRequest)(url2, {
|
|
20896
21013
|
method,
|
|
20897
21014
|
ca,
|
|
20898
21015
|
rejectUnauthorized: true,
|
|
20899
|
-
signal: controller.signal,
|
|
20900
|
-
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" } : {} }
|
|
20901
21018
|
}, (response) => {
|
|
20902
21019
|
if (response.statusCode >= 300 && response.statusCode < 400) {
|
|
20903
21020
|
response.destroy();
|
|
@@ -20909,7 +21026,7 @@ var Client = class {
|
|
|
20909
21026
|
response.on("error", () => reject(new ArtiumError("SERVER_UNAVAILABLE")));
|
|
20910
21027
|
response.on("end", () => {
|
|
20911
21028
|
try {
|
|
20912
|
-
|
|
21029
|
+
resolve5({
|
|
20913
21030
|
status: response.statusCode,
|
|
20914
21031
|
body: bytes ? Buffer.concat(chunks) : response.statusCode === 204 ? void 0 : JSON.parse(Buffer.concat(chunks).toString("utf8")),
|
|
20915
21032
|
serverVersion: typeof response.headers["x-artium-cli-version"] === "string" ? response.headers["x-artium-cli-version"] : void 0
|
|
@@ -20973,6 +21090,70 @@ var Client = class {
|
|
|
20973
21090
|
if (previous) this.reload(previous);
|
|
20974
21091
|
return result;
|
|
20975
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
|
+
}
|
|
20976
21157
|
// The caller holds the session lock, including the reload after acquisition.
|
|
20977
21158
|
async refresh(previous) {
|
|
20978
21159
|
const current = this.reload(previous);
|
|
@@ -21021,13 +21202,13 @@ async function readPassword(stdin, environmentVariable) {
|
|
|
21021
21202
|
process.stdin.setRawMode(true);
|
|
21022
21203
|
process.stdin.resume();
|
|
21023
21204
|
try {
|
|
21024
|
-
return await new Promise((
|
|
21205
|
+
return await new Promise((resolve5, reject) => {
|
|
21025
21206
|
let password = "";
|
|
21026
21207
|
const keypress = (text2, key) => {
|
|
21027
21208
|
if (key.name === "return" || key.ctrl && key.name === "c") {
|
|
21028
21209
|
process.stdin.off("keypress", keypress);
|
|
21029
21210
|
if (key.ctrl) reject(new ArtiumError("CANCELLED"));
|
|
21030
|
-
else
|
|
21211
|
+
else resolve5(password);
|
|
21031
21212
|
} else if (key.name === "backspace") password = Array.from(password).slice(0, -1).join("");
|
|
21032
21213
|
else if (text2 && !key.ctrl && !new RegExp("\\p{Cc}", "u").test(text2)) password += text2;
|
|
21033
21214
|
};
|
|
@@ -41174,7 +41355,11 @@ var hybrid_axi_default = {
|
|
|
41174
41355
|
nativeWriteSettings: {
|
|
41175
41356
|
supportedOperations: [
|
|
41176
41357
|
"convertQuickScan",
|
|
41358
|
+
"hash-download-bin",
|
|
41177
41359
|
"huntingMatches",
|
|
41360
|
+
"report-download-bin",
|
|
41361
|
+
"report-download-dropped-files",
|
|
41362
|
+
"report-download-memory",
|
|
41178
41363
|
"submitDroppedFile",
|
|
41179
41364
|
"submitFile",
|
|
41180
41365
|
"submitUrl",
|
|
@@ -41206,7 +41391,7 @@ var hybrid_axi_default = {
|
|
|
41206
41391
|
inputs: {
|
|
41207
41392
|
stdin: "body-file - only",
|
|
41208
41393
|
files: "uploaded leaves only",
|
|
41209
|
-
downloads: "
|
|
41394
|
+
downloads: "artium-axi download only; authenticated one-shot CLI verified save"
|
|
41210
41395
|
},
|
|
41211
41396
|
nativeWrites: "native preview, operation allow-list, execute, exact confirm and durable journal"
|
|
41212
41397
|
},
|
|
@@ -41229,7 +41414,7 @@ var hybrid_axi_default = {
|
|
|
41229
41414
|
example: [
|
|
41230
41415
|
"quota"
|
|
41231
41416
|
],
|
|
41232
|
-
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"
|
|
41233
41418
|
},
|
|
41234
41419
|
suggestionFields: [
|
|
41235
41420
|
"help"
|
|
@@ -43448,7 +43633,7 @@ function managedFlagRanges(command, tool) {
|
|
|
43448
43633
|
function hostedHelp(help, tool, flags, commands2) {
|
|
43449
43634
|
if (tool === "hybrid-axi") {
|
|
43450
43635
|
help = help.split("\n").filter((line) => !/HYBRID_AXI_|SecretRefs|hybrid-axi (?:setup|config|profile)|--(?:config|profile|origin|output)\b/.test(line)).join("\n");
|
|
43451
|
-
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.";
|
|
43452
43637
|
if (commands2 && !help.includes(guidance)) help = help.trimEnd() + "\n" + guidance + "\n";
|
|
43453
43638
|
}
|
|
43454
43639
|
if (tool === "tenable-axi") {
|
|
@@ -43700,12 +43885,19 @@ async function doctor(createClient = (deadline) => new Client(process.env, void
|
|
|
43700
43885
|
try {
|
|
43701
43886
|
const parsed = toolsResponseSchema.safeParse(await client.authenticated("/v1/tools", "GET"));
|
|
43702
43887
|
if (!parsed.success) throw new ArtiumError("INVALID_RESPONSE");
|
|
43703
|
-
for (const tool of parsed.data.tools)
|
|
43704
|
-
tool.
|
|
43705
|
-
|
|
43706
|
-
|
|
43707
|
-
|
|
43708
|
-
|
|
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
|
+
}
|
|
43709
43901
|
} catch (error62) {
|
|
43710
43902
|
const cause = error62 instanceof ArtiumError ? error62 : new ArtiumError("INVALID_RESPONSE");
|
|
43711
43903
|
line("tools", false, cause.code, cause);
|
|
@@ -43716,7 +43908,7 @@ async function doctor(createClient = (deadline) => new Client(process.env, void
|
|
|
43716
43908
|
|
|
43717
43909
|
// src/cli/help.ts
|
|
43718
43910
|
var commands = {
|
|
43719
|
-
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" },
|
|
43720
43912
|
setup: {
|
|
43721
43913
|
usage: "artium-axi setup --host <claude|codex|opencode|pi> [--hook] [--force]",
|
|
43722
43914
|
purpose: "Install skills for your signed-in account\u2019s granted tools and an optional Claude Code session hook.",
|
|
@@ -43741,6 +43933,24 @@ var commands = {
|
|
|
43741
43933
|
logout: { usage: "artium-axi logout", purpose: "Remove local tokens and revoke the server session.", flags: [], example: "artium-axi logout" },
|
|
43742
43934
|
doctor: { usage: "artium-axi doctor", purpose: "Check the server, certificate, token, CLI version and cached tool health.", flags: [], example: "artium-axi doctor" },
|
|
43743
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
|
+
},
|
|
43744
43954
|
"falcon-axi": {
|
|
43745
43955
|
usage: "artium-axi falcon-axi [<args>]",
|
|
43746
43956
|
purpose: "Run Falcon commands with your managed credentials.",
|
|
@@ -43780,7 +43990,7 @@ var commands = {
|
|
|
43780
43990
|
"hybrid-axi": {
|
|
43781
43991
|
usage: "artium-axi hybrid-axi [<args>]",
|
|
43782
43992
|
purpose: "Investigate Hybrid Analysis with a managed API key.",
|
|
43783
|
-
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],
|
|
43784
43994
|
example: "artium-axi hybrid-axi quota"
|
|
43785
43995
|
},
|
|
43786
43996
|
"az-axi": {
|
|
@@ -43853,7 +44063,7 @@ function validateArguments(args, prefixHelp) {
|
|
|
43853
44063
|
start = 2;
|
|
43854
44064
|
}
|
|
43855
44065
|
const entry = commands[level];
|
|
43856
|
-
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])];
|
|
43857
44067
|
const choices = `Valid flags for ${level}: ${validFlags.join(", ")}.` + (level === "tools" ? " Valid subcommands: show <tool>." : level === "tools show" ? ` Valid tools: ${toolSchema.options.join(", ")}.` : "");
|
|
43858
44068
|
let tool = false;
|
|
43859
44069
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -43865,8 +44075,9 @@ function validateArguments(args, prefixHelp) {
|
|
|
43865
44075
|
} else {
|
|
43866
44076
|
if (!validFlags.includes(token) || seen.has(token)) fail(token, level, choices);
|
|
43867
44077
|
seen.add(token);
|
|
43868
|
-
if (token === "--username" || token === "--host") {
|
|
44078
|
+
if (token === "--username" || token === "--host" || command === "download" && ["--operation", "--selector", "--type", "--destination", "--transfer", "--confirm"].includes(token)) {
|
|
43869
44079
|
const value = args[index + 1];
|
|
44080
|
+
if (!value && command === "download") fail(value, `${level} ${token}`, `Supply a value for ${token}. Use ${entry.usage}. ${choices}`);
|
|
43870
44081
|
if (!value) fail(value, `${level} ${token}`, `Supply ${token} ${token === "--host" ? "<claude|codex|opencode|pi>" : "<username>"}. ${choices}`);
|
|
43871
44082
|
if (token === "--host" && !["claude", "codex", "opencode", "pi"].includes(value)) fail(value, `${level} --host`, `Valid hosts: claude, codex, opencode, pi. Use ${entry.usage}.`);
|
|
43872
44083
|
index++;
|
|
@@ -43876,6 +44087,7 @@ function validateArguments(args, prefixHelp) {
|
|
|
43876
44087
|
if (level === "tools show" && !tool && !seen.has("--help") && !prefixHelp) fail(void 0, level, choices);
|
|
43877
44088
|
}
|
|
43878
44089
|
function helpCommand(args, prefixHelp = false) {
|
|
44090
|
+
if (args[0] === "download" && args.includes("--help")) return "download";
|
|
43879
44091
|
if (args[0] === "setup" && args.includes("--help")) {
|
|
43880
44092
|
const path2 = args.filter((arg) => arg !== "--help");
|
|
43881
44093
|
for (let index = 1; index < path2.length; index++) {
|
|
@@ -44279,6 +44491,10 @@ var ManagedSession = class _ManagedSession {
|
|
|
44279
44491
|
checkServer(serverUrl) {
|
|
44280
44492
|
if (this.session && this.session.serverUrl.replace(/\/$/, "") !== serverUrl) throw new ArtiumError("SESSION_SERVER_MISMATCH");
|
|
44281
44493
|
}
|
|
44494
|
+
async once(request) {
|
|
44495
|
+
if (!this.session) throw new ArtiumError("TOKEN_INVALID");
|
|
44496
|
+
return request(this.session.accessToken);
|
|
44497
|
+
}
|
|
44282
44498
|
async authenticated(request, refresh) {
|
|
44283
44499
|
if (!this.session) throw new ArtiumError("TOKEN_INVALID");
|
|
44284
44500
|
try {
|
|
@@ -44301,9 +44517,9 @@ var ManagedSession = class _ManagedSession {
|
|
|
44301
44517
|
if (session.status === "authenticated") this.protect(session.accessToken, session.refreshToken);
|
|
44302
44518
|
else this.protect(session.passwordChangeToken);
|
|
44303
44519
|
try {
|
|
44304
|
-
await new Promise((
|
|
44520
|
+
await new Promise((resolve5, reject) => {
|
|
44305
44521
|
this.channel.write(`${JSON.stringify({ serverUrl, ...session })}
|
|
44306
|
-
`, (error62) => error62 ? reject(error62) :
|
|
44522
|
+
`, (error62) => error62 ? reject(error62) : resolve5());
|
|
44307
44523
|
});
|
|
44308
44524
|
} catch {
|
|
44309
44525
|
throw new ArtiumError(
|
|
@@ -44316,6 +44532,12 @@ var ManagedSession = class _ManagedSession {
|
|
|
44316
44532
|
write(stream, text2) {
|
|
44317
44533
|
this.output[stream] += text2;
|
|
44318
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
|
+
}
|
|
44319
44541
|
flush() {
|
|
44320
44542
|
this.channel.destroy();
|
|
44321
44543
|
const scanner = new SecretScanner(this.secrets);
|
|
@@ -44357,6 +44579,255 @@ async function update() {
|
|
|
44357
44579
|
return "status: updated\nnext: artium-axi --version\n";
|
|
44358
44580
|
}
|
|
44359
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
|
+
|
|
44360
44831
|
// src/cli/main.ts
|
|
44361
44832
|
var managed;
|
|
44362
44833
|
for (const stream of [process.stdout, process.stderr]) {
|
|
@@ -44406,6 +44877,10 @@ async function main(args) {
|
|
|
44406
44877
|
output2("stdout", await update());
|
|
44407
44878
|
return;
|
|
44408
44879
|
}
|
|
44880
|
+
if (command === "download") {
|
|
44881
|
+
await download(args.slice(1), (text2) => output2("stdout", text2), managed);
|
|
44882
|
+
return;
|
|
44883
|
+
}
|
|
44409
44884
|
if (command === "setup") {
|
|
44410
44885
|
output2("stdout", await setup(args.slice(1)));
|
|
44411
44886
|
return;
|
|
@@ -44525,9 +45000,9 @@ remoteRevocation: ${verified ? "verified" : "unverified"}
|
|
|
44525
45000
|
if (home) {
|
|
44526
45001
|
const tools = toolsResponseSchema.safeParse(await client.authenticated("/v1/tools", "GET", void 0, homeSession));
|
|
44527
45002
|
if (!tools.success) throw new ArtiumError("INVALID_RESPONSE");
|
|
44528
|
-
const executable =
|
|
45003
|
+
const executable = resolve4(process.argv[1]);
|
|
44529
45004
|
const local = relative(homedir3(), executable);
|
|
44530
|
-
const bin = local && !
|
|
45005
|
+
const bin = local && !isAbsolute2(local) && !local.startsWith(`..${sep}`) && local !== ".." ? `~${sep}${local}` : executable;
|
|
44531
45006
|
view = `bin: ${JSON.stringify(bin)}
|
|
44532
45007
|
description: Run managed upstream tools through one local sign-in.
|
|
44533
45008
|
${view}${toolsView(tools.data.tools)}`;
|
|
@@ -44554,16 +45029,20 @@ ${view}${toolsView(tools.data.tools)}`;
|
|
|
44554
45029
|
}
|
|
44555
45030
|
}
|
|
44556
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
|
+
}
|
|
44557
45036
|
output2("stdout", `error:
|
|
44558
45037
|
${Object.entries(response.error).map(([key, value]) => ` ${key}: ${JSON.stringify(value)}`).join("\n")}
|
|
44559
45038
|
`);
|
|
44560
45039
|
process.exitCode = errors[response.error.code].exitCode;
|
|
44561
45040
|
}
|
|
44562
|
-
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(() => {
|
|
44563
45042
|
try {
|
|
44564
45043
|
managed?.flush();
|
|
44565
45044
|
} catch {
|
|
44566
45045
|
managed = void 0;
|
|
44567
|
-
printError(errorResponse(new ArtiumError("SECRET_IN_OUTPUT"),
|
|
45046
|
+
printError(errorResponse(new ArtiumError("SECRET_IN_OUTPUT"), randomUUID5()));
|
|
44568
45047
|
}
|
|
44569
45048
|
});
|