-
-
Notifications
You must be signed in to change notification settings - Fork 2.4k
feat(bedrock): add model artifact and guardrail grounding security checks for the AWS provider #12459
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
HugoPBrito
merged 22 commits into
prowler-cloud:master
from
tamg-aws:feat/bedrock-model-artifact-checks
Aug 21, 2026
Merged
feat(bedrock): add model artifact and guardrail grounding security checks for the AWS provider #12459
Changes from 12 commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
88b5eb2
feat(bedrock): add model artifact and guardrail grounding security ch…
tamg-aws 843bffc
fix(bedrock): report unreadable inventory as MANUAL and audit only ow…
tamg-aws 8770ec3
test(bedrock): exhaust the decision space for the two untestable reso…
tamg-aws eaa3f95
fix(bedrock): address review findings on the model artifact checks
tamg-aws 8ce87a5
fix(bedrock): report unlisted guardrails and repair the IaC snippets
tamg-aws 979dc65
fix(bedrock): keep data sources when a scan is scoped to a knowledge …
tamg-aws 4cb1268
refactor(bedrock): filter ValidationException in one place for all fo…
tamg-aws f0f9315
fix(bedrock): make the knowledge base data source Terraform example d…
tamg-aws 38f293f
fix(bedrock): require complete evidence before asserting a dedicated …
tamg-aws 9493322
fix(bedrock): store typed contextual grounding filters instead of dicts
tamg-aws ef31a75
fix(bedrock): judge agent role sharing on deployed versions, not the …
tamg-aws 3652e38
docs(bedrock): tell the agent role remediation to move the aliases
tamg-aws b76da50
fix(bedrock): name only the grounding filter attributes actually omitted
tamg-aws fb9fa14
fix(bedrock): make the incomplete-inventory message fit every entry i…
tamg-aws ea705c0
fix(bedrock): agree the missing-filter nouns with how many are missing
tamg-aws 22baea7
fix(bedrock): judge role sharing on the whole account, in any partiti…
tamg-aws 5e6d542
test(bedrock): cover scoped scans, non-commercial partitions, and ina…
tamg-aws 53b1073
feat(compliance): map the new Bedrock checks into the AWS AI Security…
tamg-aws cbfe405
chore(changelog): note the Bedrock Agent ARN partition fix
tamg-aws d7bbcfe
chore(bedrock): trim a comment duplicating the docstring and guard th…
tamg-aws 78fdcee
chore(bedrock): format the grounding-filter Risk like its three siblings
tamg-aws 173c84d
fix(bedrock): handle transitional agent alias states
HugoPBrito File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| `bedrock_guardrail_contextual_grounding_filter_enabled`, `bedrock_custom_model_encrypted_with_cmk`, `bedrock_knowledge_base_encrypted_with_cmk` and `bedrock_agent_role_not_shared_across_agents` are four new AWS Bedrock checks covering guardrail contextual grounding, custom model encryption, knowledge-base data-source encryption, and non-shared agent execution roles. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
42 changes: 42 additions & 0 deletions
42
...t_role_not_shared_across_agents/bedrock_agent_role_not_shared_across_agents.metadata.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| { | ||
| "Provider": "aws", | ||
| "CheckID": "bedrock_agent_role_not_shared_across_agents", | ||
| "CheckTitle": "Bedrock Agent has a dedicated execution role", | ||
| "CheckType": [ | ||
| "Software and Configuration Checks/AWS Security Best Practices" | ||
| ], | ||
| "ServiceName": "bedrock", | ||
| "SubServiceName": "", | ||
| "ResourceIdTemplate": "", | ||
| "Severity": "high", | ||
| "ResourceType": "Other", | ||
| "ResourceGroup": "ai_ml", | ||
| "Description": "Every **Bedrock Agent** assumes the role in its `agentResourceRoleArn`. That role must belong to exactly one agent, so no agent inherits another's permissions.", | ||
| "Risk": "A shared execution role gives every agent using it the union of all their permissions, so a low-trust agent reaching an untrusted tool holds the rights granted for a high-trust one, turning a single prompt injection into access it was never scoped for. It also destroys attribution: CloudTrail records the role session, so an action cannot be traced to one agent.", | ||
|
HugoPBrito marked this conversation as resolved.
Outdated
|
||
| "RelatedUrl": "", | ||
| "AdditionalURLs": [ | ||
| "https://docs.aws.amazon.com/bedrock/latest/userguide/agents-permissions.html", | ||
| "https://docs.aws.amazon.com/bedrock/latest/APIReference/API_agent_UpdateAgent.html" | ||
| ], | ||
| "Remediation": { | ||
| "Code": { | ||
| "CLI": "", | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| "NativeIaC": "```yaml\nResources:\n ExampleResource:\n Type: AWS::Bedrock::Agent\n Properties:\n AgentName: example_resource\n AgentResourceRoleArn: arn:aws:iam::123456789012:role/AmazonBedrockExecutionRoleForAgents_example_resource # Critical: one role per agent, never shared\n```", | ||
| "Other": "1. Open the AWS Console and go to Amazon Bedrock\n2. Select **Agents**, then an agent sharing a role\n3. Create a new IAM role trusted by bedrock.amazonaws.com holding only that agent's permissions\n4. Scope its trust policy with aws:SourceArn set to the agent ARN\n5. Edit the agent to use the new role, keeping its other settings, then prepare the agent to cut a version\n6. Open **Aliases** and point every alias at the new version, since an alias left on the old version keeps invoking the shared role\n7. Repeat until each agent has its own role, then remove the unused permissions from the old role", | ||
| "Terraform": "```hcl\nresource \"aws_bedrockagent_agent\" \"example_resource\" {\n agent_name = \"example_resource\"\n agent_resource_role_arn = aws_iam_role.example_resource_agent.arn # Critical: one role per agent, never shared\n foundation_model = \"example-model-id\"\n}\n```" | ||
| }, | ||
| "Recommendation": { | ||
| "Text": "Give each Bedrock Agent its own execution role scoped to that agent's ARN with aws:SourceArn. Repoint the draft with UpdateAgent, passing every field it already has because omitted ones are not preserved, then PrepareAgent and UpdateAgentAlias to move each alias onto the new version: an alias left on the old version keeps invoking the shared role.", | ||
| "Url": "https://hub.prowler.com/check/bedrock_agent_role_not_shared_across_agents" | ||
| } | ||
| }, | ||
| "Categories": [ | ||
| "gen-ai", | ||
| "identity-access" | ||
| ], | ||
| "DependsOn": [], | ||
| "RelatedTo": [ | ||
| "bedrock_agent_role_least_privilege" | ||
| ], | ||
| "Notes": "Reports one finding per AGENT, not per role, so a role shared by three agents produces three findings each naming the other two. Sharing is counted across the whole account inventory, so a partial scan can under-report. An agent whose role ARN could not be retrieved from GetAgent returns MANUAL rather than PASS, because an unknown role can be neither confirmed nor ruled out as shared, and it is also excluded from every other agent's share count." | ||
| } | ||
152 changes: 152 additions & 0 deletions
152
...edrock_agent_role_not_shared_across_agents/bedrock_agent_role_not_shared_across_agents.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,152 @@ | ||
| from prowler.lib.check.models import Check, Check_Report_AWS | ||
| from prowler.providers.aws.services.bedrock.bedrock_agent_client import ( | ||
| bedrock_agent_client, | ||
| ) | ||
|
|
||
| SHARED_ROLE_AGENT_COUNT = 2 | ||
|
|
||
|
|
||
| class bedrock_agent_role_not_shared_across_agents(Check): | ||
| """Ensure each Bedrock Agent has a dedicated execution role. | ||
|
|
||
| A shared `agentResourceRoleArn` means every agent using it inherits the | ||
| union of all their permissions, so the least-privileged agent in the set | ||
| still holds the rights of the most privileged one. It also destroys | ||
| attribution: CloudTrail records the role session, so an action taken with | ||
| that role cannot be tied back to a single agent. | ||
|
|
||
| Every role an agent can run under counts, not only the working draft's. | ||
| GetAgent returns the draft, but an agent version is an immutable snapshot | ||
| that keeps the role it was cut with, and an alias routes invocations at a | ||
| specific version — so a deployed version can still hold a role the draft no | ||
| longer has, and sharing it is the same exposure. Only versions an alias | ||
| routes to are considered, since a version no alias points at cannot be | ||
| invoked. An alias routing at DRAFT resolves to the draft role already read. | ||
|
|
||
| Sharing is judged against the whole account inventory, so the verdict is only | ||
| as complete as that inventory. Dedication cannot be asserted while any part of | ||
| the picture is missing — an unlisted Region, an agent whose own role could | ||
| not be read, or an agent whose deployed versions could not be listed may hold | ||
| the same role. A role already seen on two agents is shared whatever else is | ||
| missing, so FAIL survives an incomplete inventory. | ||
|
|
||
| - PASS: No role this agent holds is used by any other agent, every Region's | ||
| agent inventory was listed, every discovered agent's role was readable, and | ||
| every agent's deployed versions were listed. | ||
| - FAIL: Two or more agents hold the same role, on the draft or on a deployed | ||
| version; the other agents holding it are named in the message, and the | ||
| version is named when the sharing is through one. | ||
| - MANUAL: This agent's own execution role could not be retrieved from | ||
| GetAgent; or its roles look dedicated but another agent's role or deployed | ||
| versions are unknown, or a Region could not be listed; or ListAgents failed | ||
| for a Region, which therefore contributed no agents at all. | ||
| """ | ||
|
|
||
| def execute(self) -> list[Check_Report_AWS]: | ||
| """Execute the check logic. | ||
|
|
||
| Returns: | ||
| A list of reports containing the result of the check. | ||
| """ | ||
| findings = [] | ||
|
|
||
| incomplete_regions = sorted(bedrock_agent_client.agents_scan_errors) | ||
| for region, error in sorted(bedrock_agent_client.agents_scan_errors.items()): | ||
| report = Check_Report_AWS( | ||
| metadata=self.metadata(), resource={"region": region} | ||
| ) | ||
| report.region = region | ||
| report.resource_id = "agent/unknown" | ||
| report.resource_arn = f"arn:{bedrock_agent_client.audited_partition}:bedrock:{region}:{bedrock_agent_client.audited_account}:agent/unknown" | ||
| report.status = "MANUAL" | ||
| report.status_extended = f"Bedrock Agents could not be listed in region {region} ({error}); verify manually that no execution role is shared between agents." | ||
| findings.append(report) | ||
|
|
||
| # Keyed on agent ARN, not name, so two same-named agents still count | ||
| # twice. An agent is indexed under every role it holds, because a | ||
| # deployed version keeps the role it was cut with: sharing through a | ||
| # version is the same exposure as sharing through the draft. | ||
| agents_by_role = {} | ||
|
HugoPBrito marked this conversation as resolved.
|
||
| unresolved_agents = [] | ||
| for agent in bedrock_agent_client.agents.values(): | ||
| entry = (agent.arn, agent.name or agent.id) | ||
| for role_arn in self._roles_held_by(agent): | ||
| if entry not in agents_by_role.setdefault(role_arn, []): | ||
| agents_by_role[role_arn].append(entry) | ||
| # Both gaps are recorded, not just the first: an agent can have an | ||
| # unreadable draft role and an unlistable version inventory at once, | ||
| # and each independently keeps another agent from being called | ||
| # dedicated. | ||
| if not agent.detail_retrieved or not agent.role_arn: | ||
| unresolved_agents.append(agent.name or agent.id) | ||
| if not agent.versions_listed: | ||
| unresolved_agents.append( | ||
| f"deployed versions of {agent.name or agent.id}" | ||
| ) | ||
|
|
||
| # Dedication can only be asserted from a complete picture: an unlisted | ||
| # Region, an agent whose role could not be read, or an agent whose | ||
| # deployed versions could not be listed may hold the same role. | ||
| incomplete = sorted(unresolved_agents) + [ | ||
| f"agents in region {region}" for region in incomplete_regions | ||
| ] | ||
|
|
||
| for agent in bedrock_agent_client.agents.values(): | ||
| report = Check_Report_AWS(metadata=self.metadata(), resource=agent) | ||
|
HugoPBrito marked this conversation as resolved.
|
||
| name = agent.name or agent.id | ||
|
|
||
| if not agent.detail_retrieved or not agent.role_arn: | ||
| report.status = "MANUAL" | ||
| report.status_extended = f"Bedrock Agent {name} execution role could not be retrieved in region {agent.region}; verify manually that no other agent shares it." | ||
| findings.append(report) | ||
| continue | ||
|
|
||
| # Any role this agent holds, on the draft or on a deployed version, | ||
| # is a finding when another agent holds it too. Reported on the | ||
| # lowest-sorted shared role for determinism across scans. | ||
| shared_roles = sorted( | ||
| role_arn | ||
| for role_arn in self._roles_held_by(agent) | ||
| if len(agents_by_role.get(role_arn, [])) >= SHARED_ROLE_AGENT_COUNT | ||
| ) | ||
| if shared_roles: | ||
| role_arn = shared_roles[0] | ||
| others = sorted( | ||
| other_name | ||
| for other_arn, other_name in agents_by_role[role_arn] | ||
| if other_arn != agent.arn | ||
| ) | ||
| through = ( | ||
| "" | ||
| if role_arn == agent.role_arn | ||
| else f" through deployed version {sorted(version for version, version_role in agent.version_role_arns.items() if version_role == role_arn)[0]}" | ||
| ) | ||
| report.status = "FAIL" | ||
| report.status_extended = f"Bedrock Agent {name} shares execution role {role_arn}{through} with {', '.join(others)} in region {agent.region}, so each agent inherits the union of their permissions and CloudTrail cannot attribute an action to one of them." | ||
| elif incomplete: | ||
| report.status = "MANUAL" | ||
| report.status_extended = f"Bedrock Agent {name} execution role is used by no other agent whose role could be read in region {agent.region}, but the role of {', '.join(incomplete)} is unknown; verify manually that none of them shares it." | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
| else: | ||
| report.status = "PASS" | ||
| report.status_extended = f"Bedrock Agent {name} has a dedicated execution role in region {agent.region}." | ||
| findings.append(report) | ||
|
|
||
| return findings | ||
|
|
||
| def _roles_held_by(self, agent) -> set: | ||
| """Collect every execution role an agent can run under. | ||
|
|
||
| Args: | ||
| agent: The Bedrock Agent to inspect. | ||
|
|
||
| Returns: | ||
| The working draft's role plus the role of each deployed version an | ||
| alias routes to, skipping any that could not be read. | ||
| """ | ||
| roles = set() | ||
| if agent.detail_retrieved and agent.role_arn: | ||
| roles.add(agent.role_arn) | ||
| roles.update( | ||
| role_arn for role_arn in agent.version_role_arns.values() if role_arn | ||
| ) | ||
| return roles | ||
Empty file.
43 changes: 43 additions & 0 deletions
43
...ock_custom_model_encrypted_with_cmk/bedrock_custom_model_encrypted_with_cmk.metadata.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| { | ||
| "Provider": "aws", | ||
| "CheckID": "bedrock_custom_model_encrypted_with_cmk", | ||
| "CheckTitle": "Bedrock custom model is encrypted with a customer-managed KMS key", | ||
| "CheckType": [ | ||
| "Software and Configuration Checks/AWS Security Best Practices", | ||
| "Software and Configuration Checks/AWS Security Best Practices/Data Encryption" | ||
| ], | ||
| "ServiceName": "bedrock", | ||
| "SubServiceName": "", | ||
| "ResourceIdTemplate": "", | ||
| "Severity": "critical", | ||
| "ResourceType": "Other", | ||
| "ResourceGroup": "ai_ml", | ||
| "Description": "**Bedrock custom models** produced by model customization can be encrypted at rest with a customer-managed KMS key rather than resting under an AWS-owned key the organization cannot audit, rotate, or revoke.", | ||
| "Risk": "A fine-tuned model is derived from the training data used to build it and can leak that data through inference. Under an AWS-owned key the organization has no key policy to restrict who may decrypt the artifacts, no rotation it controls, no CloudTrail record of key usage, and no way to revoke access to the model by disabling a key.", | ||
|
HugoPBrito marked this conversation as resolved.
Outdated
|
||
| "RelatedUrl": "", | ||
| "AdditionalURLs": [ | ||
| "https://docs.aws.amazon.com/bedrock/latest/userguide/encryption-custom-job.html", | ||
| "https://docs.aws.amazon.com/bedrock/latest/APIReference/API_GetCustomModel.html" | ||
| ], | ||
| "Remediation": { | ||
| "Code": { | ||
| "CLI": "", | ||
| "NativeIaC": "", | ||
| "Other": "1. Open the AWS Console and go to Amazon Bedrock\n2. Select **Custom models**, then create a new customization job\n3. Under encryption, choose a customer-managed KMS key instead of the default\n4. Grant the customization service role kms:Decrypt, kms:GenerateDataKey and kms:DescribeKey on that key\n5. Re-run customization, because the key of an existing custom model cannot be changed in place", | ||
| "Terraform": "```hcl\nresource \"aws_bedrock_custom_model\" \"example_resource\" {\n custom_model_name = \"example-custom-model\"\n job_name = \"example-customization-job\"\n base_model_identifier = \"arn:aws:bedrock:us-east-1::foundation-model/amazon.titan-text-express-v1\"\n role_arn = \"arn:aws:iam::123456789012:role/example-bedrock-customization-role\"\n custom_model_kms_key_id = \"arn:aws:kms:us-east-1:123456789012:key/example-key-id\" # Critical: without this the model is encrypted with an AWS owned key\n\n hyperparameters = {\n epochCount = \"1\"\n }\n\n output_data_config {\n s3_uri = \"s3://example-bucket/output/\"\n }\n\n training_data_config {\n s3_uri = \"s3://example-bucket/training/\"\n }\n}\n```" | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| }, | ||
| "Recommendation": { | ||
| "Text": "Choose a customer-managed KMS key when creating each model customization job so the resulting custom model rests under a key with an auditable policy and rotation. The key cannot be changed after the model is created, so re-run customization for existing models.", | ||
| "Url": "https://hub.prowler.com/check/bedrock_custom_model_encrypted_with_cmk" | ||
| } | ||
| }, | ||
| "Categories": [ | ||
| "gen-ai", | ||
| "encryption" | ||
| ], | ||
| "DependsOn": [], | ||
| "RelatedTo": [ | ||
| "bedrock_prompt_encrypted_with_cmk" | ||
| ], | ||
| "Notes": "Reports one finding per custom model, reading modelKmsKeyArn from GetCustomModel because ListCustomModels summaries do not carry it. A model whose detail could not be retrieved returns MANUAL rather than PASS, because an absent key ARN means the answer is unknown, not that no key is set." | ||
| } | ||
54 changes: 54 additions & 0 deletions
54
...edrock/bedrock_custom_model_encrypted_with_cmk/bedrock_custom_model_encrypted_with_cmk.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| from prowler.lib.check.models import Check, Check_Report_AWS | ||
| from prowler.providers.aws.services.bedrock.bedrock_client import bedrock_client | ||
|
|
||
|
|
||
| class bedrock_custom_model_encrypted_with_cmk(Check): | ||
| """Ensure Bedrock custom models are encrypted with a customer-managed KMS key. | ||
|
|
||
| - PASS: GetCustomModel returns a `modelKmsKeyArn`, so the model artifacts | ||
| are encrypted with a key the account controls. | ||
| - FAIL: No `modelKmsKeyArn` is set, so the model is encrypted with an | ||
| AWS-owned key that the organization cannot audit, rotate, or revoke. | ||
| - MANUAL: GetCustomModel failed, so the key could not be retrieved and an | ||
| absent value cannot be read as "no key"; or ListCustomModels failed for a | ||
| region, so that region's custom models are unknown rather than absent. | ||
|
|
||
| Only models this account owns are audited (`ListCustomModels` is called with | ||
| `isOwned=True`): the KMS key of a model shared in through Resource Access | ||
| Manager belongs to the owning account and cannot be changed here. | ||
| """ | ||
|
|
||
| def execute(self) -> list[Check_Report_AWS]: | ||
| """Execute the check logic. | ||
|
|
||
| Returns: | ||
| A list of reports containing the result of the check. | ||
| """ | ||
| findings = [] | ||
|
|
||
| for region, error in sorted(bedrock_client.custom_models_scan_errors.items()): | ||
| report = Check_Report_AWS( | ||
| metadata=self.metadata(), resource={"region": region} | ||
| ) | ||
| report.region = region | ||
| report.resource_id = "custom-model/unknown" | ||
| report.resource_arn = f"arn:{bedrock_client.audited_partition}:bedrock:{region}:{bedrock_client.audited_account}:custom-model/unknown" | ||
| report.status = "MANUAL" | ||
| report.status_extended = f"Bedrock custom models could not be listed in region {region} ({error}); verify manually that every custom model uses a customer-managed KMS key." | ||
| findings.append(report) | ||
|
|
||
| for model in bedrock_client.custom_models.values(): | ||
| report = Check_Report_AWS(metadata=self.metadata(), resource=model) | ||
|
|
||
| if not model.detail_retrieved: | ||
| report.status = "MANUAL" | ||
| report.status_extended = f"Bedrock custom model {model.name} encryption configuration could not be retrieved in region {model.region}; verify manually that it uses a customer-managed KMS key." | ||
| elif model.kms_key_arn: | ||
| report.status = "PASS" | ||
| report.status_extended = f"Bedrock custom model {model.name} is encrypted with a customer-managed KMS key in region {model.region}." | ||
| else: | ||
| report.status = "FAIL" | ||
| report.status_extended = f"Bedrock custom model {model.name} is not encrypted with a customer-managed KMS key in region {model.region}, so the fine-tuned weights rest under an AWS-owned key the organization cannot audit or revoke." | ||
| findings.append(report) | ||
|
|
||
| return findings |
Empty file.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.