@seekrit/cli 1.9.5 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +52 -23
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -5591,11 +5591,13 @@ function encodeOpensshPrivateKey(seed, pub, comment) {
|
|
|
5591
5591
|
//#region ../../packages/crypto/src/token.ts
|
|
5592
5592
|
/**
|
|
5593
5593
|
* Service tokens (CI, docker builds, agent proxies, k8s, …) are self-contained
|
|
5594
|
-
* principals
|
|
5595
|
-
*
|
|
5596
|
-
*
|
|
5594
|
+
* principals. One local bundle holds independent authentication and decryption
|
|
5595
|
+
* material. Only serviceTokenAuth(token) may be sent to the API; the private
|
|
5596
|
+
* key stays local. The server stores the auth credential hash and public key.
|
|
5597
5597
|
*
|
|
5598
|
-
*
|
|
5598
|
+
* Bundle: `skt_<id>_v2.<32-byte auth secret>.<private key pkcs8, base64url>`
|
|
5599
|
+
* Wire: `skt_<id>_v2.<32-byte auth secret>`
|
|
5600
|
+
* Legacy bundles remain readable offline but must never be transmitted.
|
|
5599
5601
|
*/
|
|
5600
5602
|
const TOKEN_PREFIX = "skt";
|
|
5601
5603
|
const CLI_SESSION_PREFIX = "skc";
|
|
@@ -5625,11 +5627,11 @@ async function createServiceToken() {
|
|
|
5625
5627
|
const { publicKeyJwk, privateKeyJwk } = await generateKeyPair();
|
|
5626
5628
|
const pkcs8 = await exportPrivateKeyPkcs8(await importPrivateKey(privateKeyJwk));
|
|
5627
5629
|
const tokenId = randomTokenId();
|
|
5628
|
-
const token = `${tokenId}
|
|
5630
|
+
const token = `${tokenId}_v2.${toBase64Url(crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(32)))}.${toBase64Url(pkcs8)}`;
|
|
5629
5631
|
return {
|
|
5630
5632
|
token,
|
|
5631
5633
|
tokenId,
|
|
5632
|
-
tokenHash: await hashToken(token),
|
|
5634
|
+
tokenHash: await hashToken(serviceTokenAuth(token)),
|
|
5633
5635
|
publicKeyJwk
|
|
5634
5636
|
};
|
|
5635
5637
|
}
|
|
@@ -5642,8 +5644,8 @@ async function createHoneyToken() {
|
|
|
5642
5644
|
};
|
|
5643
5645
|
}
|
|
5644
5646
|
async function parseServiceToken(token) {
|
|
5645
|
-
const match = /^(skt_[0-9A-Za-z]+)_([A-Za-z0-9_-]+)$/.exec(token);
|
|
5646
|
-
if (!match) throw new SeekritCryptoError("MALFORMED_TOKEN", "not a valid seekrit service token");
|
|
5647
|
+
const match = /^(skt_[0-9A-Za-z]+)_(?:v2\.[A-Za-z0-9_-]{43}\.)?([A-Za-z0-9_-]+)$/.exec(token);
|
|
5648
|
+
if (!match || match[0] !== token) throw new SeekritCryptoError("MALFORMED_TOKEN", "not a valid seekrit service token");
|
|
5647
5649
|
const [, tokenId, keyB64] = match;
|
|
5648
5650
|
try {
|
|
5649
5651
|
return {
|
|
@@ -5654,6 +5656,18 @@ async function parseServiceToken(token) {
|
|
|
5654
5656
|
throw new SeekritCryptoError("MALFORMED_TOKEN", "service token private key is corrupted");
|
|
5655
5657
|
}
|
|
5656
5658
|
}
|
|
5659
|
+
/** Extract only the independent auth credential. Never fall back to a raw token. */
|
|
5660
|
+
function serviceTokenAuth(token) {
|
|
5661
|
+
const match = /^(skt_[0-9A-Za-z]+_v2\.[A-Za-z0-9_-]{43})(?:\.[A-Za-z0-9_-]+)?$/.exec(token);
|
|
5662
|
+
const auth = match?.[1];
|
|
5663
|
+
if (!match || match[0] !== token || !auth) throw new SeekritCryptoError("MALFORMED_TOKEN", "a v2 service token is required for API access; replace legacy tokens in the dashboard");
|
|
5664
|
+
return auth;
|
|
5665
|
+
}
|
|
5666
|
+
/** Preserve human/session credentials while stripping service-token private keys. */
|
|
5667
|
+
function apiBearerToken(token) {
|
|
5668
|
+
const value = token.trim();
|
|
5669
|
+
return isServiceToken(value) ? serviceTokenAuth(value) : token;
|
|
5670
|
+
}
|
|
5657
5671
|
function isServiceToken(value) {
|
|
5658
5672
|
return value.startsWith(`${TOKEN_PREFIX}_`);
|
|
5659
5673
|
}
|
|
@@ -5686,7 +5700,7 @@ async function createAgentTaskToken() {
|
|
|
5686
5700
|
}
|
|
5687
5701
|
//#endregion
|
|
5688
5702
|
//#region package.json
|
|
5689
|
-
var version = "
|
|
5703
|
+
var version = "2.0.0";
|
|
5690
5704
|
//#endregion
|
|
5691
5705
|
//#region ../../packages/api-client/src/index.ts
|
|
5692
5706
|
/**
|
|
@@ -5725,13 +5739,15 @@ var SeekritClient = class {
|
|
|
5725
5739
|
...extraHeaders
|
|
5726
5740
|
};
|
|
5727
5741
|
if (this.client) headers["x-seekrit-client"] = this.client;
|
|
5728
|
-
if (this.auth.type === "bearer") headers.authorization = `Bearer ${this.auth.token}`;
|
|
5742
|
+
if (this.auth.type === "bearer") headers.authorization = `Bearer ${apiBearerToken(this.auth.token)}`;
|
|
5729
5743
|
else if (this.auth.type === "dynamic") {
|
|
5730
5744
|
const token = await this.auth.getToken();
|
|
5731
5745
|
if (!token) throw new SeekritApiError(401, "unauthorized", "session expired");
|
|
5732
|
-
headers.authorization = `Bearer ${token}`;
|
|
5733
|
-
} else if (this.auth.type === "m2m")
|
|
5734
|
-
|
|
5746
|
+
headers.authorization = `Bearer ${apiBearerToken(token)}`;
|
|
5747
|
+
} else if (this.auth.type === "m2m") {
|
|
5748
|
+
if (isServiceToken(this.auth.clientId.trim()) || isServiceToken(this.auth.clientSecret.trim())) throw new Error("service tokens must use bearer authentication, not machine client credentials");
|
|
5749
|
+
headers.authorization = `Basic ${btoa(`${this.auth.clientId}:${this.auth.clientSecret}`)}`;
|
|
5750
|
+
} else headers["x-seekrit-dev-user"] = this.auth.email;
|
|
5735
5751
|
if (body !== void 0) headers["content-type"] = "application/json";
|
|
5736
5752
|
const res = await this.fetchImpl(`${this.baseUrl}${path}`, {
|
|
5737
5753
|
method,
|
|
@@ -6205,6 +6221,7 @@ var SeekritClient = class {
|
|
|
6205
6221
|
* identity are three different answers.
|
|
6206
6222
|
*/
|
|
6207
6223
|
introspectAgentTask(token) {
|
|
6224
|
+
if (!token.startsWith("skd_")) throw new Error("expected a dispatched task token");
|
|
6208
6225
|
return this.request("POST", "/v1/tasks/introspect", { token });
|
|
6209
6226
|
}
|
|
6210
6227
|
/** End a run's authority now. Idempotent. */
|
|
@@ -9350,7 +9367,7 @@ const OFFLINE_DECRYPTOR_HTML = `<!doctype html>
|
|
|
9350
9367
|
|
|
9351
9368
|
/** skt_<id>_<pkcs8 base64url>: a service token carries its own private key. */
|
|
9352
9369
|
async function importToken(token) {
|
|
9353
|
-
var match = /^(skt_[0-9A-Za-z]+)_([A-Za-z0-9_-]+)$/.exec(String(token).trim());
|
|
9370
|
+
var match = /^(skt_[0-9A-Za-z]+)_(?:v2\\.[A-Za-z0-9_-]{43}\\.)?([A-Za-z0-9_-]+)$/.exec(String(token).trim());
|
|
9354
9371
|
if (!match) throw new Error("not a valid seekrit service token");
|
|
9355
9372
|
var privateKey = await crypto.subtle.importKey(
|
|
9356
9373
|
"pkcs8",
|
|
@@ -12130,8 +12147,15 @@ async function ensureM2mAdminToken(dotenvVars = {}) {
|
|
|
12130
12147
|
if (!creds) return null;
|
|
12131
12148
|
const cached = readCachedM2mToken(creds.clientId);
|
|
12132
12149
|
if (cached) {
|
|
12133
|
-
|
|
12134
|
-
|
|
12150
|
+
let usable = false;
|
|
12151
|
+
try {
|
|
12152
|
+
serviceTokenAuth(cached);
|
|
12153
|
+
usable = true;
|
|
12154
|
+
} catch {}
|
|
12155
|
+
if (usable) {
|
|
12156
|
+
process.env.SEEKRIT_TOKEN = cached;
|
|
12157
|
+
return cached;
|
|
12158
|
+
}
|
|
12135
12159
|
}
|
|
12136
12160
|
const token = await mintAdminToken(process.env.SEEKRIT_API_URL ?? dotenvVars.SEEKRIT_API_URL ?? readGlobalConfig().apiUrl ?? "https://api.seekrit.dev", creds);
|
|
12137
12161
|
cacheM2mToken(creds.clientId, token);
|
|
@@ -12715,7 +12739,7 @@ function collect$5(value, acc) {
|
|
|
12715
12739
|
* release-please-config.json), so the pin follows the crate without anyone
|
|
12716
12740
|
* remembering to move it.
|
|
12717
12741
|
*/
|
|
12718
|
-
const PROXY_VERSION = "0.
|
|
12742
|
+
const PROXY_VERSION = "0.13.0";
|
|
12719
12743
|
const BIN = "seekrit-proxy";
|
|
12720
12744
|
/**
|
|
12721
12745
|
* Host → Rust target triple.
|
|
@@ -12751,7 +12775,7 @@ function versionPrefix(version) {
|
|
|
12751
12775
|
return version.startsWith("v") ? version : `v${version}`;
|
|
12752
12776
|
}
|
|
12753
12777
|
function resolveVersion(explicit) {
|
|
12754
|
-
return explicit ?? process.env.SEEKRIT_PROXY_VERSION ?? "0.
|
|
12778
|
+
return explicit ?? process.env.SEEKRIT_PROXY_VERSION ?? "0.13.0";
|
|
12755
12779
|
}
|
|
12756
12780
|
function resolveBaseUrl(explicit) {
|
|
12757
12781
|
return (explicit ?? process.env.SEEKRIT_PROXY_BASE_URL ?? "https://proxy.seekrit.dev").replace(/\/+$/, "");
|
|
@@ -12909,7 +12933,7 @@ const PLUGIN_BIN = "seekrit-nomad-secrets";
|
|
|
12909
12933
|
* entry in release-please-config.json), so the pin follows the crate without
|
|
12910
12934
|
* anyone remembering to move it — the same arrangement `PROXY_VERSION` has.
|
|
12911
12935
|
*/
|
|
12912
|
-
const NOMAD_PLUGIN_VERSION = "0.
|
|
12936
|
+
const NOMAD_PLUGIN_VERSION = "0.3.0";
|
|
12913
12937
|
/** Public artifact host. Mirrors GitHub Releases; public, no egress fees. */
|
|
12914
12938
|
const DEFAULT_BASE_URL = "https://nomad.seekrit.dev";
|
|
12915
12939
|
/** Where the plugin reads its node config, matching `config::DEFAULT_CONFIG_PATH`. */
|
|
@@ -12996,7 +13020,7 @@ async function fetchBytes(url) {
|
|
|
12996
13020
|
* let Nomad fingerprint a half-written executable.
|
|
12997
13021
|
*/
|
|
12998
13022
|
async function installPlugin(pluginDir, options = {}) {
|
|
12999
|
-
const version = options.version ?? "0.
|
|
13023
|
+
const version = options.version ?? "0.3.0";
|
|
13000
13024
|
const baseUrl = (options.baseUrl ?? "https://nomad.seekrit.dev").replace(/\/+$/, "");
|
|
13001
13025
|
const { target, exe } = detectTarget();
|
|
13002
13026
|
if (exe !== "") fail("Nomad secret provider plugins are not supported on Windows clients");
|
|
@@ -14701,10 +14725,10 @@ Next:
|
|
|
14701
14725
|
});
|
|
14702
14726
|
console.log(path);
|
|
14703
14727
|
const { target } = detectTarget();
|
|
14704
|
-
process.stderr.write(`seekrit-proxy ${versionPrefix(options.proxyVersion ?? "0.
|
|
14728
|
+
process.stderr.write(`seekrit-proxy ${versionPrefix(options.proxyVersion ?? "0.13.0")} (${target})\n`);
|
|
14705
14729
|
});
|
|
14706
14730
|
proxy.command("where").description("show which binary `seekrit proxy run` would use, without fetching it").option("--proxy-version <version>", `version to report (default: ${PROXY_VERSION})`).option("--json", "machine-readable output").action((options) => {
|
|
14707
|
-
const version = options.proxyVersion ?? process.env.SEEKRIT_PROXY_VERSION ?? "0.
|
|
14731
|
+
const version = options.proxyVersion ?? process.env.SEEKRIT_PROXY_VERSION ?? "0.13.0";
|
|
14708
14732
|
const override = process.env.SEEKRIT_PROXY_BIN;
|
|
14709
14733
|
const { target, exe } = detectTarget();
|
|
14710
14734
|
const path = override ?? proxyBinaryPath(version, target, exe);
|
|
@@ -14763,7 +14787,7 @@ Next:
|
|
|
14763
14787
|
process.stdout.write(renderComposeSnippet(plan, {
|
|
14764
14788
|
service: options.service,
|
|
14765
14789
|
workload: options.workload,
|
|
14766
|
-
image: options.image ?? `seekritdev/proxy:0.
|
|
14790
|
+
image: options.image ?? `seekritdev/proxy:0.13.0`,
|
|
14767
14791
|
publish: Boolean(options.publish)
|
|
14768
14792
|
}));
|
|
14769
14793
|
});
|
|
@@ -16622,6 +16646,11 @@ program.command("export").description("print decrypted secrets (dotenv, json, or
|
|
|
16622
16646
|
registerAccessCommands(program);
|
|
16623
16647
|
registerHoneyTokenCommands(program);
|
|
16624
16648
|
const token = program.command("token").description("manage service tokens (CI, docker, agents)");
|
|
16649
|
+
token.command("auth").description("print only the v2 authentication component for a manual API request").action(() => {
|
|
16650
|
+
const ctx = buildContext();
|
|
16651
|
+
if (ctx.auth.type !== "bearer" || !isServiceToken(ctx.auth.token)) fail("token auth requires a v2 service token in SEEKRIT_TOKEN or your saved configuration");
|
|
16652
|
+
console.log(serviceTokenAuth(ctx.auth.token));
|
|
16653
|
+
});
|
|
16625
16654
|
token.command("create").description("create a service token (runtime, or --admin for provisioning); prints it once").requiredOption("--name <name>", "display name, e.g. ci-deploy").option("--org <slug>", "organization slug (defaults to seekrit.json, or your only org)").option("--app <slug>", "application to bind the token to (runtime tokens)").option("--env <slug>", "environment to bind the token to (runtime tokens)").option("--admin", "mint an org-scoped admin token that can provision structure (no env binding required)").option("--allow <group=env>", "also grant an alternate group slice (for `run --with`)", collectKv).option("--no-grant", "skip auto-granting the env + composed group keys").action(async (options) => {
|
|
16626
16655
|
const ctx = buildContext();
|
|
16627
16656
|
const role = options.admin ? "admin" : "member";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seekrit/cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "End-to-end encrypted secrets manager CLI — inject decrypted secrets into any command.",
|
|
5
5
|
"homepage": "https://seekrit.dev/docs/guides/cli",
|
|
6
6
|
"keywords": [
|
|
@@ -46,9 +46,9 @@
|
|
|
46
46
|
"@types/node": "^26.6.5",
|
|
47
47
|
"tsdown": "^0.23.1",
|
|
48
48
|
"vitest": "^5.0.5",
|
|
49
|
+
"@seekrit/crypto": "0.0.1",
|
|
49
50
|
"@seekrit/api-client": "0.0.1",
|
|
50
|
-
"@seekrit/core": "0.0.1"
|
|
51
|
-
"@seekrit/crypto": "0.0.1"
|
|
51
|
+
"@seekrit/core": "0.0.1"
|
|
52
52
|
},
|
|
53
53
|
"scripts": {
|
|
54
54
|
"build": "tsdown",
|