diff --git a/package.json b/package.json index aeff635..6547281 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@adobe/aio-cli-plugin-api-mesh", - "version": "5.6.0", + "version": "5.6.1", "description": "Adobe I/O CLI plugin to develop and manage API mesh sources", "keywords": [ "oclif-plugin" diff --git a/src/constants.js b/src/constants.js index 09bf8dd..6ef8f34 100644 --- a/src/constants.js +++ b/src/constants.js @@ -4,6 +4,8 @@ const dotenv = require('dotenv'); dotenv.config(); const clientEnv = getCliEnv(); +const MAX_SECRET_SIZE_BYTES = 5 * 1024; // 5 KB — matches Cloudflare's per-secret limit + const StageConstants = { DEV_CONSOLE_BASE_URL: 'https://developers-stage.adobe.io/console', DEV_CONSOLE_API_KEY: 'adobe-api-manager-sms-stage', @@ -12,6 +14,8 @@ const StageConstants = { SMS_BASE_URL: 'https://graph-stage.adobe.io/api-admin', MESH_BASE_URL: 'https://edge-stage-graph.adobe.io/api', SMS_API_KEY: 'adobe-graph-stage-onboarding', + MAX_SECRET_COUNT: 50, + MAX_SECRET_SIZE_BYTES, }; const ProdConstants = { @@ -23,6 +27,8 @@ const ProdConstants = { MESH_BASE_URL: 'https://edge-graph.adobe.io/api', MESH_SANDBOX_BASE_URL: 'https://edge-sandbox-graph.adobe.io/api', SMS_API_KEY: 'adobe-graph-prod', + MAX_SECRET_COUNT: 50, + MAX_SECRET_SIZE_BYTES, }; const envConstants = clientEnv === 'stage' ? StageConstants : ProdConstants; @@ -38,4 +44,6 @@ module.exports = { MESH_BASE_URL: process.env.MESH_BASE_URL || envConstants.MESH_BASE_URL, MESH_SANDBOX_BASE_URL: process.env.MESH_SANDBOX_BASE_URL || envConstants.MESH_SANDBOX_BASE_URL, SMS_API_KEY: process.env.SMS_API_KEY || envConstants.SMS_API_KEY, + MAX_SECRET_COUNT: process.env.MAX_SECRET_COUNT || envConstants.MAX_SECRET_COUNT, + MAX_SECRET_SIZE_BYTES: process.env.MAX_SECRET_SIZE_BYTES || envConstants.MAX_SECRET_SIZE_BYTES, }; diff --git a/src/utils.js b/src/utils.js index 861c205..fb0e05e 100644 --- a/src/utils.js +++ b/src/utils.js @@ -10,6 +10,9 @@ const parseEnv = require('envsub/js/envsub-parser'); const os = require('os'); const chalk = require('chalk'); const crypto = require('crypto'); +const CONSTANTS = require('./constants'); + +const { MAX_SECRET_COUNT, MAX_SECRET_SIZE_BYTES } = CONSTANTS; /** * @returns returns the root directory of the project @@ -513,6 +516,27 @@ async function interpolateSecrets(secretsFilePath, command) { } } +/** + * Validates that each individual secret value does not exceed MAX_SECRET_SIZE_BYTES + * (5 KB — Cloudflare's per-secret limit). + * the YAML serialization of that value is used for the size measurement. + * + * @param {object} parsedSecrets Parsed secrets object from YAML + */ +function validateSecretsSize(parsedSecrets) { + for (const [key, value] of Object.entries(parsedSecrets)) { + const valueString = typeof value === 'string' ? value : YAML.stringify(value); + const valueSizeBytes = Buffer.byteLength(valueString, 'utf8'); + if (valueSizeBytes > MAX_SECRET_SIZE_BYTES) { + throw new Error( + chalk.red( + `Secret "${key}" exceeds the 5 KB size limit. Please reduce its size and try again.`, + ), + ); + } + } +} + /** * Parse secrets YAML content. * @@ -544,7 +568,21 @@ async function parseSecrets(secretsContent) { if (typeof parsedSecrets === 'string') { throw new Error(chalk.red('Please provide a valid YAML in key:value format.')); } + + const numSecrets = Object.entries(parsedSecrets).length; + + if (numSecrets > MAX_SECRET_COUNT) { + throw new Error( + chalk.red( + `Number of secrets exceeds limit. Maximum allowed number of secrets is ${MAX_SECRET_COUNT}`, + ), + ); + } + + validateSecretsSize(parsedSecrets); + const secretsYamlString = YAML.stringify(parsedSecrets); + return secretsYamlString; //TODO: here we will encrypt secrets and return. } catch (err) { throw new Error(chalk.red(getSecretsYamlParseError(err)));