Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions DEPENDENCY_SECURITY_GUIDELINES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Dependency Security and Vulnerability Management Guidelines

This document outlines the procedures and principles for managing vulnerabilities and dependency updates in this repository. Given the complex and legacy nature of some dependencies in this project, these guidelines ensure that security fixes do not introduce breaking changes.

These rules apply to all contributors, including human developers, automated dependency tools, and AI coding assistants.

---

## Core Principles

### 1. Factual Verification (No Assumptions)
* **Verify Usage:** Never assume a package is vulnerable or safe based solely on security audit reports (like `npm audit`). Reports flag version ranges, not actual usage.
* **Ground Truth:** Always inspect the source code in `node_modules` or the repository to verify if the vulnerable code path is actually reachable or executed in this application.
* **Exhaustive Search:** Check all execution paths when tracing usage, not just the first few results.

### 2. Handle Missing Context Explicitly
* **Missing Dependencies:** If `node_modules` is not installed, do not infer behavior. Install dependencies to inspect the real code.
* **Dynamic Imports:** Be aware of dynamic path construction or unusual import patterns that might hide dependency usage.
* **Acknowledge Limits:** If usage cannot be factually verified, document the limitation clearly rather than guessing.

---

## Vulnerability Fix Workflow

When a vulnerability is identified, follow this prioritized, risk-averse approach:

### 1. Standard Audit Fix
Attempt a standard `npm audit fix` first. This applies safe, non-breaking updates that respect the version constraints in `package.json`.

### 2. Minor and Patch Updates
If the audit fix does not resolve the issue, consider a manual minor or patch update (e.g., `1.0.1` -> `1.0.2`). These are generally safe and unlikely to impact functionality.

### 3. Major Version Updates (High Risk)
If a major version update is required:
* **Research**: Thoroughly research breaking changes introduced in the major version.
* **Scan**: Scan the codebase to identify all areas affected by the breaking changes.
* **Proactive Fixes**: Propose fixes for any code impacted by breaking changes.
* **Verification**: Execute the functional test suite against the local codebase to verify stability.

### 4. Dependency Overrides
* Use the `overrides` field in `package.json` only as a last resort for sub-dependencies that cannot be resolved via parent package updates.
* **Warning**: This forces updates on sub-packages which can break nested dependencies. Thorough testing of the entire application is mandatory.

---

## Internal Packages (`volos-*`, `microgateway-*`)

Internal packages are managed by the team and often reside in sibling directories (e.g., `../microgateway-plugins` or inside `../volos/`) relative to the main project folder.

When dealing with these packages:
1. **Check `node_modules` First:** Always check the codebase via the installed dependencies in `node_modules` first to understand exactly what version and code execution path the project is running.
2. **Holistic Fixes:** If a vulnerability stems from an internal package, do not take shortcuts like using overrides in the top-level `package.json` if it can be fixed at the source. Suggest applying the fix directly to the source codebase of the internal package.

---

## Special Directives for AI Coding Assistants

When an AI agent or Large Language Model (LLM)—acting as an expert Subject Matter Expert (SME) in Software Engineering and AI-Assisted Development—is processing this repository, it must adhere to the following behavioral mandates to ensure 100% accuracy and prevent common failure modes:

* **Countering the 'Helpfulness' Trap:** LLMs often hallucinate plausible-sounding paths or names to appear helpful. You **must** resist this. Never use assumed or placeholder names for files, directories, or packages. If you reference a path, you must have factually verified its existence first.
* **No Guessing:** When faced with a gap in knowledge, an LLM's default behavior is to guess. You **must** override this. Stop, admit the gap, and run the necessary search or ask the user.
* **Human-in-the-Loop with Citation:** Never take a potentially breaking action based on a guess. When presenting options or asking for user guidance, always provide proper factual citations (file paths, line numbers, code snippets) so the user can verify your reasoning.
* **Empirical Behavior Verification (Dual-Version Execution)**: To achieve absolute certainty that an override or upgrade does not introduce regressions, you must employ an empirical approach. Create an isolated test harness to execute the specific code path using the exact import and usage patterns identified in the project. Where feasible, execute this harness against both the legacy and target versions of the dependency, comparing execution results to verify behavioral equivalence. This is the gold standard for factual verification.

2 changes: 1 addition & 1 deletion kubernetes/docker/edgemicro/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM node:24.14.1-bookworm-slim
FROM node:24.15.0-bookworm-slim

# === SECURITY PATCH LAYER ===
# 1. Update OS packages to resolve Debian CVEs
Expand Down
2 changes: 1 addition & 1 deletion kubernetes/docker/edgemicro/Dockerfile.beta
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM node:24.14.1-bookworm-slim
FROM node:24.15.0-bookworm-slim

