diff --git a/CLAUDE.md b/CLAUDE.md index c67e752..bd1e176 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,61 +4,137 @@ A Python development framework for writing, testing, and deploying AWS Lambda functions. Provides local execution environment that simulates Lambda's `event` and `context` objects, Cognito integration for testing authenticated APIs, API Gateway management via Swagger, and Makefile-driven deployment workflow. +**Status:** Legacy framework (created ~2015). Likely superseded by AWS SAM CLI or CDK for new Lambda development. Evaluate before building new functionality on top of it. + **Use case**: Rapid Lambda function development with local testing before deploying to AWS. +## Architecture + +``` + +------------------+ + | Makefile | (orchestrator — build, test, deploy, connect) + +--------+---------+ + | + +----------------+----------------+ + | | | + +-----v-----+ +------v------+ +------v------+ + | run.py | | connect.py | | dist/*.zip | + | (local | | (API test | | (deploy to | + | runner) | | client) | | Lambda) | + +-----+-----+ +------+------+ +------+------+ + | | | + +-----v-----+ +------v------+ | + | MockContext| |apiconnect.py| | + | (Lambda | | (SigV4 + | | + | context) | | Cognito) | | + +-----+-----+ +------+------+ | + | | | + +-----v----------------v----------------v-----+ + | lib/ | + | common.py — shared utilities | + | env.py — secrets (generated from S3) | + +----------------------------------------------+ + | + +-----v-----+ + | src/ | one subdirectory per Lambda function + | / | each has index.py with handler(event, context) + +-----------+ +``` + +### Data Flow: Secrets (env.py generation) + +``` +S3 bucket (sportarchive-${ENV}-code) + └── ${ENV}_creds (key-value file) + │ + ▼ `make .env` (aws s3 cp) + ./lib/env.py (Python module with variables) + │ + ├── imported by lib/common.py, tests/MockContext.py, lib/apiconnect.py + └── bundled into every dist/*.zip ⚠ SECURITY: secrets in artifact +``` + +**WARNING:** Secrets are bundled into Lambda ZIP packages. This is a known security issue (see FINDINGS.md C2). For new functions, use AWS Secrets Manager or SSM Parameter Store instead. + ## Tech Stack -- **Language**: Python 2.7 (legacy; may need upgrade to Python 3.9+) +- **Language**: Python 3.x (code uses Python 3 syntax like `http.client`, `urllib.error`) +- **Lambda Runtime**: python3.8 (hardcoded in Makefile — **EOL, upgrade to python3.12**) - **AWS Services**: Lambda, API Gateway, Cognito, S3 - **Tools**: - `aws-cli` 1.9+ — AWS resource management - - `aws-apigateway-importer` — Swagger-based API deployment (Git submodule) + - `aws-apigateway-importer` — Swagger-based API deployment (Git submodule, **deprecated**) - `pip` 6+ — Python package management -- **Testing**: `unittest` module +- **Testing**: `unittest` module (tests require live AWS credentials for Cognito) - **Build**: `Makefile` — all operations (build, test, deploy, run) ## Quick Start ```bash -# Setup -pip install -r requirements.txt +# 1. Setup AWS credentials aws configure # Set up AWS credentials -# Create a new Lambda function -# 1. Create src// directory -# 2. Add src//index.py with handler function -# 3. Add src//__init__.py +# 2. Install dependencies +pip install -r requirements.txt -# Run locally -make run/ EVENT=events/test.json +# 3. Generate env.py from S3 (REQUIRED before anything works) +make .env # uses DEV by default +make .env ENV=prod # for production -# Create function in AWS Lambda (first time) -make create/ +# 4. Create a new Lambda function +# - Create src// directory +# - Add src//index.py with handler(event, context) +# - Add src//__init__.py -# Deploy (update existing function) -make deploy/ +# 5. Run locally +make run/ EVENT=events/test.json -# Run unit tests +# 6. Run unit tests make test make test/ -# Connect to secure API via Cognito +# 7. Create function in AWS Lambda (first time) +make create/ DESC='My function description' + +# 8. Deploy (update existing function) +make deploy/ +make deploy/ ENV=prod + +# 9. Test API Gateway endpoint with Cognito auth make connect ENDPOINT=/my_endpoint METHOD=GET QUERY="param=value" ``` +**Prerequisites that trip up new developers:** +- `make .env` must run before `run`, `test`, `connect`, `create`, or `deploy` — it downloads secrets from S3 +- AWS credentials must be configured with access to the S3 code bucket +- If `env.py` is missing, ALL imports fail (lib/common.py imports it at module level) + ## Project Structure -- `src/` — Lambda function source code (one subdirectory per function) - - `src//index.py` — Entry point with `handler(event, context)` function - - `src//__init__.py` — Required for Python module -- `lib/` — Shared libraries imported by multiple functions -- `tests/` — Unit tests (files named `test*.py`) -- `swagger/` — Swagger YAML files for API Gateway definition -- `scripts/` — Utility scripts -- `requirements.txt` — Python dependencies (installed into each function's ZIP) -- `Makefile` — Build, test, deploy automation -- `run.py` — Local Lambda simulator script -- `connect.py` — API Gateway + Cognito test client +``` +. +├── src/ Lambda function source code +│ ├── / +│ │ ├── __init__.py +│ │ └── index.py handler(event, context) entry point +│ └── example_func/ Example function for reference +├── lib/ Shared libraries (copied into every ZIP) +│ ├── __init__.py +│ ├── common.py Utilities: payload parsing, identity caching, error class +│ ├── apiconnect.py Cognito auth + SigV4 signed API requests +│ └── env.py ⚠ GENERATED — secrets from S3 (gitignored) +├── tests/ Unit tests +│ ├── MockContext.py Lambda context simulator (makes live Cognito calls!) +│ ├── test*.py Test files (auto-discovered by unittest) +│ └── data/ Test event JSON files +├── swagger/ API Gateway Swagger YAML definitions +├── scripts/ Utility scripts (runtime update) +├── run.py Local Lambda executor +├── connect.py API Gateway + Cognito test client +├── Makefile Build/test/deploy automation +├── requirements.txt Python deps (boto3, requests) +└── .env ⚠ GENERATED — local copy of env.py (gitignored) +``` ## Dependencies @@ -70,34 +146,71 @@ make connect ENDPOINT=/my_endpoint METHOD=GET QUERY="param=value" **Internal:** - `lib/` directory — shared code copied into each function's ZIP -- `.env` file — environment variables and secrets (downloaded from S3) - - - +- `lib/env.py` — environment variables and secrets (downloaded from S3, gitignored) + +**Python packages (requirements.txt):** +- `boto3==1.12.8` — AWS SDK (very outdated — current is 1.35+) +- `requests==2.31.0` — HTTP client (has open CVE, PR #6 pending for 2.32.2) + + + + + +## Makefile Targets Reference + +| Target | Description | Example | +|--------|-------------|---------| +| `make help` | Show all available commands | `make` | +| `make run/` | Run function locally | `make run/example_func EVENT=test.json VERBOSE=1` | +| `make test` | Run all unit tests | `make test VERBOSE=1` | +| `make test/` | Run specific test | `make test/ExampleFunc` | +| `make create/` | Create new Lambda function in AWS | `make create/signin DESC='User signin'` | +| `make deploy/` | Deploy function update to AWS | `make deploy/signin ENV=prod` | +| `make deploy` | Deploy ALL functions | `make deploy ENV=prod` | +| `make dist/.zip` | Build deployment ZIP | `make dist/signin.zip` | +| `make dist` | Build ALL ZIPs | `make dist` | +| `make .env` | Download secrets from S3 | `make .env ENV=prod` | +| `make setmem/` | Set function memory | `make setmem/signin SIZE=512` | +| `make connect` | Test API endpoint | `make connect METHOD=POST ENDPOINT=/signin PAYLOAD=tests/data/signin.json` | +| `make api` | Deploy API Gateway | `make api VERS=0.6 UPDATE= STAGE=dev` | +| `make clean` | Remove build artifacts | `make clean` | ## API / Interface **Lambda function structure:** ```python # src//index.py +from lib import common +# from lib import env # if you need secrets + def handler(event, context): # event: API Gateway event or custom event JSON - # context: Lambda context object (simulated locally) + # context: Lambda context object (simulated locally by MockContext) + # context.identity.cognito_identity_id — Cognito user identity + # context.aws_request_id — unique request ID + # context.function_name — function name return { "statusCode": 200, "body": json.dumps({"message": "success"}) } ``` -**Makefile commands:** -- `make create/` — Create new Lambda function in AWS -- `make deploy/ [ENV=prod]` — Update existing Lambda function -- `make run/ [EVENT=file] [VERBOSE=1]` — Run locally -- `make dist/.zip` — Build deployment ZIP -- `make test` — Run all unit tests -- `make connect ENDPOINT=/path METHOD=GET` — Test API Gateway with Cognito auth +**Error handling pattern (from example_func):** +```python +try: + # ... your logic ... +except common.SAError as e: + print(e) + raise Exception('error: custom_failed') +except KeyError as e: + print(common.SAError("accessing unknown property")) + raise Exception('error: property_failed') +except Exception as e: + print(common.SAError("unknown error of type %s" % type(e))) + raise Exception('error: generic_failed') +``` -**Environment variables** (from `.env` file in S3): +**Environment variables** (from `lib/env.py`, sourced from S3): - `API_HOST` — API Gateway host - `API_ENDPOINT` — Full API Gateway URL - `API_STAGE` — Stage name (e.g., `/dev`, `/prod`) @@ -109,31 +222,29 @@ def handler(event, context): - **One function per directory**: Each Lambda function has its own `src//` directory - **Shared lib directory**: Common code in `lib/` is copied into every function's ZIP -- **Environment from S3**: Config and secrets are stored in S3, downloaded at build time, and included in ZIP as `lib/env.py` -- **Swagger-first API design**: API Gateway is defined in Swagger YAML, auto-deployed via `aws-apigateway-importer` +- **Environment from S3**: Config and secrets stored in S3, downloaded at build time, included in ZIP as `lib/env.py` +- **Swagger-first API design**: API Gateway defined in Swagger YAML, auto-deployed via `aws-apigateway-importer` - **Local simulation**: `run.py` mocks Lambda's `event` and `context` objects for local testing - **Cognito integration**: `connect.py` fetches temporary credentials and signs API requests +- **Identity caching**: `.identity_` files cache Cognito identities between runs ## Environment **Required local setup:** -- Python 2.7 (or 3.x if migrated) +- Python 3.x (3.9+ recommended; the code already uses Python 3 imports) - AWS CLI configured with credentials - S3 bucket for Lambda function storage -- S3 bucket for `.env` file storage +- S3 bucket containing `${ENV}_creds` file **Required AWS resources:** - Lambda execution role (IAM role with permissions for your functions) - API Gateway (created/updated via Swagger) - Cognito Identity Pool (if using authenticated APIs) -**Environment variables** (local development): -- `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_DEFAULT_REGION` — Set via `aws configure` - -**Makefile configuration** (hardcoded in Makefile — should be parameterized): -- `AWS_BUCKET_CODE` — S3 bucket for Lambda ZIPs -- `IAM_ROLE` — Lambda execution role ARN -- `ENV` — Environment (DEV, QA, PROD) +**Makefile configuration** (currently hardcoded — should be parameterized): +- `sportarchive-${ENV}-code` — S3 bucket for Lambda ZIPs and creds +- `lambda_orchestrate_role` — Lambda execution role name +- `ENV` — Environment: dev (default), prod - `PROFILE` — AWS CLI profile (optional) @@ -142,53 +253,74 @@ def handler(event, context): ## Deployment **First-time setup:** -1. Create `.env` file with required variables -2. Upload `.env` to S3 at the path expected by Makefile -3. Update Makefile with correct S3 buckets and IAM role ARNs -4. Create Lambda functions: `make create/` +1. Create `${ENV}_creds` file with required variables (see Environment variables above) +2. Upload to S3: `aws s3 cp ${ENV}_creds s3://sportarchive-${ENV}-code/${ENV}_creds` +3. Update Makefile with correct S3 buckets and IAM role ARNs if different +4. Run `make .env` to download and generate `lib/env.py` +5. Create Lambda functions: `make create/ DESC='description'` **Updates:** 1. Make code changes -2. Test locally: `make run/` -3. Deploy: `make deploy/` +2. Test locally: `make run/ EVENT=tests/data/test.json` +3. Run tests: `make test` +4. Deploy: `make deploy/` (or `make deploy/ ENV=prod`) -**API Gateway deployment:** -1. Update Swagger YAML in `swagger/` directory -2. Run `aws-apigateway-importer` (see Makefile) -3. Deploy to stage +**What `make deploy/` does:** +1. Builds `dist/.zip` (function code + lib/ + build/ dependencies) +2. Uploads ZIP to S3 (`s3://sportarchive-${ENV}-code/lambda/.zip`) +3. Updates Lambda function code (`aws lambda update-function-code`) +4. Runs `scripts/lambda_configuration_update.sh` (sets runtime to python3.8) - + ## Testing **Unit tests:** -- Write test cases in `tests/test*.py` -- Each test file can contain multiple test classes -- Import Lambda handlers: `from src..index import handler` -- Run: `make test` (all) or `make test/` (specific) +- Files: `tests/test*.py` (auto-discovered by `python -m unittest discover`) +- Each test file can contain multiple test classes inheriting `unittest.TestCase` +- Import handlers: `from src..index import handler` +- Run: `make test` (all) or `make test/` (specific, name is part after "test") + +**IMPORTANT:** Tests require `lib/env.py` to exist (run `make .env` first). MockContext makes live Cognito API calls, so AWS credentials must be configured. **Local integration testing:** -- Use `make run/ EVENT=events/.json` -- Create test event JSON files for different scenarios -- Verify output matches expected results +- `make run/ EVENT=events/.json` +- Create test event JSON files in `tests/data/` +- The `%IDENTITY_ID%` placeholder in event JSON is replaced with actual Cognito identity **API Gateway testing:** -- Use `make connect ENDPOINT=/path METHOD=GET QUERY="key=val"` -- Tests full flow: Cognito auth → API Gateway → Lambda -- Requires Cognito Identity Pool configured +- `make connect ENDPOINT=/path METHOD=GET QUERY="key=val"` +- Full flow: Cognito unauthenticated identity -> temporary credentials -> SigV4 signed request -> API Gateway -> Lambda +- Requires Cognito Identity Pool configured and accessible + +## Known Issues + +See `FINDINGS.md` for the full audit. Key issues: + +1. **Secrets bundled in ZIPs** (Critical) — `lib/env.py` with secrets is included in every Lambda deployment package +2. **Python 3.8 runtime EOL** (High) — Hardcoded in Makefile and scripts; must upgrade to 3.12 +3. **Tests are broken** (Medium) — `testExampleFunc.py` references `src.assets` instead of `src.example_func` +4. **Tests require live AWS** (Medium) — MockContext makes real Cognito API calls +5. **Open security PR** (High) — Dependabot PR #6 for requests CVE-2024-35195 has been open 15+ months +6. **boto3 version very outdated** (Medium) — Pinned to 1.12.8 (Feb 2020), current is 1.35+ ## Gotchas -- **Python 2.7 is EOL**: This framework uses Python 2.7, which is no longer supported. Lambda supports Python 3.9+. Migration recommended. -- **Hardcoded S3 buckets**: Makefile has hardcoded S3 bucket names and IAM role ARNs. Make these configurable. -- **Submodule dependency**: `aws-apigateway-importer` is a Git submodule. Run `git submodule update --init` after cloning. -- **lib/ copied into every function**: Shared code in `lib/` is duplicated in every function's ZIP. For large shared libraries, consider Lambda Layers instead. -- **Environment secrets in ZIP**: The `.env` file is bundled into the function ZIP. For sensitive secrets, use AWS Secrets Manager or Parameter Store instead. -- **Manual API Gateway deployment**: Swagger files must be manually deployed via `aws-apigateway-importer`. Consider AWS SAM or Terraform for automated API management. -- **No Lambda Layers support**: This framework predates Lambda Layers. For shared dependencies, use Layers instead of copying `lib/` into every ZIP. -- **Cognito unauthenticated tokens**: `connect.py` uses unauthenticated Cognito identities. For authenticated testing, add user pool integration. +- **`make .env` is a prerequisite for EVERYTHING** — Without it, `lib/env.py` doesn't exist and all Python imports fail. This is the #1 source of confusion for new developers. +- **Python 3.8 is EOL**: Lambda runtime in Makefile is hardcoded to python3.8. New function creation will fail. Upgrade to python3.12. +- **Hardcoded S3 buckets**: Makefile has hardcoded `sportarchive-${ENV}-code` bucket name. Make this configurable for other environments. +- **Submodule dependency**: `aws-apigateway-importer` is a Git submodule (deprecated). Run `git submodule update --init` after cloning, or use `aws apigateway import-rest-api` instead. +- **lib/ copied into every function**: Shared code in `lib/` is duplicated in every function's ZIP. For large shared libraries, consider Lambda Layers. +- **Secrets in ZIP**: The `env.py` file with secrets is bundled into function ZIPs. Use AWS Secrets Manager or Parameter Store for new functions. +- **Manual API Gateway deployment**: Swagger files must be manually deployed. Consider AWS SAM or CDK. +- **No Lambda Layers support**: Framework predates Lambda Layers. +- **Cognito unauthenticated tokens**: `connect.py` uses unauthenticated Cognito identities. +- **`sed -i` in Makefile**: The `api` target uses GNU `sed -i` which fails on macOS. Use `sed -i ''` for compatibility. +- **Region hardcoded**: `us-east-1` is hardcoded in `lib/apiconnect.py:75`. Cannot test other regions without code change. +- **`run.py` has no timeout**: `MockContext.get_remaining_time_in_millis()` returns infinity. Functions won't timeout locally like they do in Lambda. +- **GitHub backup workflow broken**: `.github/workflows/github-backup.yml` triggers on `develop` branch, but repo uses `master`. - \ No newline at end of file + diff --git a/FINDINGS.md b/FINDINGS.md new file mode 100644 index 0000000..687715a --- /dev/null +++ b/FINDINGS.md @@ -0,0 +1,352 @@ +# AI Audit: aws-lambda-python-local + +**Date:** 2026-02-17 +**Auditor:** Backend Developer Agent (Claude Opus 4.6) +**Repo:** bfansports/aws-lambda-python-local +**Branch:** master (commit 4b753f1) +**Focus:** Execution safety, sandboxing, dependency management, Python version compatibility + +--- + +## Critical + +### C1 — Arbitrary Code Execution via `importlib.import_module` (run.py:36) + +**File:** `run.py` +**Line:** 36 + +```python +module = importlib.import_module('src.{name}.index'.format(name=args.name)) +``` + +The `name` argument is taken directly from CLI input with no validation or sanitization. While this is a local dev tool (not a server), the pattern allows loading any Python module on `PYTHONPATH` by crafting the name argument (e.g., `../../malicious_module`). The `src.{name}.index` pattern provides some path scoping but does not prevent directory traversal via Python module names. + +**Risk:** A malicious event file or CI configuration could exploit this to execute arbitrary code. +**Recommendation:** Validate `args.name` against a whitelist of actual function directories: +```python +import os +valid_funcs = [d for d in os.listdir('src') if os.path.isdir(os.path.join('src', d)) and d != '__pycache__'] +if args.name not in valid_funcs: + sys.exit(f"Unknown function: {args.name}. Available: {', '.join(valid_funcs)}") +``` + +### C2 — Secrets Bundled in Deployment ZIP (Makefile:111-115, 128-130) + +**File:** `Makefile` +**Lines:** 111-115, 128-130 + +The `.env` file is downloaded from S3, converted to `lib/env.py`, and then **zipped into every Lambda deployment package**. This means secrets (API keys, account IDs, Cognito pool IDs) are permanently embedded in the ZIP artifact stored in S3. + +```makefile +.env: + aws s3 cp s3://sportarchive-${ENV}-code/${ENV}_creds ./lib/env.py + cp ./lib/env.py .env +``` + +```makefile +dist/%.zip: src/%/* build/setup.cfg $(wildcard lib/**/*) .env + ... + zip -r -q $@ lib # <-- lib/env.py with secrets included +``` + +**Risk:** Secrets persist in S3 ZIP artifacts, Lambda package history, and local build artifacts. Anyone with S3 read access to the code bucket gets all secrets. +**Recommendation:** Migrate to AWS Secrets Manager or SSM Parameter Store. Lambda functions should fetch secrets at runtime, not bundle them. + +### C3 — Hardcoded AWS Account Info and S3 Buckets (Makefile, multiple lines) + +**File:** `Makefile` +**Lines:** 88, 93, 104, 106-107, 129 + +The S3 bucket name `sportarchive-${ENV}-code` and the IAM role `arn:aws:iam::${AWS_ACCOUNT}:role/lambda_orchestrate_role` are hardcoded. The role ARN has a malformed format (double colon `::` with no account ID between them — it relies on `${AWS_ACCOUNT}` env var which is never set by the Makefile itself). + +```makefile +aws s3 cp $< s3://sportarchive-${ENV}-code/lambda/$( 2.32.2) + +**File:** `requirements.txt` + +PR #6 has been open since November 2024 (15+ months). It bumps `requests` from 2.31.0 to 2.32.2, which includes CVE-2024-35195 (TLS verification bypass when `verify=False` is set on first request in a Session). + +**Risk:** Known security vulnerability in a direct dependency. +**Recommendation:** Merge PR #6 immediately. + +--- + +## Medium + +### M1 — Test File References Wrong Module (tests/testExampleFunc.py:9) + +**File:** `tests/testExampleFunc.py` +**Line:** 9 + +```python +def run_func(**keyword_args): + return src.assets.index.handler( + keyword_args['event'], + keyword_args['context']) +``` + +The function calls `src.assets.index.handler` but the test imports `src.example_func.index` (line 6). There is no `src/assets/` directory in the repo. The test will always fail with `NameError: name 'src' is not defined` (since `src.assets` was never imported) or `ModuleNotFoundError`. + +**Risk:** Tests provide false confidence — they don't actually validate anything. +**Recommendation:** Fix to `src.example_func.index.handler(...)` and verify the test actually runs. + +### M2 — MockContext Makes Live AWS Calls (tests/MockContext.py:31-36, 66-71) + +**File:** `tests/MockContext.py` +**Lines:** 31-36, 66-71 + +```python +res = boto3.client('cognito-identity').get_id( + AccountId=env.AWS_ACCOUNT_ID, + IdentityPoolId=env.IDENTITY_POOL +) +``` + +Both `MockContext` and `_add_cognito_id` make real AWS Cognito API calls during test setup. Unit tests should not require live AWS credentials or network access. + +**Risk:** Tests fail without AWS credentials configured. Tests are not reproducible in CI without AWS access. Flaky due to network dependency. +**Recommendation:** Use `moto` library or `unittest.mock` to stub Cognito calls. Provide a `MockContextUnitTest` that works fully offline (one exists but still calls Cognito in `_add_cognito_id`). + +### M3 — `lib/common.py` Imports `lib.env` at Module Level (line 10) + +**File:** `lib/common.py` +**Line:** 10 + +```python +from lib import env +``` + +`lib/env.py` is generated from S3 download and is gitignored. Any import of `lib.common` will fail with `ImportError` if `env.py` doesn't exist. This affects `run.py`, `connect.py`, and all tests — none can even import without first running `make .env`. + +**Risk:** Confusing error for new developers. `pip install -r requirements.txt` is insufficient to run anything. +**Recommendation:** Guard the import with try/except or move env-dependent functions to a separate module. At minimum, document the dependency prominently. + +### M4 — Identity Cache File Written with No Permissions Check (lib/common.py:60-62) + +**File:** `lib/common.py` +**Lines:** 60-62 + +```python +def put_identity(IdentityId): + myfile = open('.identity_'+env.CONFIG_MODE, 'w') + myfile.write(IdentityId) + myfile.close() +``` + +Cognito Identity IDs are written to local files (`.identity_DEV`, `.identity_PROD`) with default permissions (typically 0644). These files contain AWS identity information. + +**Risk:** Identity tokens readable by other users on shared machines. +**Recommendation:** Use `os.open()` with explicit permissions (0600) or write to a user-specific temp directory. Also use `with` statement for proper file handling. + +### M5 — Shell Injection Risk in Makefile `api` Target (Makefile:57-74) + +**File:** `Makefile` +**Lines:** 57-74 + +```makefile +$(eval ORCHESTRATE_AUTH = 'Basic $(shell echo -n "${ORCHESTRATE_KEY}:" | base64)') +sed -i "s/%ORCH_CREDS%/${ORCHESTRATE_AUTH}/g" ${DST_FILE} +sed -i "s/%AWS_ACCOUNT%/${AWS_ACCOUNT}/g" ${DST_FILE} +``` + +`sed -i` with unescaped variable substitution. If `ORCHESTRATE_KEY` or `AWS_ACCOUNT` contain sed metacharacters, the command will fail or behave unexpectedly. Also, `sed -i` without backup extension is GNU-specific and fails on macOS BSD sed. + +**Risk:** Build failure on macOS; potential for unexpected substitution if env vars contain special characters. +**Recommendation:** Use `sed -i '' ...` for macOS compatibility or use `envsubst` instead of `sed`. + +### M6 — `pip install -t` Without `--upgrade` or Version Pinning (Makefile:120) + +**File:** `Makefile` +**Line:** 120 + +```makefile +pip install -r $^ -t $(@D) +``` + +Dependencies are installed into `build/` without `--upgrade` flag. Only `boto3` and `requests` are pinned. Transitive dependencies are not locked (no `requirements.lock` or `pip freeze` output). + +**Risk:** Non-reproducible builds; different developers get different transitive dependency versions. +**Recommendation:** Add a lock file (`pip-compile` from `pip-tools`) and pin all transitive dependencies. + +### M7 — GitHub Actions Workflow References Non-Existent `develop` Branch + +**File:** `.github/workflows/github-backup.yml` +**Line:** 5 + +```yaml +on: + push: + branches: + - develop +``` + +The repo's default branch is `master`, not `develop`. This workflow never triggers. + +**Risk:** S3 backups are not running. +**Recommendation:** Change to `master` or remove if backups are handled elsewhere. + +--- + +## Low + +### L1 — Inconsistent Indentation in Test File (tests/testExampleFunc.py) + +**File:** `tests/testExampleFunc.py` + +Mixes 4-space and 8+ space indentation. Class body uses ~12 spaces. This is valid Python but hurts readability and suggests copy-paste errors. + +**Recommendation:** Reformat to standard 4-space indentation. + +### L2 — Unused Imports Across Multiple Files + +- `lib/common.py` — `string` (line 8) is imported but never used +- `lib/common.py` — `requests` (line 7) is imported but never used +- `lib/common.py` — `HTTPError` from `urllib.error` (line 9) is imported but never used +- `tests/MockContext.py` — `functools` (line 1), `uuid` (line 3), `copy` (line 5) are unused +- `tests/testExampleFunc.py` — `uuid` (line 2), `copy` (line 3) are unused +- `run.py` — `lib` (line 11) is imported but never used + +**Recommendation:** Remove unused imports. Consider adding `flake8` or `ruff` to CI. + +### L3 — Semicolons in Python (run.py:38) + +```python +event = json.loads(event); +``` + +Trailing semicolon is a style issue (likely a Java/JS habit). Harmless but non-Pythonic. + +### L4 — `connect.py` Description Says "Run a Lambda function locally" (line 11) + +**File:** `connect.py` +**Line:** 11 + +```python +parser = argparse.ArgumentParser(description='Run a Lambda function locally.') +``` + +The description is copy-pasted from `run.py`. Should say "Connect to API Gateway endpoint." + +### L5 — Missing HTTP Methods in `callApi` (lib/apiconnect.py:116-123) + +Only GET, POST, PUT, DELETE are handled. PATCH is mentioned in the Makefile help text but not implemented. HEAD, OPTIONS are also missing. + +**Recommendation:** Add PATCH support or use `requests.request(method, ...)` for generic method support. + +### L6 — `noauth` Argument Logic Inverted (connect.py:28, lib/apiconnect.py:28) + +The `--noauth` flag's help says "Use a new identity even if .identity file exists" but the code checks `if (noauth == 0 and self.IdentityId is False)`. When `--noauth` is provided (noauth=1), the cached identity check is skipped entirely and the code falls through to use `self.IdentityId` which would be the cached value from `common.get_identity()` — the opposite of the intended behavior. + +**Risk:** The `--noauth` flag does not work as documented. +**Recommendation:** Review and fix the conditional logic. + +### L7 — `aws-apigateway-importer` Submodule Is Deprecated + +**File:** `.gitmodules` + +The `aws-apigateway-importer` tool from awslabs was archived years ago. AWS CLI now supports `aws apigateway put-rest-api` and `import-rest-api` natively. + +**Recommendation:** Remove the submodule; use `aws apigateway import-rest-api` or migrate to SAM/CDK. + +--- + +## Agent Skill Improvements + +Recommendations for improving the CLAUDE.md in this repo: + +1. **Add architecture diagram** — The relationship between run.py, connect.py, Makefile, lib/, and src/ is non-obvious. A text diagram helps agents navigate. +2. **Document the env.py generation flow** — The S3 -> .env -> lib/env.py -> ZIP chain is the most confusing part of the codebase. Agents waste tokens rediscovering it. +3. **List all Makefile targets** — Agents need to know available commands without parsing Makefile. +4. **Mark the repo as legacy/inactive** — If this framework has been superseded by SAM or CDK, say so explicitly. Agents should not suggest building on top of it. +5. **Add security section** — Document the secrets-in-ZIP pattern so agents flag it immediately. +6. **Add test running prerequisites** — Document that `make .env` (requires AWS creds) is needed before anything works. + +--- + +## Positive Observations + +1. **Clean function isolation pattern** — One function per `src//` directory with standardized `index.py:handler` entry point is a good convention that predates SAM's template.yaml approach. +2. **Makefile automation is comprehensive** — Create, deploy, test, connect, and API management all in one file. For its era (2015-2016), this was well-designed. +3. **Cognito integration for local testing** — The ability to get real temporary credentials and test authenticated API endpoints locally is valuable and often missing from Lambda frameworks. +4. **Identity caching** — The `.identity_*` file caching avoids unnecessary Cognito calls during development iteration. +5. **Shared lib pattern** — The `lib/` directory with automatic inclusion in ZIPs was a practical solution before Lambda Layers existed. +6. **Existing CLAUDE.md** — The repo already has a well-structured CLAUDE.md with the right sections, Ask tags for unknown info, and accurate gotchas documentation. +7. **Gitignore is thorough** — Properly excludes `.env`, `.identity_*`, `lib/env.py`, build artifacts, and Python cache files.