nimblybase 0.1.11 → 0.1.12
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 +17 -2
- package/dist/index.js +63 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -226,6 +226,9 @@ credential configuration, private-key files, source maps, `.git` and
|
|
|
226
226
|
`node_modules`. Symlinks and unsafe URL filenames are refused. An upload uses
|
|
227
227
|
the byte snapshot that was scanned and hashed, even if a local file changes.
|
|
228
228
|
|
|
229
|
+
CLI 0.1.12+ uses a linear JWT scan so repeated token-like text cannot trigger
|
|
230
|
+
regex backtracking during preparation.
|
|
231
|
+
|
|
229
232
|
Both site and function preparation refuse recognised management/provider
|
|
230
233
|
credential patterns without printing their values. Public application keys
|
|
231
234
|
remain allowed. Pattern scanning cannot detect every secret, encoded value or
|
|
@@ -235,8 +238,20 @@ private business datum; review what belongs in a public artifact.
|
|
|
235
238
|
The preview proves local preparation only: it does not verify host installation,
|
|
236
239
|
project authorization, provider runtime compatibility or live health. A
|
|
237
240
|
cloud-only agent still needs a proven repository/build/upload path; connecting
|
|
238
|
-
to MCP alone does not supply one.
|
|
239
|
-
|
|
241
|
+
to MCP alone does not supply one.
|
|
242
|
+
|
|
243
|
+
The server independently refuses recognised credentials in function source
|
|
244
|
+
before recording/uploading code. Site uploads reject those patterns too;
|
|
245
|
+
manifest planning/commit refuses private paths, and commit/rollback rechecks
|
|
246
|
+
the actual stored bytes, hashes and sizes before changing the serving site.
|
|
247
|
+
Each publication reads each distinct blob once, sequentially (up to 100 MiB).
|
|
248
|
+
The server allows at most 9,000 distinct blobs to leave room below the default
|
|
249
|
+
Worker subrequest ceiling; up to 20,000 paths may share those blobs. A local
|
|
250
|
+
preview is not server admission. Safe existing sites remain served; an unsafe
|
|
251
|
+
historical release must be cleaned before it can be republished or restored.
|
|
252
|
+
These are accidental-leak guards, not a complete audit, malware scanner,
|
|
253
|
+
usage budget or protection against a project administrator rewriting R2
|
|
254
|
+
through their own privileged code. Function environment secrets remain valid.
|
|
240
255
|
|
|
241
256
|
## When something is broken
|
|
242
257
|
|
package/dist/index.js
CHANGED
|
@@ -384,7 +384,7 @@ function when(iso) {
|
|
|
384
384
|
|
|
385
385
|
// src/api.ts
|
|
386
386
|
var DEFAULT_API_URL = "https://api-uat.nimblybase.com";
|
|
387
|
-
var VERSION = "0.1.
|
|
387
|
+
var VERSION = "0.1.12";
|
|
388
388
|
function dashboardUrl(api) {
|
|
389
389
|
const match = /^https:\/\/api(-[a-z0-9]+)?\.(.+)$/.exec(api);
|
|
390
390
|
return match ? `https://app${match[1] ?? ""}.${match[2]}` : null;
|
|
@@ -8737,22 +8737,78 @@ import { build as bundle } from "esbuild";
|
|
|
8737
8737
|
// src/artifact-safety.ts
|
|
8738
8738
|
import { constants } from "node:fs";
|
|
8739
8739
|
import { lstat, open as open2 } from "node:fs/promises";
|
|
8740
|
+
|
|
8741
|
+
// ../../packages/contracts/dist/artifact-safety.js
|
|
8740
8742
|
var CREDENTIALS = [
|
|
8741
8743
|
{ kind: "NimblyBase management credential", pattern: /nb_(?:svc|pat|oat|ort)_[A-Za-z0-9_-]{20,}|nb_plat_\d{1,12}_[0-9a-f]{64}/ },
|
|
8742
8744
|
{ kind: "provider secret key", pattern: /(?:sk|rk)_(?:test|live)_[A-Za-z0-9]{20,}/ },
|
|
8743
8745
|
{ kind: "AWS access key", pattern: /(?:AKIA|ASIA)[A-Z0-9]{16}/ },
|
|
8744
8746
|
{ kind: "GitHub credential", pattern: /gh[pousr]_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{20,}/ },
|
|
8745
8747
|
{ kind: "Slack credential", pattern: /xox[baprs]-[A-Za-z0-9-]{20,}/ },
|
|
8746
|
-
{ kind: "private key", pattern: /-----BEGIN (?:[A-Z0-9]+ )*PRIVATE KEY-----/ }
|
|
8747
|
-
{ kind: "signed JWT credential", pattern: /eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{20,}/ }
|
|
8748
|
+
{ kind: "private key", pattern: /-----BEGIN (?:[A-Z0-9]+ )*PRIVATE KEY-----/ }
|
|
8748
8749
|
];
|
|
8750
|
+
function embeddedCredentialKind(content) {
|
|
8751
|
+
return CREDENTIALS.find(({ pattern }) => pattern.test(content))?.kind ?? (findSignedJwt(content) ? "signed JWT credential" : null);
|
|
8752
|
+
}
|
|
8753
|
+
function redactArtifactLabel(label) {
|
|
8754
|
+
const text = CREDENTIALS.reduce((text2, { pattern }) => text2.replace(new RegExp(pattern.source, "g"), "[redacted]"), label);
|
|
8755
|
+
let offset = 0;
|
|
8756
|
+
let result = "";
|
|
8757
|
+
let found;
|
|
8758
|
+
while (found = findSignedJwt(text, offset)) {
|
|
8759
|
+
result += text.slice(offset, found.start) + "[redacted]";
|
|
8760
|
+
offset = found.end;
|
|
8761
|
+
}
|
|
8762
|
+
return result + text.slice(offset);
|
|
8763
|
+
}
|
|
8764
|
+
function findSignedJwt(content, from = 0) {
|
|
8765
|
+
const segments = /[A-Za-z0-9_-]{10,}/g;
|
|
8766
|
+
segments.lastIndex = from;
|
|
8767
|
+
let previous = null;
|
|
8768
|
+
let header = null;
|
|
8769
|
+
for (let match = segments.exec(content); match; match = segments.exec(content)) {
|
|
8770
|
+
const start = match.index;
|
|
8771
|
+
const value = match[0];
|
|
8772
|
+
const end = start + value.length;
|
|
8773
|
+
const adjacent = previous && start === previous.end + 1 && content[previous.end] === ".";
|
|
8774
|
+
if (!adjacent)
|
|
8775
|
+
header = null;
|
|
8776
|
+
if (adjacent && header?.headerStart !== null && header?.headerStart !== void 0 && value.length >= 20) {
|
|
8777
|
+
return { start: header.headerStart, end };
|
|
8778
|
+
}
|
|
8779
|
+
const index = value.indexOf("eyJ");
|
|
8780
|
+
const current = { end, headerStart: index >= 0 && value.length - index >= 13 ? start + index : null };
|
|
8781
|
+
header = adjacent ? previous : null;
|
|
8782
|
+
previous = current;
|
|
8783
|
+
}
|
|
8784
|
+
return null;
|
|
8785
|
+
}
|
|
8786
|
+
var PRIVATE_ARTIFACT_NAMES = /* @__PURE__ */ new Set([
|
|
8787
|
+
".git",
|
|
8788
|
+
"node_modules",
|
|
8789
|
+
".ds_store",
|
|
8790
|
+
"thumbs.db",
|
|
8791
|
+
".npmrc",
|
|
8792
|
+
".netrc",
|
|
8793
|
+
".envrc",
|
|
8794
|
+
".aws",
|
|
8795
|
+
".ssh",
|
|
8796
|
+
".nimblybase",
|
|
8797
|
+
"credentials.json"
|
|
8798
|
+
]);
|
|
8799
|
+
function excludedArtifactName(value) {
|
|
8800
|
+
const name = value.toLowerCase();
|
|
8801
|
+
return PRIVATE_ARTIFACT_NAMES.has(name) || name === ".env" || name.startsWith(".env.") || name.endsWith(".map") || /\.(?:pem|p12|pfx|key)$/.test(name) || /^id_(?:rsa|ed25519|ecdsa|dsa)$/.test(name);
|
|
8802
|
+
}
|
|
8803
|
+
|
|
8804
|
+
// src/artifact-safety.ts
|
|
8749
8805
|
function assertNoEmbeddedCredentials(bytes, label) {
|
|
8750
8806
|
const content = Buffer.from(bytes).toString("latin1");
|
|
8751
|
-
const found =
|
|
8807
|
+
const found = embeddedCredentialKind(content);
|
|
8752
8808
|
if (found) {
|
|
8753
|
-
const safeLabel =
|
|
8809
|
+
const safeLabel = redactArtifactLabel(label);
|
|
8754
8810
|
throw new CliError(
|
|
8755
|
-
`${safeLabel} contains a recognised ${found
|
|
8811
|
+
`${safeLabel} contains a recognised ${found}; no artifact was uploaded.`,
|
|
8756
8812
|
EXIT.usage,
|
|
8757
8813
|
"Remove credentials from the build. Use function environment secrets for private values. Public app keys are allowed; this pattern check does not detect every possible secret."
|
|
8758
8814
|
);
|
|
@@ -9344,7 +9400,6 @@ import { readdir as readdir2, readFile as readFile6, lstat as lstat2 } from "nod
|
|
|
9344
9400
|
import { createHash as createHash5 } from "node:crypto";
|
|
9345
9401
|
import { spawn as spawn2 } from "node:child_process";
|
|
9346
9402
|
import { join as join5, relative as relative2, resolve as resolve8, sep } from "node:path";
|
|
9347
|
-
var SKIP = /* @__PURE__ */ new Set([".git", "node_modules", ".ds_store", "thumbs.db", ".npmrc", ".netrc", ".envrc", ".aws", ".ssh", ".nimblybase", "credentials.json"]);
|
|
9348
9403
|
var MAX_FILES = 2e4;
|
|
9349
9404
|
var MAX_FILE_BYTES = 25 * 1024 * 1024;
|
|
9350
9405
|
var MAX_SITE_BYTES = 100 * 1024 * 1024;
|
|
@@ -9591,7 +9646,7 @@ async function collect(dir) {
|
|
|
9591
9646
|
for (const entry of await readdir2(current, { withFileTypes: true })) {
|
|
9592
9647
|
const name = entry.name.toLowerCase();
|
|
9593
9648
|
const path = toUrlPath(relative2(dir, join5(current, entry.name)));
|
|
9594
|
-
if (
|
|
9649
|
+
if (excludedArtifactName(name)) {
|
|
9595
9650
|
omitted.push(path);
|
|
9596
9651
|
continue;
|
|
9597
9652
|
}
|