Part 2: Prepare Claude Haiku 4.5 in Amazon Bedrock
Current Amazon Bedrock documentation states that AWS enables foundation-model access by default. However, first-time users of Anthropic models may still need to submit use-case details before invocation.
To complete this step, select an Anthropic model in the Bedrock model catalog or playground.
In the AWS Management Console:
- Open Amazon Bedrock in
us-east-1. - Open the model catalog or model playground.
- Select Claude Haiku 4.5.
- Submit the Anthropic use-case details if prompted.
- Run a short test message.
Next, verify programmatic invocation with the AWS CLI:
aws bedrock-runtime converse --model-id "anthropic.claude-haiku-4-5-20251001-v1:0" --messages '[{"role":"user","content":[{"text":"Find the security issue: password = "admin123""}]}]' --region us-east-1The response should contain a model-generated message and token-usage metadata.
The final application uses the following US geographical inference profile:
us.anthropic.claude-haiku-4-5-20251001-v1:0AWS documents this identifier as a supported geo-inference ID for Claude Haiku 4.5.
Part 3: Create the GitHub Repository and Token
Create a public GitHub repository named
Diffenderwith:
- A
README.md - An MIT license
- A Python
.gitignore
Â
Next, create a fine-grained personal access token restricted to the Diffender repository.
Grant these repository permissions:
- Pull requests: Read and write
- Issues: Read and write
- Contents: Read-only
The current implementation retrieves the pull request through the pull requests endpoint and posts the final message through the issue-comments endpoint.
That distinction is easy to miss. GitHub pull requests are also issues internally, so creating a general conversation comment on a pull request uses:
POST /repos/{owner}/{repo}/issues/{issue_number}/commentsGitHub requires pull request or contents read access to retrieve the pull request. In addition, GitHub evaluates fine-grained token permissions for each REST endpoint.
Copy the token beginning with:
github_pat_Do not add it to the repository, a local configuration file, or the SAM template.
Part 4: Create the Project Structure
Clone the repository and create the application directories:
git clone <a class="decorated-link" href="https://github.com/chescaasignacion/Diffender.git" target="_new" rel="noopener" data-start="11514" data-end="11563">https://github.com/chescaasignacion/Diffender.git</a> cd Diffender mkdir -p src test-samples touch src/requirements.txtThe project structure is:
Diffender/ ├── src/ │ ├── app.py │ └── requirements.txt ├── test-samples/ │ └── insecure_example.py ├── template.yaml ├── README.md ├── LICENSE └── .gitignoreAWS SAM Template
Create
template.yaml:[yaml] AWSTemplateFormatVersion: ‘2010-09-09’
Transform: AWS::Serverless-2016-10-31
Description: Diffender – an AI security code reviewer for GitHub PRs, powered by Amazon Bedrock.Parameters:
BedrockModelId:
Type: String
Default: us.anthropic.claude-haiku-4-5-20251001-v1:0Globals:
Function:
Timeout: 60
MemorySize: 256
Runtime: python3.12Resources:
DiffenderFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: diffender
Handler: app.lambda_handler
CodeUri: src/
Environment:
Variables:
BEDROCK_MODEL_ID: !Ref BedrockModelId
GITHUB_TOKEN_PARAM: /diffender/github-token
WEBHOOK_SECRET_PARAM: /diffender/webhook-secret
Policies:
– Version: ‘2012-10-17’
Statement:
– Effect: Allow
Action:
– ssm:GetParameter
Resource:
– !Sub arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/diffender/*
– Version: ‘2012-10-17’
Statement:
– Effect: Allow
Action:
– kms:Decrypt
Resource: ‘‘
Condition:
StringEquals:
kms:ViaService: !Sub ssm.${AWS::Region}.amazonaws.com
– Version: ‘2012-10-17’
Statement:
– Effect: Allow
Action:
– bedrock:InvokeModel
Resource: ‘‘
Events:
Webhook:
Type: Api
Properties:
Path: /webhook
Method: postOutputs:
WebhookUrl:
Value: !Sub https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/webhook[/yaml]The template defines one Lambda function and one API Gateway REST API route. It also creates the Lambda execution role from inline policy statements.
The function can perform three categories of AWS action:
- Retrieve the Parameter Store values
- Decrypt encrypted parameters through AWS KMS
- Invoke Amazon Bedrock
The Amazon Bedrock and AWS KMS statements use wildcard resource values in this tutorial. Therefore, a production version should evaluate whether those resources can be narrowed based on the selected AWS KMS key, model, and inference-profile ARNs.
Lambda Handler
The complete Lambda handler is available in the <a href=”https://github.com/chescaasignacion/Diffender”>Diffender GitHub repository</a>. The following sections focus on the code that controls secret retrieval, signature validation, Amazon Bedrock invocation, and GitHub integration.
Retrieve Secrets from Parameter Store
ssm = boto3.client("ssm") _cache = {} def _get_param(name): if name not in _cache: _cache[name] = ssm.get_parameter( Name=name, WithDecryption=True, )["Parameter"]["Value"] &amp;lt;div class="relative w-full mt-4 mb-1"&amp;gt;&amp;lt;div class=""&amp;gt;&amp;lt;div class="contents"&amp;gt;&amp;lt;div class="relative"&amp;gt;&amp;lt;div class="h-full min-h-0 min-w-0"&amp;gt;&amp;lt;div class="h-full min-h-0 min-w-0"&amp;gt;&amp;lt;div class="border border-token-border-light border-radius-3xl corner-superellipse/1.1 rounded-3xl"&amp;gt;&amp;lt;div class="h-full w-full border-radius-3xl bg-(--code-block-surface) corner-superellipse/1.1 overflow-clip rounded-3xl [--code-block-surface:var(--bg-elevated-secondary)] dark:[--code-block-surface:var(--composer-surface-primary)] lxnfua_clipPathFallback"&amp;gt;&amp;lt;div class="pointer-events-none absolute end-1.5 top-1 z-2 md:end-2 md:top-1"&amp;gt;&amp;amp;nbsp;&amp;lt;/div&amp;gt;&amp;lt;div class="relative"&amp;gt;&amp;lt;div class="pe-11 pt-3"&amp;gt;&amp;lt;div class="relative z-0 flex max-w-full"&amp;gt;&amp;lt;div id="code-block-viewer" class="q9tKkq_viewer cm-editor z-10 light:cm-light dark:cm-light flex h-full w-full flex-col items-stretch ͼd ͼr" dir="ltr"&amp;gt;&amp;lt;div class="cm-scroller"&amp;gt;&amp;lt;pre class="cm-content q9tKkq_readonly m-0"&amp;gt;&amp;lt;code&amp;gt;return _cache[name]&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;div class=""&amp;gt;&amp;lt;div class=""&amp;gt;&amp;amp;nbsp;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;The function retrieves each parameter only once per warm Lambda execution environment. Later invocations can reuse the cached value.
Validate the GitHub Signature
def verify_signature(raw_body, signature_header): if not signature_header: return False &amp;lt;div class="relative w-full mt-4 mb-1"&amp;gt;&amp;lt;div class=""&amp;gt;&amp;lt;div class="contents"&amp;gt;&amp;lt;div class="relative"&amp;gt;&amp;lt;div class="h-full min-h-0 min-w-0"&amp;gt;&amp;lt;div class="h-full min-h-0 min-w-0"&amp;gt;&amp;lt;div class="border border-token-border-light border-radius-3xl corner-superellipse/1.1 rounded-3xl"&amp;gt;&amp;lt;div class="h-full w-full border-radius-3xl bg-(--code-block-surface) corner-superellipse/1.1 overflow-clip rounded-3xl [--code-block-surface:var(--bg-elevated-secondary)] dark:[--code-block-surface:var(--composer-surface-primary)] lxnfua_clipPathFallback"&amp;gt;&amp;lt;div class="pointer-events-none absolute end-1.5 top-1 z-2 md:end-2 md:top-1"&amp;gt;&amp;amp;nbsp;&amp;lt;/div&amp;gt;&amp;lt;div class="relative"&amp;gt;&amp;lt;div class="pe-11 pt-3"&amp;gt;&amp;lt;div class="relative z-0 flex max-w-full"&amp;gt;&amp;lt;div id="code-block-viewer" class="q9tKkq_viewer cm-editor z-10 light:cm-light dark:cm-light flex h-full w-full flex-col items-stretch ͼd ͼr" dir="ltr"&amp;gt;&amp;lt;div class="cm-scroller"&amp;gt;&amp;lt;pre class="cm-content q9tKkq_readonly m-0"&amp;gt;&amp;lt;code&amp;gt;secret = _get_param(WEBHOOK_SECRET_PARAM).encode() expected = ( "sha256=" + hmac.new(secret, raw_body, hashlib.sha256).hexdigest() ) return hmac.compare_digest(expected, signature_header)&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;div class=""&amp;gt;&amp;lt;div class=""&amp;gt;&amp;amp;nbsp;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;This function recalculates the HMAC-SHA-256 signature from the raw request body and compares it with the value supplied by GitHub.
Retrieve the Pull Request Diff
def get_pr_diff(owner, repo, number): url = ( f"&amp;lt;a class="decorated-link" href="https://api.github.com/repos/" target="_new" rel="noopener" data-start="15541" data-end="15570"&amp;gt;https://api.github.com/repos/&amp;lt;/a&amp;gt;" f"{owner}/{repo}/pulls/{number}" ) &amp;lt;div class="relative w-full mt-4 mb-1"&amp;gt;&amp;lt;div class=""&amp;gt;&amp;lt;div class="contents"&amp;gt;&amp;lt;div class="relative"&amp;gt;&amp;lt;div class="h-full min-h-0 min-w-0"&amp;gt;&amp;lt;div class="h-full min-h-0 min-w-0"&amp;gt;&amp;lt;div class="border border-token-border-light border-radius-3xl corner-superellipse/1.1 rounded-3xl"&amp;gt;&amp;lt;div class="h-full w-full border-radius-3xl bg-(--code-block-surface) corner-superellipse/1.1 overflow-clip rounded-3xl [--code-block-surface:var(--bg-elevated-secondary)] dark:[--code-block-surface:var(--composer-surface-primary)] lxnfua_clipPathFallback"&amp;gt;&amp;lt;div class="pointer-events-none absolute end-1.5 top-1 z-2 md:end-2 md:top-1"&amp;gt;&amp;amp;nbsp;&amp;lt;/div&amp;gt;&amp;lt;div class="relative"&amp;gt;&amp;lt;div class="pe-11 pt-3"&amp;gt;&amp;lt;div class="relative z-0 flex max-w-full"&amp;gt;&amp;lt;div id="code-block-viewer" class="q9tKkq_viewer cm-editor z-10 light:cm-light dark:cm-light flex h-full w-full flex-col items-stretch ͼd ͼr" dir="ltr"&amp;gt;&amp;lt;div class="cm-scroller"&amp;gt;&amp;lt;pre class="cm-content q9tKkq_readonly m-0"&amp;gt;&amp;lt;code&amp;gt;return _github_request( "GET", url, accept="application/vnd.github.v3.diff", )&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;div class=""&amp;gt;&amp;lt;div class=""&amp;gt;&amp;amp;nbsp;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;The GitHub API returns the code changes as a unified diff, which contains the lines added, removed, or modified by the pull request.
Invoke Claude Through Amazon Bedrock
def review_with_bedrock(diff_text): max_chars = 60000 truncated = diff_text[:max_chars] &amp;lt;div class="relative w-full mt-4 mb-1"&amp;gt;&amp;lt;div class=""&amp;gt;&amp;lt;div class="contents"&amp;gt;&amp;lt;div class="relative"&amp;gt;&amp;lt;div class="h-full min-h-0 min-w-0"&amp;gt;&amp;lt;div class="h-full min-h-0 min-w-0"&amp;gt;&amp;lt;div class="border border-token-border-light border-radius-3xl corner-superellipse/1.1 rounded-3xl"&amp;gt;&amp;lt;div class="h-full w-full border-radius-3xl bg-(--code-block-surface) corner-superellipse/1.1 overflow-clip rounded-3xl [--code-block-surface:var(--bg-elevated-secondary)] dark:[--code-block-surface:var(--composer-surface-primary)] lxnfua_clipPathFallback"&amp;gt;&amp;lt;div class="pointer-events-none absolute end-1.5 top-1 z-2 md:end-2 md:top-1"&amp;gt;&amp;amp;nbsp;&amp;lt;/div&amp;gt;&amp;lt;div class="relative"&amp;gt;&amp;lt;div class="pe-11 pt-3"&amp;gt;&amp;lt;div class="relative z-0 flex max-w-full"&amp;gt;&amp;lt;div id="code-block-viewer" class="q9tKkq_viewer cm-editor z-10 light:cm-light dark:cm-light flex h-full w-full flex-col items-stretch ͼd ͼr" dir="ltr"&amp;gt;&amp;lt;div class="cm-scroller"&amp;gt;&amp;lt;pre class="cm-content q9tKkq_readonly m-0"&amp;gt;&amp;lt;code&amp;gt;note = ( "" if len(diff_text) &amp;amp;lt;= max_chars else "\n\n" ) user_message = ( "Review this pull request diff:\n\n" f"```diff\n{truncated}{note}\n```" ) response = bedrock.converse( modelId=BEDROCK_MODEL_ID, system=[{"text": SYSTEM_PROMPT}], messages=[ { "role": "user", "content": [{"text": user_message}], } ], inferenceConfig={ "maxTokens": 1500, "temperature": 0.2, }, ) return response["output"]["message"]["content"][0]["text"]&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;div class=""&amp;gt;&amp;lt;div class=""&amp;gt;&amp;amp;nbsp;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;The function limits the diff to 60,000 characters before sending it to the model. It also caps the response at 1,500 tokens.
Post the Review to GitHub
def post_pr_comment(owner, repo, number, body): url = ( f"&amp;lt;a class="decorated-link" href="https://api.github.com/repos/" target="_new" rel="noopener" data-start="16917" data-end="16946"&amp;gt;https://api.github.com/repos/&amp;lt;/a&amp;gt;" f"{owner}/{repo}/issues/{number}/comments" ) &amp;lt;div class="relative w-full mt-4 mb-1"&amp;gt;&amp;lt;div class=""&amp;gt;&amp;lt;div class="contents"&amp;gt;&amp;lt;div class="relative"&amp;gt;&amp;lt;div class="h-full min-h-0 min-w-0"&amp;gt;&amp;lt;div class="h-full min-h-0 min-w-0"&amp;gt;&amp;lt;div class="border border-token-border-light border-radius-3xl corner-superellipse/1.1 rounded-3xl"&amp;gt;&amp;lt;div class="h-full w-full border-radius-3xl bg-(--code-block-surface) corner-superellipse/1.1 overflow-clip rounded-3xl [--code-block-surface:var(--bg-elevated-secondary)] dark:[--code-block-surface:var(--composer-surface-primary)] lxnfua_clipPathFallback"&amp;gt;&amp;lt;div class="pointer-events-none absolute end-1.5 top-1 z-2 md:end-2 md:top-1"&amp;gt;&amp;amp;nbsp;&amp;lt;/div&amp;gt;&amp;lt;div class="relative"&amp;gt;&amp;lt;div class="pe-11 pt-3"&amp;gt;&amp;lt;div class="relative z-0 flex max-w-full"&amp;gt;&amp;lt;div id="code-block-viewer" class="q9tKkq_viewer cm-editor z-10 light:cm-light dark:cm-light flex h-full w-full flex-col items-stretch ͼd ͼr" dir="ltr"&amp;gt;&amp;lt;div class="cm-scroller"&amp;gt;&amp;lt;pre class="cm-content q9tKkq_readonly m-0"&amp;gt;&amp;lt;code&amp;gt;return _github_request( "POST", url, data={"body": body}, )&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;div class=""&amp;gt;&amp;lt;div class=""&amp;gt;&amp;amp;nbsp;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;GitHub uses the Issues Comments API for general pull request conversation comments.
Requirements File
The tutorial does not install an external application package. The Python Lambda runtime includes the AWS SDK for Python, so
src/requirements.txtcan remain empty for this build.For a controlled production release, package and pin the required SDK version so the application does not depend on whichever SDK version the runtime includes.
&amp;lt;h1 data-section-id="1n0k789" data-start="17567" data-end="17612"&amp;gt;Intentionally empty for the tutorial build.&amp;lt;/h1&amp;gt;Deliberately Insecure Test File
Create
test-samples/insecure_example.py:"""Intentionally insecure sample. Open a pull request with this file to see Diffender in action. """ import hashlib import sqlite3 import subprocess AWS_SECRET_ACCESS_KEY = ( "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" ) def get_user(username): """Contains an intentional SQL-injection vulnerability.""" &amp;lt;div class="relative w-full mt-4 mb-1"&amp;gt;&amp;lt;div class=""&amp;gt;&amp;lt;div class="contents"&amp;gt;&amp;lt;div class="relative"&amp;gt;&amp;lt;div class="h-full min-h-0 min-w-0"&amp;gt;&amp;lt;div class="h-full min-h-0 min-w-0"&amp;gt;&amp;lt;div class="border border-token-border-light border-radius-3xl corner-superellipse/1.1 rounded-3xl"&amp;gt;&amp;lt;div class="h-full w-full border-radius-3xl bg-(--code-block-surface) corner-superellipse/1.1 overflow-clip rounded-3xl [--code-block-surface:var(--bg-elevated-secondary)] dark:[--code-block-surface:var(--composer-surface-primary)] lxnfua_clipPathFallback"&amp;gt;&amp;lt;div class="pointer-events-none absolute end-1.5 top-1 z-2 md:end-2 md:top-1"&amp;gt;&amp;amp;nbsp;&amp;lt;/div&amp;gt;&amp;lt;div class="relative"&amp;gt;&amp;lt;div class="pe-11 pt-3"&amp;gt;&amp;lt;div class="relative z-0 flex max-w-full"&amp;gt;&amp;lt;div id="code-block-viewer" class="q9tKkq_viewer cm-editor z-10 light:cm-light dark:cm-light flex h-full w-full flex-col items-stretch ͼd ͼr" dir="ltr"&amp;gt;&amp;lt;div class="cm-scroller"&amp;gt;&amp;lt;pre class="cm-content q9tKkq_readonly m-0"&amp;gt;&amp;lt;code&amp;gt;return sqlite3.connect("app.db").execute( "SELECT * FROM users WHERE name = '%s'" % username ).fetchall()&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;div class=""&amp;gt;&amp;lt;div class=""&amp;gt;&amp;amp;nbsp;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt; def ping(host): """Contains an intentional command-injection vulnerability.""" &amp;lt;div class="relative w-full mt-4 mb-1"&amp;gt;&amp;lt;div class=""&amp;gt;&amp;lt;div class="contents"&amp;gt;&amp;lt;div class="relative"&amp;gt;&amp;lt;div class="h-full min-h-0 min-w-0"&amp;gt;&amp;lt;div class="h-full min-h-0 min-w-0"&amp;gt;&amp;lt;div class="border border-token-border-light border-radius-3xl corner-superellipse/1.1 rounded-3xl"&amp;gt;&amp;lt;div class="h-full w-full border-radius-3xl bg-(--code-block-surface) corner-superellipse/1.1 overflow-clip rounded-3xl [--code-block-surface:var(--bg-elevated-secondary)] dark:[--code-block-surface:var(--composer-surface-primary)] lxnfua_clipPathFallback"&amp;gt;&amp;lt;div class="pointer-events-none absolute end-1.5 top-1 z-2 md:end-2 md:top-1"&amp;gt;&amp;amp;nbsp;&amp;lt;/div&amp;gt;&amp;lt;div class="relative"&amp;gt;&amp;lt;div class="pe-11 pt-3"&amp;gt;&amp;lt;div class="relative z-0 flex max-w-full"&amp;gt;&amp;lt;div id="code-block-viewer" class="q9tKkq_viewer cm-editor z-10 light:cm-light dark:cm-light flex h-full w-full flex-col items-stretch ͼd ͼr" dir="ltr"&amp;gt;&amp;lt;div class="cm-scroller"&amp;gt;&amp;lt;pre class="cm-content q9tKkq_readonly m-0"&amp;gt;&amp;lt;code&amp;gt;return subprocess.check_output( "ping -c 1 " + host, shell=True, )&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;div class=""&amp;gt;&amp;lt;div class=""&amp;gt;&amp;amp;nbsp;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt; def hash_password(password): """Contains intentionally weak password hashing.""" &amp;lt;div class="relative w-full mt-4 mb-1"&amp;gt;&amp;lt;div class=""&amp;gt;&amp;lt;div class="contents"&amp;gt;&amp;lt;div class="relative"&amp;gt;&amp;lt;div class="h-full min-h-0 min-w-0"&amp;gt;&amp;lt;div class="h-full min-h-0 min-w-0"&amp;gt;&amp;lt;div class="border border-token-border-light border-radius-3xl corner-superellipse/1.1 rounded-3xl"&amp;gt;&amp;lt;div class="h-full w-full border-radius-3xl bg-(--code-block-surface) corner-superellipse/1.1 overflow-clip rounded-3xl [--code-block-surface:var(--bg-elevated-secondary)] dark:[--code-block-surface:var(--composer-surface-primary)] lxnfua_clipPathFallback"&amp;gt;&amp;lt;div class="pointer-events-none absolute end-1.5 top-1 z-2 md:end-2 md:top-1"&amp;gt;&amp;amp;nbsp;&amp;lt;/div&amp;gt;&amp;lt;div class="relative"&amp;gt;&amp;lt;div class="pe-11 pt-3"&amp;gt;&amp;lt;div class="relative z-0 flex max-w-full"&amp;gt;&amp;lt;div id="code-block-viewer" class="q9tKkq_viewer cm-editor z-10 light:cm-light dark:cm-light flex h-full w-full flex-col items-stretch ͼd ͼr" dir="ltr"&amp;gt;&amp;lt;div class="cm-scroller"&amp;gt;&amp;lt;pre class="cm-content q9tKkq_readonly m-0"&amp;gt;&amp;lt;code&amp;gt;return hashlib.md5(password.encode()).hexdigest()&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;div class=""&amp;gt;&amp;lt;div class=""&amp;gt;&amp;amp;nbsp;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;This intentionally unsafe file belongs only in an isolated demonstration repository. Therefore, never use it in an actual application.
Part 5: Build and Deploy with AWS SAM
AWS SAM keeps the application code and infrastructure definition in the same project. Readers who need a refresher on SAM templates, resources, and deployment commands can review the AWS SAM cheat sheet.
Build the application in a Python 3.12-compatible container:
sam build --use-containerThe
sam buildcommand prepares the source and infrastructure definition for deployment. In addition, using a container reduces differences between the local machine and the Lambda build environment.Next, deploy the stack:
sam deploy --guidedUse the following guided responses:
Stack Name: Diffender AWS Region: us-east-1 Parameter BedrockModelId: accept the default Confirm changes before deploy: Y Allow SAM CLI IAM role creation: Y Disable rollback: N DiffenderFunction Webhook may not have authorization defined: Y Save arguments to configuration file: Y SAM configuration file: samconfig.toml SAM configuration environment: defaultDuring deployment, SAM displays an authorization warning because the endpoint does not use an API Gateway authorizer. That is intentional because GitHub cannot authenticate through IAM or Amazon Cognito before sending the webhook.
Instead, Diffender verifies each request through application-level HMAC validation.
After deployment, copy the
WebhookUrlfrom the CloudFormation outputs:&amp;lt;a class="decorated-link cursor-pointer" target="_new" rel="noopener" data-start="20120" data-end="20188"&amp;gt;https://YOUR_API_ID.execute-api.us-east-1.amazonaws.com/Prod/webhook&amp;lt;/a&amp;gt;
Finally, commit
samconfig.tomlonly after confirming that it contains no secrets. The file normally stores deployment preferences and parameter values, but secrets should remain in Parameter Store.Â