# === SECURITY PATCH LAYER ===
# 1. Update OS packages to resolve Debian CVEs
Expand Down
17 changes: 17 additions & 0 deletions kubernetes/docker/edgemicro/Dockerfile.distroless
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Stage 1: Build/Install dependencies using standard Node image
FROM node:24.15.0-bookworm-slim AS builder
WORKDIR /app

# Install edgemicro locally so all dependencies are self-contained under /app/node_modules
RUN npm install --omit=dev --omit=optional edgemicro@latest
COPY docker-start.js ./

# Stage 2: Runtime using Distroless
FROM gcr.io/distroless/nodejs24-debian12
WORKDIR /app
COPY --from=builder /app /app

ENV NODE_ENV=production
EXPOSE 8000 8443

CMD ["docker-start.js"]
90 changes: 75 additions & 15 deletions kubernetes/docker/edgemicro/betabuild.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,90 @@
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
echo DIR is $DIR

if [ $# -lt 2 ]; then
echo "Please provide edgemicro version and GCP project id"
echo "Usage: $0 <version> <gcp-project-id> [github-repo]"
echo "Example: $0 for-3.3.9 apigee-microgateway apigee-internal/microgateway"
if [ $# -lt 1 ]; then
echo "Usage: $0 <branch> [semantic-version] [gcp-project-id] [github-repo]"
echo "Example: $0 for-3.3.9"
echo "Example: $0 feat-auth 3.3.9"
exit 1
fi

version=$1
project_id=$2
repo=${3:-apigee-internal/microgateway}
branch=$1
semver=$2
project_id=${3:-apigee-microgateway}
repo=${4:-apigee-internal/microgateway}

#us-west1-docker.pkg.dev/apigee-microgateway/edgemicro-beta
# Assert gcloud login and access to Artifact Registry
gcloud artifacts repositories describe edgemicro-beta --location=us-west1 --project="$project_id" &>/dev/null || { echo "Error: Cannot access Artifact Registry. Check gcloud login."; exit 1; }

# Assert Docker is configured to use gcloud for this registry
if [ ! -f ~/.docker/config.json ] || ! grep -q "us-west1-docker.pkg.dev" ~/.docker/config.json; then
echo "Error: Docker is not configured for us-west1-docker.pkg.dev."
echo "Run: gcloud auth configure-docker us-west1-docker.pkg.dev"
exit 1
fi



if [ -z "$semver" ]; then
if [ "$branch" == "npm" ]; then
semver=$(npm view edgemicro version)
echo "Auto-fetched latest version $semver from NPM"
elif [[ "$branch" =~ for-([0-9]+\.[0-9]+\.[0-9]+) ]]; then
semver="${BASH_REMATCH[1]}"
echo "Extracted version $semver from branch $branch"
else
echo "Error: Cannot extract version from branch '$branch'."
echo "Please provide the semantic version as the second argument."
exit 1
fi
fi

# Using '|' as the sed delimiter eliminates the need to escape slashes in the repo path
sed -i.bak "s| *edgemicro.*| ${repo}#$version|g" installnode.sh
if [ "$branch" == "npm" ]; then
sed -i.bak "s|npm install.*-g edgemicro.*|npm install --omit=dev --omit=optional -g edgemicro@$semver|g" installnode.sh
else
sed -i.bak "s|npm install.*-g edgemicro.*|npm install --omit=dev --omit=optional -g ${repo}#$branch|g" installnode.sh
fi

# Clear older image and build cache
echo "Clearing older image and cache..."
docker rmi edgemicro-beta:$branch 2>/dev/null || true
docker builder prune -f || true

docker build --provenance=false --no-cache -t edgemicro-beta:$branch $DIR -f Dockerfile.beta

# Query existing tags to find the next beta number
echo "Querying existing tags for public-image-$semver-beta.*"
existing_tags=$(gcloud artifacts tags list --repository=edgemicro-beta --location=us-west1 --package=emg --project="$project_id" 2>/dev/null | grep -o "public-image-$semver-beta\.[0-9]\+")

max=0
for tag in $existing_tags; do
if [[ "$tag" =~ public-image-$semver-beta\.([0-9]+) ]]; then
n="${BASH_REMATCH[1]}"
if (( n > max )); then
max=$n
fi
fi
done

# Build with --platform linux/amd64 to ensure it runs correctly on general-purpose servers (x86_64) instead of ARM64
docker build --platform linux/amd64 --no-cache -t edgemicro-beta:$version $DIR -f Dockerfile.beta
new_n=$((max + 1))
echo "Next beta number is $new_n"
new_tag="public-image-$semver-beta.$new_n"

docker tag edgemicro-beta:$version us-west1-docker.pkg.dev/$project_id/edgemicro-beta/emg:$version
docker tag edgemicro-beta:$version us-west1-docker.pkg.dev/$project_id/edgemicro-beta/emg:beta
# Tag and push the new image
docker tag edgemicro-beta:$branch us-west1-docker.pkg.dev/$project_id/edgemicro-beta/emg:$new_tag
docker push us-west1-docker.pkg.dev/$project_id/edgemicro-beta/emg:$new_tag

docker push us-west1-docker.pkg.dev/$project_id/edgemicro-beta/emg:$version
docker push us-west1-docker.pkg.dev/$project_id/edgemicro-beta/emg:beta
# Add deprecated tags to older images
for tag in $existing_tags; do
if [[ "$tag" =~ public-image-$semver-beta\.([0-9]+) ]]; then
n="${BASH_REMATCH[1]}"
dep_tag="deprecated-public-image-$semver-beta.$n"

echo "Adding tag $dep_tag to existing tag $tag"
gcloud artifacts docker tags add "us-west1-docker.pkg.dev/$project_id/edgemicro-beta/emg:$tag" "us-west1-docker.pkg.dev/$project_id/edgemicro-beta/emg:$dep_tag"
fi
done

rm installnode.sh
mv installnode.sh.bak installnode.sh
Expand Down
54 changes: 54 additions & 0 deletions kubernetes/docker/edgemicro/docker-start.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
const fs = require('fs');
const path = require('path');
const { spawn } = require('child_process');

const os = require('os');

const apigeeRoot = '/opt/apigee';
const configDir = path.join(os.homedir(), '.edgemicro');

// Ensure directories exist
fs.mkdirSync(configDir, { recursive: true });
fs.mkdirSync(path.join(apigeeRoot, 'logs'), { recursive: true });
fs.mkdirSync(path.join(apigeeRoot, 'plugins'), { recursive: true });

// Decode base64 config if provided
if (process.env.EDGEMICRO_CONFIG) {
const org = process.env.EDGEMICRO_ORG || 'default';
const env = process.env.EDGEMICRO_ENV || 'default';
const configPath = path.join(configDir, `${org}-${env}-config.yaml`);
const decoded = Buffer.from(process.env.EDGEMICRO_CONFIG, 'base64').toString('utf8');
fs.writeFileSync(configPath, decoded);
}

// Prepare start arguments
const args = ['start'];
if (process.env.EDGEMICRO_ORG) args.push('-o', process.env.EDGEMICRO_ORG);
if (process.env.EDGEMICRO_ENV) args.push('-e', process.env.EDGEMICRO_ENV);
if (process.env.EDGEMICRO_KEY) args.push('-k', process.env.EDGEMICRO_KEY);
if (process.env.EDGEMICRO_SECRET) args.push('-s', process.env.EDGEMICRO_SECRET);

const port = process.env.EDGEMICRO_PORT || '8000';
args.push('-r', port);

const pluginDir = process.env.EDGEMICRO_PLUGIN_DIR || path.join(apigeeRoot, 'plugins');
args.push('-d', pluginDir);

if (process.env.EDGEMICRO_PROCESSES) {
args.push('-p', process.env.EDGEMICRO_PROCESSES);
}

console.log(`Starting edgemicro with args: ${args.join(' ')}`);

const cliScript = '/app/node_modules/edgemicro/cli/edgemicro';
const child = spawn(process.execPath, [cliScript, ...args], { stdio: 'inherit' });

process.on('SIGTERM', () => {
console.log('Received SIGTERM, stopping edgemicro...');
child.kill('SIGTERM');
});

process.on('SIGINT', () => {
console.log('Received SIGINT, stopping edgemicro...');
child.kill('SIGINT');
});
2 changes: 1 addition & 1 deletion kubernetes/docker/edgemicro/dockerbuild.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ project_id=$2

if [ $# -eq 2 ]; then

sed -i.bak "s/ *edgemicro.*/ edgemicro@$version/g" installnode.sh
sed -i.bak "s|npm install.*-g edgemicro.*|npm install --omit=dev --omit=optional -g edgemicro@$version|g" installnode.sh
docker build --no-cache -t edgemicro:$version $DIR
docker tag edgemicro:$version gcr.io/$project_id/edgemicro:$version
docker tag edgemicro:$version gcr.io/$project_id/edgemicro:latest
Expand Down
8 changes: 7 additions & 1 deletion kubernetes/docker/edgemicro/installnode.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
#!/bin/bash
set echo off
set -e

# 1. Install globally (this line is targeted by betabuild.sh's sed replacement)
npm install --omit=dev --omit=optional -g edgemicro

# 2. Navigate to the globally installed package and run install to apply committed overrides
cd "$(npm root -g)/edgemicro"
npm install --omit=dev --omit=optional
Loading