CI/CD Pipeline
Creating a Security Action on GitHub Actions Using Remote Quick Command
Currently, SCM tools do not provide direct and immediate automation of development processes. Instead, automation are carried out locally and integrated with CI/CD pipelines to verify or modify code implementations submitted by the development team. The following example shows how AI automation prompts engineers to review code and perform customized operations.
Steps
-
Prompt Configuration
- Create an essential security action on GitHub Action using StackSpot AI Remote Quick Command.
- Ask the Quick Command to check security vulnerabilities based on the data received. See the example prompt:
Prompt:
Check security vulnerabilities, describe them and fix the selected code {{input_data}}
Your answer should just be following the JSON structure below:
[
{
"title": "title",
"severity": "severity",
"correction": "correction",
"lines": "lines"
}
]
Where the "title" would be a string resuming the vulnerability in 15 words maximum.
Where the "severity" would be a string representing the impact of the vulnerability, using critical, high, medium or low.
Where the "correction" would be a code suggestion to resolve the issue identified.
Where the "lines" would represent the file code lines where the vulnerability has been identified.
The prompt requires a JSON object as output, which, in this case, is an array. It also provides detailed rules for each field in the response object.
- Authenticate to call the Remote Quick Command. For more information, see the RQC page.
Check the example of a function calling this API in Python:
def get_access_token(account_slug, client_id, client_key):
url = f"https://idm.stackspot.com/{account_slug}/oidc/oauth/token"
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
data = {
'client_id': client_id,
'grant_type': 'client_credentials',
'client_secret': client_key
}
response = requests.post(url, headers=headers, data=data)
response_data = response.json()
return response_data['access_token']
- Execute the Remote Quick Command. See an example of a function calling this API in Python:
def create_rqc_execution(qc_slug, access_token, input_data):
url = f"https://genai-code-buddy-api.stackspot.com/v1/quick-commands/create-execution/{qc_slug}"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {access_token}'
}
data = {
'input_data': input_data
}
response = requests.post(
url,
headers=headers,
json=data
)
if response.status_code == 200:
decoded_content = response.content.decode('utf-8') # Decode bytes to string
extracted_value = decoded_content.strip('"') # Strip the surrounding quotes
response_data = extracted_value
print('ExecutionID:', response_data)
return response_data
else:
print(response.status_code)
print(response.content)
- Check the Remote Execution Status. The example shows a function calling this API in Python using a 5s delay between requests:
def get_execution_status(execution_id, access_token):
url = f"https://genai-code-buddy-api.stackspot.com/v1/quick-commands/callback/{execution_id}"
headers = {'Authorization': f'Bearer {access_token}'}
i = 0
while True:
response = requests.get(
url,
headers=headers
)
response_data = response.json()
status = response_data['progress']['status']
if status in ['COMPLETED', 'FAILED']:
return response_data
else:
print("Status:", f'{status} ({i})')
print("Execution in progress, waiting...")
i+=1
time.sleep(5) # Wait for 5 seconds before polling again
- Process the Result Data. The result_data is a JSON object that can be manipulated for any action. See the Python example using environment variables:
# Replace the placeholders with your actual data
CLIENT_ID = os.getenv("CLIENT_ID")
CLIENT_KEY = os.getenv("CLIENT_KEY")
ACCOUNT_SLUG = os.getenv("CLIENT_REALM")
QC_SLUG = os.getenv("QC_SLUG")
INPUT_DATA = os.getenv("INPUT_DATA")
# Execute the steps
access_token = get_access_token(ACCOUNT_SLUG, CLIENT_ID, CLIENT_KEY)
execution_id = create_rqc_execution(QC_SLUG, access_token, INPUT_DATA)
execution_status = get_execution_status(execution_id, access_token)
result = execution_status['result']
# Remove the leading and trailing ```json and ``` for correct JSON parsing
if result.startswith("```json"):
result = result[7:-4].strip()
result_data = json.loads(result)
- Implement in a CI/CD Pipeline Using GitHub Actions
- Create a composite action to call other existing actions from the GitHub marketplace. This action will use three other actions:
- actions/checkout: Access the repository files.
- tj-actions/changed-files: Identify which files were updated.
- actions/setup-python: Configure Python in the runner.
Example of how the script looks in the action.yaml file:
- name: Run Remote Quick Command
env:
CLIENT_ID: ${{ inputs.CLIENT_ID }}
CLIENT_KEY: ${{ inputs.CLIENT_KEY }}
CLIENT_REALM: ${{ inputs.CLIENT_REALM }}
QC_SLUG: ${{ inputs.QC_SLUG }}
CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
run: python3 ${{ github.action_path }}/rqc.py
shell: bash
- Adapt the Python script to extract the CHANGED_FILES variable and perform a loop with the returned list. Example in Python:
# Replace the placeholders with your actual data
CLIENT_ID = os.getenv("CLIENT_ID")
CLIENT_KEY = os.getenv("CLIENT_KEY")
ACCOUNT_SLUG = os.getenv("CLIENT_REALM")
QC_SLUG = os.getenv("QC_SLUG")
CHANGED_FILES = os.getenv("CHANGED_FILES")
print(f'\033[36mFiles to analyze: {CHANGED_FILES}\033[0m')
CHANGED_FILES = ast.literal_eval(CHANGED_FILES)
for file_path in CHANGED_FILES:
print(f'\n\033[36mFile Path: {file_path}\033[0m')
# Open the file and read its content
with open(file_path, 'r') as file:
file_content = file.read()
# Execute the steps
access_token = get_access_token(ACCOUNT_SLUG, CLIENT_ID, CLIENT_KEY)
execution_id = create_rqc_execution(QC_SLUG, access_token, file_content)
execution_status = get_execution_status(execution_id, access_token)
result = execution_status['result']
# Remove the leading and trailing ```json and ``` for correct JSON parsing
if result.startswith("```json"):
result = result[7:-4].strip()
result_data = json.loads(result)
vulnerabilities_amount = len(result_data)
print(f"\n\033[36m{vulnerabilities_amount} item(s) have been found for file {file_path}:\033[0m")
# Iterate through each item and print the required fields
for item in result_data:
print(f"\nTitle: {item['title']}")
print(f"Severity: {item['severity']}")
print(f"Correction: {item['correction']}")
print(f"Lines: {item['lines']}")
The last section of the code extracts the data from the result JSON object, according to what was configured during the prompt configuration step, to display the data during the action execution.
- Workflow Example
- To call this action, you need a workflow file to trigger a pull_request event. See the example:
name: Action Test
on:
pull_request:
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: <owner>/<repository>@main
with:
CLIENT_ID: ${{ secrets.CLIENT_ID }}
CLIENT_KEY: ${{ secrets.CLIENT_KEY }}
CLIENT_REALM: ${{ secrets.CLIENT_REALM }}
QC_SLUG: sast-rqc # here is the remote Quick Command slug example
When triggering this pipeline, the workflow will run and call our script in Python. It should return something like the image below for each file updated in the PR:
The action is working as expected. Based on the results, consider adding more features to improve the action. See more examples:
- Add Knowledge Sources for the Quick Command to use when checking vulnerabilities to keep them updated with the latest security data.
- Generate a report of the repository vulnerabilities, segregated by file.
- Comment on the pull request in which vulnerabilities you detected.
- Automatically open a pull request using AI to correct the detected vulnerabilities.
- Run the action, checking all repository files occasionally to see if new vulnerabilities have been discovered.
- Add more vulnerability checks (SAST, DAST, etc).