@amirhosseinnateghi/vibed-cli 0.1.0 → 0.1.1
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 +247 -139
- package/package.json +5 -2
package/dist/index.js
CHANGED
|
@@ -6018,141 +6018,6 @@ import { dirname, extname, isAbsolute, join as join2, resolve } from "node:path"
|
|
|
6018
6018
|
|
|
6019
6019
|
// ../sandbox/src/index.ts
|
|
6020
6020
|
var import_node_html_parser = __toESM(require_dist(), 1);
|
|
6021
|
-
var FORBIDDEN_API_PATTERNS = [
|
|
6022
|
-
{ re: /getUserMedia/, name: "getUserMedia" },
|
|
6023
|
-
{ re: /navigator\s*\.\s*geolocation/, name: "geolocation" },
|
|
6024
|
-
{ re: /new\s+Notification|Notification\s*\.\s*requestPermission/, name: "Notification" },
|
|
6025
|
-
{ re: /serviceWorker/, name: "serviceWorker" },
|
|
6026
|
-
{ re: /openDatabase/, name: "openDatabase" },
|
|
6027
|
-
{ re: /PaymentRequest|navigator\s*\.\s*payments/, name: "PaymentRequest" },
|
|
6028
|
-
{ re: /navigator\s*\.\s*credentials|PublicKeyCredential|WebAuthn/, name: "WebAuthn" },
|
|
6029
|
-
{ re: /requestPointerLock/, name: "pointerLock" }
|
|
6030
|
-
// allowed via gesture only; flag
|
|
6031
|
-
];
|
|
6032
|
-
var TOP_NAV_PATTERNS = [
|
|
6033
|
-
/window\s*\.\s*top/,
|
|
6034
|
-
/window\s*\.\s*parent\s*\.\s*location/,
|
|
6035
|
-
/\btop\s*\.\s*location/,
|
|
6036
|
-
/\bparent\s*\.\s*location/
|
|
6037
|
-
];
|
|
6038
|
-
var BRAND_NAMES = [
|
|
6039
|
-
"google",
|
|
6040
|
-
"gmail",
|
|
6041
|
-
"apple",
|
|
6042
|
-
"icloud",
|
|
6043
|
-
"microsoft",
|
|
6044
|
-
"outlook",
|
|
6045
|
-
"paypal",
|
|
6046
|
-
"facebook",
|
|
6047
|
-
"instagram",
|
|
6048
|
-
"telegram",
|
|
6049
|
-
"whatsapp",
|
|
6050
|
-
"metamask",
|
|
6051
|
-
"binance",
|
|
6052
|
-
"coinbase"
|
|
6053
|
-
];
|
|
6054
|
-
function extractExternalHosts(html) {
|
|
6055
|
-
const scrubbed = html.replace(/\bxmlns(:[a-z0-9-]+)?\s*=\s*("[^"]*"|'[^']*')/gi, "").replace(/<!DOCTYPE[^>]*>/gi, "");
|
|
6056
|
-
const hosts = /* @__PURE__ */ new Set();
|
|
6057
|
-
const re = /https?:\/\/([a-z0-9.-]+)/gi;
|
|
6058
|
-
let m;
|
|
6059
|
-
while (m = re.exec(scrubbed)) {
|
|
6060
|
-
const host = m[1]?.toLowerCase();
|
|
6061
|
-
if (host) hosts.add(host);
|
|
6062
|
-
}
|
|
6063
|
-
return [...hosts];
|
|
6064
|
-
}
|
|
6065
|
-
function looksLikeHtml(root) {
|
|
6066
|
-
const hasEl = root.querySelector("*") != null;
|
|
6067
|
-
return hasEl;
|
|
6068
|
-
}
|
|
6069
|
-
function validate(html, opts) {
|
|
6070
|
-
const errors = [];
|
|
6071
|
-
const warnings = [];
|
|
6072
|
-
const size = Buffer.byteLength(html, "utf8");
|
|
6073
|
-
if (size > opts.maxBytes) {
|
|
6074
|
-
errors.push({
|
|
6075
|
-
code: "SIZE_EXCEEDED",
|
|
6076
|
-
message: `Artifact is ${size} bytes; limit is ${opts.maxBytes}.`
|
|
6077
|
-
});
|
|
6078
|
-
}
|
|
6079
|
-
const root = (0, import_node_html_parser.parse)(html, {
|
|
6080
|
-
lowerCaseTagName: true,
|
|
6081
|
-
comment: true,
|
|
6082
|
-
voidTag: { closingSlash: true }
|
|
6083
|
-
});
|
|
6084
|
-
if (!looksLikeHtml(root)) {
|
|
6085
|
-
errors.push({ code: "NOT_HTML", message: "Content is not parseable as HTML." });
|
|
6086
|
-
return { ok: false, errors, warnings };
|
|
6087
|
-
}
|
|
6088
|
-
if (root.querySelector("iframe, frame, object, embed")) {
|
|
6089
|
-
errors.push({
|
|
6090
|
-
code: "FRAME_TAG",
|
|
6091
|
-
message: "Frames are not allowed (iframe/frame/object/embed)."
|
|
6092
|
-
});
|
|
6093
|
-
}
|
|
6094
|
-
if (root.querySelector("base")) {
|
|
6095
|
-
errors.push({ code: "BASE_TAG", message: "<base> tags are not allowed." });
|
|
6096
|
-
}
|
|
6097
|
-
for (const meta of root.querySelectorAll("meta")) {
|
|
6098
|
-
if ((meta.getAttribute("http-equiv") || "").toLowerCase() === "refresh") {
|
|
6099
|
-
errors.push({ code: "META_REFRESH", message: "meta-refresh redirects are not allowed." });
|
|
6100
|
-
break;
|
|
6101
|
-
}
|
|
6102
|
-
}
|
|
6103
|
-
for (const input of root.querySelectorAll("input")) {
|
|
6104
|
-
if ((input.getAttribute("type") || "").toLowerCase() === "password") {
|
|
6105
|
-
errors.push({
|
|
6106
|
-
code: "PASSWORD_INPUT",
|
|
6107
|
-
message: 'Password inputs are not allowed (<input type="password">).'
|
|
6108
|
-
});
|
|
6109
|
-
break;
|
|
6110
|
-
}
|
|
6111
|
-
}
|
|
6112
|
-
for (const re of TOP_NAV_PATTERNS) {
|
|
6113
|
-
if (re.test(html)) {
|
|
6114
|
-
errors.push({
|
|
6115
|
-
code: "TOP_NAVIGATION",
|
|
6116
|
-
message: "Top-level navigation attempts are not allowed.",
|
|
6117
|
-
detail: re.source
|
|
6118
|
-
});
|
|
6119
|
-
break;
|
|
6120
|
-
}
|
|
6121
|
-
}
|
|
6122
|
-
for (const { re, name } of FORBIDDEN_API_PATTERNS) {
|
|
6123
|
-
if (re.test(html)) {
|
|
6124
|
-
errors.push({
|
|
6125
|
-
code: "FORBIDDEN_API",
|
|
6126
|
-
message: `Forbidden API used: ${name}.`,
|
|
6127
|
-
detail: name
|
|
6128
|
-
});
|
|
6129
|
-
}
|
|
6130
|
-
}
|
|
6131
|
-
const hosts = extractExternalHosts(html);
|
|
6132
|
-
const offending = hosts.filter(
|
|
6133
|
-
(h) => !opts.allowedOrigins.some((allowed) => h === allowed || h.endsWith("." + allowed))
|
|
6134
|
-
);
|
|
6135
|
-
for (const host of offending) {
|
|
6136
|
-
errors.push({
|
|
6137
|
-
code: "EXTERNAL_URL",
|
|
6138
|
-
message: `External URL not in allowlist: ${host}`,
|
|
6139
|
-
detail: host
|
|
6140
|
-
});
|
|
6141
|
-
}
|
|
6142
|
-
const lower = html.toLowerCase();
|
|
6143
|
-
const hasCredField = root.querySelector('input[type="email"], input[type="text"], input[type="tel"]') != null && /(password|passcode|otp|verify|sign\s*in|log\s*in|account)/i.test(html);
|
|
6144
|
-
if (hasCredField) {
|
|
6145
|
-
const brand = BRAND_NAMES.find((b) => lower.includes(b));
|
|
6146
|
-
if (brand) {
|
|
6147
|
-
warnings.push({
|
|
6148
|
-
code: "BRAND_CREDENTIAL",
|
|
6149
|
-
message: `Possible phishing: brand "${brand}" near a credential field.`,
|
|
6150
|
-
detail: brand
|
|
6151
|
-
});
|
|
6152
|
-
}
|
|
6153
|
-
}
|
|
6154
|
-
return { ok: errors.length === 0, errors, warnings };
|
|
6155
|
-
}
|
|
6156
6021
|
|
|
6157
6022
|
// ../shared/src/constants.ts
|
|
6158
6023
|
var CATEGORIES = [
|
|
@@ -6181,6 +6046,17 @@ var config = {
|
|
|
6181
6046
|
MODERATION_AUTOPUBLISH: boolEnv("MODERATION_AUTOPUBLISH", true),
|
|
6182
6047
|
BUILDER_MODE: strEnv("BUILDER_MODE", "mock"),
|
|
6183
6048
|
// 'mock' | 'live'
|
|
6049
|
+
// Builder LLM — non-secret routing only (this object ships to the client).
|
|
6050
|
+
// The platform API key is read server-side from process.env.BUILDER_API_KEY.
|
|
6051
|
+
// Which platform provider serves the builder: 'gapgpt' | 'openrouter'.
|
|
6052
|
+
BUILDER_PROVIDER: strEnv("BUILDER_PROVIDER", "gapgpt"),
|
|
6053
|
+
BUILDER_BASE_URL: strEnv("BUILDER_BASE_URL", "https://api.gapgpt.app/v1"),
|
|
6054
|
+
BUILDER_MODEL: strEnv("BUILDER_MODEL", "claude-sonnet-4-20250514"),
|
|
6055
|
+
// Light model that auto-tags the experience category. Falls back to a keyword
|
|
6056
|
+
// heuristic if the call fails.
|
|
6057
|
+
BUILDER_CLASSIFIER_MODEL: strEnv("BUILDER_CLASSIFIER_MODEL", "gemini-2.5-flash"),
|
|
6058
|
+
BUILDER_MAX_TOKENS: intEnv("BUILDER_MAX_TOKENS", 8192),
|
|
6059
|
+
BUILDER_MAX_MESSAGES: intEnv("BUILDER_MAX_MESSAGES", 30),
|
|
6184
6060
|
DEFAULT_LOCALE: strEnv("DEFAULT_LOCALE", "en")
|
|
6185
6061
|
};
|
|
6186
6062
|
function strEnv(key, fallback) {
|
|
@@ -6203,6 +6079,37 @@ function boolEnv(key, fallback) {
|
|
|
6203
6079
|
return v === "true" || v === "1";
|
|
6204
6080
|
}
|
|
6205
6081
|
|
|
6082
|
+
// ../shared/src/policy.ts
|
|
6083
|
+
function boolEnv2(key, fallback) {
|
|
6084
|
+
const v = typeof process !== "undefined" ? process.env[key] : void 0;
|
|
6085
|
+
if (v == null || v === "") return fallback;
|
|
6086
|
+
return v === "true" || v === "1";
|
|
6087
|
+
}
|
|
6088
|
+
var POST_RULES = [
|
|
6089
|
+
{ id: "size_limit", code: "SIZE_EXCEEDED", title: "Size limit", default: true, env: "RULE_SIZE_LIMIT" },
|
|
6090
|
+
{ id: "frames", code: "FRAME_TAG", title: "No frames", default: true, env: "RULE_FRAMES" },
|
|
6091
|
+
{ id: "base_tag", code: "BASE_TAG", title: "No <base> tag", default: true, env: "RULE_BASE_TAG" },
|
|
6092
|
+
{ id: "meta_refresh", code: "META_REFRESH", title: "No meta-refresh", default: true, env: "RULE_META_REFRESH" },
|
|
6093
|
+
{ id: "password_input", code: "PASSWORD_INPUT", title: "No password inputs", default: true, env: "RULE_PASSWORD_INPUT" },
|
|
6094
|
+
{ id: "top_navigation", code: "TOP_NAVIGATION", title: "No top-navigation", default: true, env: "RULE_TOP_NAVIGATION" },
|
|
6095
|
+
{ id: "forbidden_apis", code: "FORBIDDEN_API", title: "No forbidden APIs (payments, background, capture\u2026)", default: true, env: "RULE_FORBIDDEN_APIS" },
|
|
6096
|
+
{ id: "external_urls", code: "EXTERNAL_URL", title: "No off-allowlist external URLs", default: true, env: "RULE_EXTERNAL_URLS" },
|
|
6097
|
+
{ id: "external_scripts", code: "EXTERNAL_SCRIPT", title: "No creator-selected JS packages (external <script>)", default: false, env: "RULE_EXTERNAL_SCRIPTS" },
|
|
6098
|
+
{ id: "external_links", code: "EXTERNAL_LINK", title: "No direct external links (<a> off-site)", default: false, env: "RULE_EXTERNAL_LINKS" },
|
|
6099
|
+
// Enforced by injecting a viewport clip at sanitize (not a reject) — a static
|
|
6100
|
+
// scan can't tell whether a post actually scrolls, so we make it can't.
|
|
6101
|
+
{ id: "no_vertical_scroll", code: "SANITIZE_CLIP", title: "No vertical page scroll (fit the viewport)", default: true, env: "RULE_NO_VERTICAL_SCROLL" }
|
|
6102
|
+
];
|
|
6103
|
+
function resolvePolicy() {
|
|
6104
|
+
const rules = {};
|
|
6105
|
+
for (const r of POST_RULES) rules[r.id] = boolEnv2(r.env, r.default);
|
|
6106
|
+
return { enabled: boolEnv2("POST_RESTRICTIONS_ENABLED", true), rules };
|
|
6107
|
+
}
|
|
6108
|
+
var postPolicy = resolvePolicy();
|
|
6109
|
+
function ruleActive(id, policy = postPolicy) {
|
|
6110
|
+
return policy.enabled && policy.rules[id] === true;
|
|
6111
|
+
}
|
|
6112
|
+
|
|
6206
6113
|
// ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js
|
|
6207
6114
|
var external_exports = {};
|
|
6208
6115
|
__export(external_exports, {
|
|
@@ -10284,8 +10191,19 @@ var createBuildSchema = external_exports.object({
|
|
|
10284
10191
|
mode: external_exports.enum(["ai", "remix"]),
|
|
10285
10192
|
remixParentId: external_exports.string().uuid().optional()
|
|
10286
10193
|
});
|
|
10194
|
+
var builderAttachmentSchema = external_exports.object({
|
|
10195
|
+
kind: external_exports.enum(["image", "file"]),
|
|
10196
|
+
name: external_exports.string().max(200),
|
|
10197
|
+
dataUrl: external_exports.string().max(8e6).optional(),
|
|
10198
|
+
// ~6MB image as base64 data URL
|
|
10199
|
+
text: external_exports.string().max(5e4).optional()
|
|
10200
|
+
});
|
|
10287
10201
|
var builderMessageSchema = external_exports.object({
|
|
10288
|
-
content: external_exports.string().
|
|
10202
|
+
content: external_exports.string().max(4e3).default(""),
|
|
10203
|
+
attachments: external_exports.array(builderAttachmentSchema).max(6).optional(),
|
|
10204
|
+
model: external_exports.string().max(100).optional()
|
|
10205
|
+
}).refine((v) => v.content.trim().length > 0 || (v.attachments?.length ?? 0) > 0, {
|
|
10206
|
+
message: "Send a message or an attachment."
|
|
10289
10207
|
});
|
|
10290
10208
|
var importSchema = external_exports.object({
|
|
10291
10209
|
html: external_exports.string().min(1).optional(),
|
|
@@ -10296,6 +10214,7 @@ var importSchema = external_exports.object({
|
|
|
10296
10214
|
var saveSchema = external_exports.object({
|
|
10297
10215
|
collectionId: external_exports.string().uuid().optional()
|
|
10298
10216
|
});
|
|
10217
|
+
var AI_PROVIDERS = ["gapgpt", "moonshot", "openai", "openrouter"];
|
|
10299
10218
|
var collectionSchema = external_exports.object({
|
|
10300
10219
|
name: external_exports.string().min(1).max(60)
|
|
10301
10220
|
});
|
|
@@ -10313,8 +10232,8 @@ var patchMeSchema = external_exports.object({
|
|
|
10313
10232
|
// once
|
|
10314
10233
|
});
|
|
10315
10234
|
var aiConnectionSchema = external_exports.object({
|
|
10316
|
-
provider: external_exports.enum(
|
|
10317
|
-
apiKey: external_exports.string().min(
|
|
10235
|
+
provider: external_exports.enum(AI_PROVIDERS),
|
|
10236
|
+
apiKey: external_exports.string().trim().min(12).max(300)
|
|
10318
10237
|
});
|
|
10319
10238
|
var feedQuerySchema = external_exports.object({
|
|
10320
10239
|
tab: external_exports.enum(["explore", "following"]).default("explore"),
|
|
@@ -10330,6 +10249,24 @@ var searchQuerySchema = external_exports.object({
|
|
|
10330
10249
|
limit: external_exports.coerce.number().int().min(1).max(30).default(15)
|
|
10331
10250
|
});
|
|
10332
10251
|
|
|
10252
|
+
// ../shared/src/models.ts
|
|
10253
|
+
var BUILDER_MODELS = [
|
|
10254
|
+
// ── Free (selectable now) — good quality, reasonable price ─────────
|
|
10255
|
+
{ id: "claude-sonnet-4-20250514", label: "Claude Sonnet 4", tier: "free", note: "Best for polished HTML", vision: true },
|
|
10256
|
+
{ id: "gemini-2.5-flash", label: "Gemini 2.5 Flash", tier: "free", note: "Fast \xB7 great UI \xB7 cheap", vision: true },
|
|
10257
|
+
{ id: "gpt-4o-mini", label: "GPT-4o mini", tier: "free", note: "Cheap & reliable", vision: true },
|
|
10258
|
+
{ id: "gpt-5-mini", label: "GPT-5 mini", tier: "free", note: "Newer, capable", vision: true },
|
|
10259
|
+
{ id: "gpt-5.1-codex", label: "GPT-5.1 Codex", tier: "free", note: "Code specialist" },
|
|
10260
|
+
{ id: "deepseek-v4-pro", label: "DeepSeek V4 Pro", tier: "free", note: "Strong coder \xB7 great value" },
|
|
10261
|
+
{ id: "deepseek-v4-flash", label: "DeepSeek V4 Flash", tier: "free", note: "Fast \xB7 very cheap" },
|
|
10262
|
+
{ id: "gapgpt-qwen-3.6", label: "Qwen 3.6", tier: "free", note: "Good value" },
|
|
10263
|
+
// ── Subscription (locked until billing is enabled) — premium ──────
|
|
10264
|
+
{ id: "claude-opus-4-8", label: "Claude Opus 4.8", tier: "subscription", note: "Top quality", vision: true },
|
|
10265
|
+
{ id: "gpt-5.2-pro", label: "GPT-5.2 Pro", tier: "subscription", note: "Premium" },
|
|
10266
|
+
{ id: "gemini-3-pro-preview", label: "Gemini 3 Pro", tier: "subscription", note: "Premium", vision: true }
|
|
10267
|
+
];
|
|
10268
|
+
var FREE_IDS = new Set(BUILDER_MODELS.filter((m) => m.tier === "free").map((m) => m.id));
|
|
10269
|
+
|
|
10333
10270
|
// ../shared/src/tokens.ts
|
|
10334
10271
|
var colors = {
|
|
10335
10272
|
brand: "#6C4CF1",
|
|
@@ -10361,6 +10298,175 @@ var toggle = {
|
|
|
10361
10298
|
knobTo: "22px"
|
|
10362
10299
|
};
|
|
10363
10300
|
|
|
10301
|
+
// ../sandbox/src/index.ts
|
|
10302
|
+
var FORBIDDEN_API_PATTERNS = [
|
|
10303
|
+
{ re: /getUserMedia/, name: "getUserMedia" },
|
|
10304
|
+
{ re: /navigator\s*\.\s*geolocation/, name: "geolocation" },
|
|
10305
|
+
{ re: /new\s+Notification|Notification\s*\.\s*requestPermission/, name: "Notification" },
|
|
10306
|
+
{ re: /serviceWorker/, name: "serviceWorker" },
|
|
10307
|
+
{ re: /openDatabase/, name: "openDatabase" },
|
|
10308
|
+
{ re: /PaymentRequest|navigator\s*\.\s*payments/, name: "PaymentRequest" },
|
|
10309
|
+
{ re: /navigator\s*\.\s*credentials|PublicKeyCredential|WebAuthn/, name: "WebAuthn" },
|
|
10310
|
+
{ re: /requestPointerLock/, name: "pointerLock" }
|
|
10311
|
+
// allowed via gesture only; flag
|
|
10312
|
+
];
|
|
10313
|
+
var TOP_NAV_PATTERNS = [
|
|
10314
|
+
/window\s*\.\s*top/,
|
|
10315
|
+
/window\s*\.\s*parent\s*\.\s*location/,
|
|
10316
|
+
/\btop\s*\.\s*location/,
|
|
10317
|
+
/\bparent\s*\.\s*location/
|
|
10318
|
+
];
|
|
10319
|
+
var BRAND_NAMES = [
|
|
10320
|
+
"google",
|
|
10321
|
+
"gmail",
|
|
10322
|
+
"apple",
|
|
10323
|
+
"icloud",
|
|
10324
|
+
"microsoft",
|
|
10325
|
+
"outlook",
|
|
10326
|
+
"paypal",
|
|
10327
|
+
"facebook",
|
|
10328
|
+
"instagram",
|
|
10329
|
+
"telegram",
|
|
10330
|
+
"whatsapp",
|
|
10331
|
+
"metamask",
|
|
10332
|
+
"binance",
|
|
10333
|
+
"coinbase"
|
|
10334
|
+
];
|
|
10335
|
+
function extractExternalHosts(html) {
|
|
10336
|
+
const scrubbed = html.replace(/\bxmlns(:[a-z0-9-]+)?\s*=\s*("[^"]*"|'[^']*')/gi, "").replace(/<!DOCTYPE[^>]*>/gi, "");
|
|
10337
|
+
const hosts = /* @__PURE__ */ new Set();
|
|
10338
|
+
const re = /https?:\/\/([a-z0-9.-]+)/gi;
|
|
10339
|
+
let m;
|
|
10340
|
+
while (m = re.exec(scrubbed)) {
|
|
10341
|
+
const host = m[1]?.toLowerCase();
|
|
10342
|
+
if (host) hosts.add(host);
|
|
10343
|
+
}
|
|
10344
|
+
return [...hosts];
|
|
10345
|
+
}
|
|
10346
|
+
function looksLikeHtml(root) {
|
|
10347
|
+
const hasEl = root.querySelector("*") != null;
|
|
10348
|
+
return hasEl;
|
|
10349
|
+
}
|
|
10350
|
+
function validate(html, opts) {
|
|
10351
|
+
const errors = [];
|
|
10352
|
+
const warnings = [];
|
|
10353
|
+
const size = Buffer.byteLength(html, "utf8");
|
|
10354
|
+
const policy = opts.policy ?? postPolicy;
|
|
10355
|
+
const on = (id) => ruleActive(id, policy);
|
|
10356
|
+
if (on("size_limit") && size > opts.maxBytes) {
|
|
10357
|
+
errors.push({
|
|
10358
|
+
code: "SIZE_EXCEEDED",
|
|
10359
|
+
message: `Artifact is ${size} bytes; limit is ${opts.maxBytes}.`
|
|
10360
|
+
});
|
|
10361
|
+
}
|
|
10362
|
+
const root = (0, import_node_html_parser.parse)(html, {
|
|
10363
|
+
lowerCaseTagName: true,
|
|
10364
|
+
comment: true,
|
|
10365
|
+
voidTag: { closingSlash: true }
|
|
10366
|
+
});
|
|
10367
|
+
if (!looksLikeHtml(root)) {
|
|
10368
|
+
errors.push({ code: "NOT_HTML", message: "Content is not parseable as HTML." });
|
|
10369
|
+
return { ok: false, errors, warnings };
|
|
10370
|
+
}
|
|
10371
|
+
if (on("frames") && root.querySelector("iframe, frame, object, embed")) {
|
|
10372
|
+
errors.push({
|
|
10373
|
+
code: "FRAME_TAG",
|
|
10374
|
+
message: "Frames are not allowed (iframe/frame/object/embed)."
|
|
10375
|
+
});
|
|
10376
|
+
}
|
|
10377
|
+
if (on("base_tag") && root.querySelector("base")) {
|
|
10378
|
+
errors.push({ code: "BASE_TAG", message: "<base> tags are not allowed." });
|
|
10379
|
+
}
|
|
10380
|
+
if (on("meta_refresh")) {
|
|
10381
|
+
for (const meta of root.querySelectorAll("meta")) {
|
|
10382
|
+
if ((meta.getAttribute("http-equiv") || "").toLowerCase() === "refresh") {
|
|
10383
|
+
errors.push({ code: "META_REFRESH", message: "meta-refresh redirects are not allowed." });
|
|
10384
|
+
break;
|
|
10385
|
+
}
|
|
10386
|
+
}
|
|
10387
|
+
}
|
|
10388
|
+
if (on("password_input")) {
|
|
10389
|
+
for (const input of root.querySelectorAll("input")) {
|
|
10390
|
+
if ((input.getAttribute("type") || "").toLowerCase() === "password") {
|
|
10391
|
+
errors.push({
|
|
10392
|
+
code: "PASSWORD_INPUT",
|
|
10393
|
+
message: 'Password inputs are not allowed (<input type="password">).'
|
|
10394
|
+
});
|
|
10395
|
+
break;
|
|
10396
|
+
}
|
|
10397
|
+
}
|
|
10398
|
+
}
|
|
10399
|
+
if (on("top_navigation")) {
|
|
10400
|
+
for (const re of TOP_NAV_PATTERNS) {
|
|
10401
|
+
if (re.test(html)) {
|
|
10402
|
+
errors.push({
|
|
10403
|
+
code: "TOP_NAVIGATION",
|
|
10404
|
+
message: "Top-level navigation attempts are not allowed.",
|
|
10405
|
+
detail: re.source
|
|
10406
|
+
});
|
|
10407
|
+
break;
|
|
10408
|
+
}
|
|
10409
|
+
}
|
|
10410
|
+
}
|
|
10411
|
+
if (on("forbidden_apis")) {
|
|
10412
|
+
for (const { re, name } of FORBIDDEN_API_PATTERNS) {
|
|
10413
|
+
if (re.test(html)) {
|
|
10414
|
+
errors.push({ code: "FORBIDDEN_API", message: `Forbidden API used: ${name}.`, detail: name });
|
|
10415
|
+
}
|
|
10416
|
+
}
|
|
10417
|
+
}
|
|
10418
|
+
if (on("external_urls")) {
|
|
10419
|
+
const hosts = extractExternalHosts(html);
|
|
10420
|
+
const offending = hosts.filter(
|
|
10421
|
+
(h) => !opts.allowedOrigins.some((allowed) => h === allowed || h.endsWith("." + allowed))
|
|
10422
|
+
);
|
|
10423
|
+
for (const host of offending) {
|
|
10424
|
+
errors.push({
|
|
10425
|
+
code: "EXTERNAL_URL",
|
|
10426
|
+
message: `External URL not in allowlist: ${host}`,
|
|
10427
|
+
detail: host
|
|
10428
|
+
});
|
|
10429
|
+
}
|
|
10430
|
+
}
|
|
10431
|
+
if (on("external_scripts")) {
|
|
10432
|
+
for (const s of root.querySelectorAll("script")) {
|
|
10433
|
+
const src = s.getAttribute("src");
|
|
10434
|
+
if (src && /^(https?:)?\/\//i.test(src.trim())) {
|
|
10435
|
+
errors.push({
|
|
10436
|
+
code: "EXTERNAL_SCRIPT",
|
|
10437
|
+
message: `External script not allowed (inline your code): ${src}`,
|
|
10438
|
+
detail: src
|
|
10439
|
+
});
|
|
10440
|
+
}
|
|
10441
|
+
}
|
|
10442
|
+
}
|
|
10443
|
+
if (on("external_links")) {
|
|
10444
|
+
for (const a of root.querySelectorAll("a")) {
|
|
10445
|
+
const href = (a.getAttribute("href") || "").trim();
|
|
10446
|
+
if (/^(https?:)?\/\//i.test(href)) {
|
|
10447
|
+
errors.push({
|
|
10448
|
+
code: "EXTERNAL_LINK",
|
|
10449
|
+
message: `External link not allowed: ${href}`,
|
|
10450
|
+
detail: href
|
|
10451
|
+
});
|
|
10452
|
+
}
|
|
10453
|
+
}
|
|
10454
|
+
}
|
|
10455
|
+
const lower = html.toLowerCase();
|
|
10456
|
+
const hasCredField = root.querySelector('input[type="email"], input[type="text"], input[type="tel"]') != null && /(password|passcode|otp|verify|sign\s*in|log\s*in|account)/i.test(html);
|
|
10457
|
+
if (hasCredField) {
|
|
10458
|
+
const brand = BRAND_NAMES.find((b) => lower.includes(b));
|
|
10459
|
+
if (brand) {
|
|
10460
|
+
warnings.push({
|
|
10461
|
+
code: "BRAND_CREDENTIAL",
|
|
10462
|
+
message: `Possible phishing: brand "${brand}" near a credential field.`,
|
|
10463
|
+
detail: brand
|
|
10464
|
+
});
|
|
10465
|
+
}
|
|
10466
|
+
}
|
|
10467
|
+
return { ok: errors.length === 0, errors, warnings };
|
|
10468
|
+
}
|
|
10469
|
+
|
|
10364
10470
|
// src/bundle.ts
|
|
10365
10471
|
var MIME = {
|
|
10366
10472
|
".css": "text/css",
|
|
@@ -10634,7 +10740,9 @@ var FIXES = {
|
|
|
10634
10740
|
TOP_NAVIGATION: "Remove top-level navigation (window.top / parent.location). The artifact runs sandboxed and can't drive the page it's embedded in.",
|
|
10635
10741
|
FORBIDDEN_API: "This browser API is blocked in the sandbox (e.g. getUserMedia, geolocation, serviceWorker, Notification, PaymentRequest, WebAuthn). Remove it or gate it behind a no-op fallback.",
|
|
10636
10742
|
PASSWORD_INPUT: `Password inputs aren't allowed (anti-phishing). Remove <input type="password">.`,
|
|
10637
|
-
EXTERNAL_URL: "References a host that isn't on the allowlist \u2014 often a call to your own backend/API or a third-party script. vibed artifacts are a single static file: inline the asset, drop the call, or move it to an allowlisted CDN (fonts.googleapis.com, cdn.jsdelivr.net, unpkg.com, cdnjs.cloudflare.com, fonts.gstatic.com)."
|
|
10743
|
+
EXTERNAL_URL: "References a host that isn't on the allowlist \u2014 often a call to your own backend/API or a third-party script. vibed artifacts are a single static file: inline the asset, drop the call, or move it to an allowlisted CDN (fonts.googleapis.com, cdn.jsdelivr.net, unpkg.com, cdnjs.cloudflare.com, fonts.gstatic.com).",
|
|
10744
|
+
EXTERNAL_SCRIPT: "Loads JavaScript from an external <script src>. Creator-selected packages aren't allowed \u2014 inline the code you need directly into the file.",
|
|
10745
|
+
EXTERNAL_LINK: "Links off the platform (<a href> to another site). Posts can't send viewers elsewhere \u2014 remove the link or make it in-page (e.g. an anchor to a section)."
|
|
10638
10746
|
};
|
|
10639
10747
|
var KB = 1024;
|
|
10640
10748
|
function fmtSize(bytes) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amirhosseinnateghi/vibed-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "make it vibed — check, bundle, and publish a project to vibed from the terminal",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -11,7 +11,10 @@
|
|
|
11
11
|
".": "./dist/index.js",
|
|
12
12
|
"./lib": "./src/lib.ts"
|
|
13
13
|
},
|
|
14
|
-
"files": [
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
15
18
|
"publishConfig": {
|
|
16
19
|
"access": "public"
|
|
17
20
|
},
|