Skip to content

fix: validate github return url metadata#5057

Merged
lukaszgryglicki merged 3 commits into
linuxfoundation:devfrom
psrsingh:fix-github-return-url-validation
May 28, 2026
Merged

fix: validate github return url metadata#5057
lukaszgryglicki merged 3 commits into
linuxfoundation:devfrom
psrsingh:fix-github-return-url-validation

Conversation

@psrsingh
Copy link
Copy Markdown
Contributor

Summary

Adds defensive validation for GitHub and GitLab return URL generation in the sign flow.

Changes

  • validate installation, repository, pull request, and merge request metadata
  • validate GitHub repository metadata before PR lookup
  • validate pull request HTML URL before returning redirect target
  • improve error handling around callback URL generation
  • prevent invalid or empty redirect URLs from propagating through callback flows

Why

The sign/auth redirect flow could continue with incomplete or malformed metadata, potentially resulting in invalid or empty redirect URLs and blank browser pages after authentication flows.

This patch improves robustness and error visibility around redirect URL generation.

Fixes #5052

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 16, 2026

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds fail-fast validation and explicit errors for required metadata (installation_id, repository_id, pull_request_id/merge_request_id), wraps known GitHub API errors, validates repository owner/name and pull request HTMLURL, and derives installation_id from repository_id when missing.

Changes

Input Validation and Error Handling for Metadata Fields

Layer / File(s) Summary
GetReturnURL input and metadata validation
cla-backend-go/github/github_repository.go
Input IDs are validated for positive values at entry. GitHub App client creation, repository and pull request fetches now optionally wrap known errors via CheckAndWrapForKnownErrors and validate repository owner/name and pull request HTMLURL are present and non-empty before returning.
Signature callback/return URL validation and installation derivation
cla-backend-go/v2/sign/service.go
Adds metadataStringValue helper and fail-fast validation for repository_id, pull_request_id/merge_request_id, and gitlabOrg.OrganizationID; refines error text when resolved installation ID is zero/missing.

Sequence Diagrams

No sequence diagrams are generated for this PR because the changes are primarily validation, error-wrapping, and minor control-flow refactoring; the flows are simple and already documented in the hidden artifact.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: validate github return url metadata' directly summarizes the main changes in both modified files, which add validation for GitHub and GitLab return URL generation metadata.
Description check ✅ Passed The description clearly relates to the changeset, explaining defensive validation for GitHub and GitLab return URL generation, improved error handling, and linking to issue #5052.
Linked Issues check ✅ Passed The PR implements defensive validation for return URL metadata generation to prevent blank redirects, validating repository/pull request metadata and improving error handling to address the blank screen issue described in #5052.
Out of Scope Changes check ✅ Passed All changes focus on validating GitHub and GitLab return URL generation metadata and error handling, directly aligned with the objectives of fixing incomplete/malformed metadata handling.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Member

@lukaszgryglicki lukaszgryglicki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/lgtm - will need rebase because I've fixed some failing CI in another PR. Once that PR is fixed this should became green.

@psrsingh
Copy link
Copy Markdown
Contributor Author

/lgtm - will need rebase because I've fixed some failing CI in another PR. Once that PR is fixed this should became green.

Thanks! I’ll rebase the branch once the CI fix PR is merged.

@lukaszgryglicki
Copy link
Copy Markdown
Member

Yeah, it needs approval: #5066.

@lukaszgryglicki
Copy link
Copy Markdown
Member

Also this feedback from AI, PTAL:

One correctness issue remains before merge: the changed getActiveSignatureReturnURL path still expects metadata shapes that do not match the current active-signature metadata schema. Active signature metadata stores repository_id and pull_request_id as strings, and it does not include installation_id. With this PR, valid active-signature metadata can now fail with missing installation_id in metadata instead of producing the GitHub PR return URL.

Please align this function with the existing service method logic already present in the same file: read pull_request_id and repository_id from metadata using a generic value-to-string conversion, parse them, and derive the installation ID from repository_id via getInstallationIDFromRepositoryID. In other words, do not require metadata["installation_id"].(int64) for this path.

Small related fix: getInstallationIDFromRepositoryID still has the same nil-error pattern when OrganizationInstallationID == 0:

return 0, err

At that point err can be nil, so the caller can continue with installation ID 0. Please return an explicit error there.

@psrsingh
Copy link
Copy Markdown
Contributor Author

@lukaszgryglicki thanks. I’ll update it to derive the installation ID from "repository_id" like the existing service logic does, and I’ll fix the nil error case in "getInstallationIDFromRepositoryID" as well.

@lukaszgryglicki
Copy link
Copy Markdown
Member

Thanks, I've merged my PR, but wait a bit with rebasing because there might be a follow up PR...

@psrsingh
Copy link
Copy Markdown
Contributor Author

Thanks, I've merged my PR, but wait a bit with rebasing because there might be a follow up PR...

Got it, thanks for the heads up. I’ll wait before rebasing.

@lukaszgryglicki
Copy link
Copy Markdown
Member

Please rebase to newest dev - my PRs were just mered.

@psrsingh psrsingh force-pushed the fix-github-return-url-validation branch from f16ffde to 50b6f0e Compare May 26, 2026 14:42
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cla-backend-go/v2/sign/service.go`:
- Around line 1572-1584: The metadata key checks currently accept empty strings
for repository_id, merge_request_id, and pull_request_id; update the checks in
the sign handler (the block that sets repositoryID, mergeRequestID,
pullRequestID from metadata) to reject empty values by using
strings.TrimSpace(value) == "" as a failure condition, construct the same error
(e.g., errors.New("missing repository_id in metadata") etc.), and call
log.WithFields(f).WithError(err).Warnf("unable to get ... for user: %s", userID)
then return the error just like the missing-key path so empty values are treated
as missing and do not produce malformed callback URLs.
- Around line 2242-2264: The package-level metadata parsing currently assumes
int/int64 types for pull_request_id, installation_id and repository_id and
rejects string/generic metadata; update this helper to use the same parsing
logic as (*service).getActiveSignatureReturnURL: accept string/generic values,
coerce with strconv (or fmt.Sprintf then strconv.ParseInt) for pull_request_id
and repository_id, derive installation_id from repository_id when appropriate
instead of requiring installation_id, and remove the strict int64 type
assertions so valid metadata processed by getActiveSignatureReturnURL isn't
rejected; keep log/error paths but parse values the same way the service method
does.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c377cdea-7a99-438f-8a76-975fc879932a

📥 Commits

Reviewing files that changed from the base of the PR and between f16ffde and 50b6f0e.

📒 Files selected for processing (2)
  • cla-backend-go/github/github_repository.go
  • cla-backend-go/v2/sign/service.go

Comment thread cla-backend-go/v2/sign/service.go Outdated
Comment thread cla-backend-go/v2/sign/service.go Outdated
@psrsingh
Copy link
Copy Markdown
Contributor Author

Please rebase to newest dev - my PRs were just mered.

Rebased onto the latest dev and force-pushed the updated branch. Also addressed the metadata validation feedback and active-signature return URL handling changes. PTAL.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cla-backend-go/v2/sign/service.go`:
- Around line 2299-2327: The service method (s *service)
getInstallationIDFromRepositoryID currently returns (0, nil) when
githubOrg.OrganizationInstallationID == 0; update this method to mirror the
package-level getInstallationIDFromRepositoryID behavior by creating and
returning an explicit error (e.g., errors.New("missing installation ID for
repository ID in metadata")), logging it with the same log fields and WithError,
and returning (0, err) so callers like getIndividualSignatureCallbackURL receive
a non-nil error instead of nil.
- Around line 1572-1591: The code calls metadataStringValue in sign/service.go
(used to extract repositoryID and mergeRequestID) but no such helper exists; add
a metadataStringValue helper in the sign package (or replace these calls with
the correct existing helper) that accepts the same metadata type used in this
file, returns (string, error) and performs the trim/empty check and error
creation (used by repositoryID and mergeRequestID extraction), then update
imports/visibility so sign/service.go compiles using that function name.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 11baa223-8e04-428f-8ed2-3995c38dba6b

📥 Commits

Reviewing files that changed from the base of the PR and between 50b6f0e and d911969.

📒 Files selected for processing (1)
  • cla-backend-go/v2/sign/service.go

Comment thread cla-backend-go/v2/sign/service.go Outdated
Comment thread cla-backend-go/v2/sign/service.go Outdated
@lukaszgryglicki
Copy link
Copy Markdown
Member

Please take a look at the newest coderabbit review, also is this PR a fix for a specific issue? If so, which one? Becaus eit touches return URL functionality in a way that requires more IDs to be set in active signature metadata and might potentially break workign flows. So the question is why do we need this?

CC @mlehotskylf - PTAL on this PR please.

AI feedback:

Two correctness items are still valid:

  1. metadataStringValue is still a compile issue.

This PR calls metadataStringValue(metadata, "repository_id") and metadataStringValue(metadata, "merge_request_id") in getIndividualSignatureCallbackURLGitlab, but I do not see an existing metadataStringValue helper in the current sign package. There is a similarly named metadataString(...) helper elsewhere in the source, but it is not the same function name/signature and does not return (string, error), so I would not rely on it as-is.

Minimal fix: either add metadataStringValue near the sign helpers/service code, or replace the calls with direct extraction. If keeping the helper name, something like this is enough:

func metadataStringValue(metadata map[string]interface{}, key string) (string, error) {
	if metadata == nil {
		return "", fmt.Errorf("missing %s in metadata", key)
	}
	value, ok := metadata[key]
	if !ok || value == nil {
		return "", fmt.Errorf("missing %s in metadata", key)
	}
	str := strings.TrimSpace(fmt.Sprintf("%v", value))
	if str == "" || str == "<nil>" {
		return "", fmt.Errorf("missing %s in metadata", key)
	}
	return str, nil
}

After that, the repeated strings.TrimSpace(...) == "" checks after each helper call can be removed, but that is optional.

  1. Please also update the existing service method func (s *service) getInstallationIDFromRepositoryID(...).

The new package-level getInstallationIDFromRepositoryID(...) added by this PR correctly returns an explicit error when OrganizationInstallationID == 0, but the existing service method still returns 0, err in that case, where err can be nil. That service method is still used by getIndividualSignatureCallbackURL, so this path can still silently continue with installation ID 0.

Minimal fix in the service method:

if installationId == 0 {
	err = errors.New("missing installation ID for repository ID in metadata")
	log.WithFields(f).WithError(err).Warnf("unable to get installation ID for repository ID: %s", repositoryID)
	return 0, err
}

@lukaszgryglicki
Copy link
Copy Markdown
Member

Looks like I know why CI fails - it is because the PR is from fork and forks don't get GitHub repo's secrets populated and some of repos used by go dependencies are private, so build/fetching fails and CI is red. Investigating/Researching this.

@psrsingh
Copy link
Copy Markdown
Contributor Author

Looks like I know why CI fails - it is because the PR is from fork and forks don't get GitHub repo's secrets populated and some of repos used by go dependencies are private, so build/fetching fails and CI is red. Investigating/Researching this.

That makes sense — thanks for digging into it.

The error does line up with private dependency access failing from forked PR workflows, especially since GitHub Actions does not expose repository secrets to forks by default. So this likely isn’t related to the SSS changes themselves.

I’ll continue cleaning up the remaining review feedback on my side while you investigate the CI setup/dependency access path. PTAL once the workflow situation is sorted.

@lukaszgryglicki
Copy link
Copy Markdown
Member

Thanks, I'm thinking about adjusting CI for forks, so it just red-fail. After initial research I can see that without making lfx-kit and lfx-models public CI cannot just build easycla and do tests, so maybe this should be skipped for forks, keeping other CI jobs that are able to proceed.

@psrsingh
Copy link
Copy Markdown
Contributor Author

Thanks, I'm thinking about adjusting CI for forks, so it just red-fail. After initial research I can see that without making lfx-kit and lfx-models public CI cannot just build easycla and do tests, so maybe this should be skipped for forks, keeping other CI jobs that are able to proceed.

I applied the requested changes in v2/sign/service.go.

Mainly:

  • added a small shared helper for metadata extraction/validation
  • updated getInstallationIDFromRepositoryID so it returns an actual error instead of silently continuing with installation ID 0

I kept the changes minimal and did not touch dependency/module setup since the current CI issue seems unrelated to the PR changes themselves.
Also, is there anything else you’d like me to adjust in this PR?

@lukaszgryglicki
Copy link
Copy Markdown
Member

lukaszgryglicki commented May 27, 2026

Once this is merged #5073 - CI should be green for forks after rebasing.

@lukaszgryglicki
Copy link
Copy Markdown
Member

You can rebase the PR now, thanks, LMK when I can re-review it.

@lukaszgryglicki
Copy link
Copy Markdown
Member

Looks like this PR will have conflicts with #5058 after merged, and I woudl prefer to get #5058 merged first, I'll provide my feedback on #5058 .

@psrsingh
Copy link
Copy Markdown
Contributor Author

Looks like this PR will have conflicts with #5058 after merged, and I woudl prefer to get #5058 merged first, I'll provide my feedback on #5058 .

got it , thanks !

@lukaszgryglicki
Copy link
Copy Markdown
Member

#5058 is close to merge, so you will see what needs to be updated after rebasing when we merge #5058 .

@lukaszgryglicki
Copy link
Copy Markdown
Member

@psrsingh #5058 was merged, please rebase/reconsider this one. Ping me when ready for review. Thanks.

Copy link
Copy Markdown
Member

@lukaszgryglicki lukaszgryglicki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs rebase and updates after #5058 was merged.

psrsingh added 2 commits May 28, 2026 16:45
Signed-off-by: psrsingh <psr.singh336@gmail.com>
Signed-off-by: psrsingh <psr.singh336@gmail.com>
@psrsingh psrsingh force-pushed the fix-github-return-url-validation branch from f6aee7b to b009a3a Compare May 28, 2026 11:26
@psrsingh
Copy link
Copy Markdown
Contributor Author

Needs rebase and updates after #5058 was merged.

Rebased on latest dev and force-pushed the updated branch. There was a merge conflict in v2/sign/service.go, but it’s resolved now.

@lukaszgryglicki
Copy link
Copy Markdown
Member

OK, is this ready for review?

@psrsingh
Copy link
Copy Markdown
Contributor Author

OK, is this ready for review?

Yep, it’s ready for review now.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
cla-backend-go/v2/sign/service.go (1)

1588-1603: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use metadataStringValue for callback metadata extraction to reject empty values consistently.

These blocks still use raw .(string) assertions, so "" passes and can propagate malformed callback URLs. Reuse metadataStringValue here to enforce trim/empty/"<nil>" checks uniformly.

Proposed minimal fix
 func (s *service) getIndividualSignatureCallbackURLGitlab(ctx context.Context, userID string, metadata map[string]interface{}) (string, error) {
@@
-	if found, ok := metadata["repository_id"].(string); ok {
-		repositoryID = found
-	} else {
-		err = errors.New("missing repository_id in metadata")
+	repositoryID, err = metadataStringValue(metadata, "repository_id")
+	if err != nil {
 		log.WithFields(f).WithError(err).Warnf("unable to get repository ID for user: %s", userID)
 		return "", err
 	}
 
-	if found, ok := metadata["merge_request_id"].(string); ok {
-		mergeRequestID = found
-	} else {
-		err = errors.New("missing merge_request_id in metadata")
+	mergeRequestID, err = metadataStringValue(metadata, "merge_request_id")
+	if err != nil {
 		log.WithFields(f).WithError(err).Warnf("unable to get merge request ID for user: %s", userID)
 		return "", err
 	}
@@
 func (s *service) getIndividualSignatureCallbackURL(ctx context.Context, userID string, metadata map[string]interface{}) (string, error) {
@@
-	if found, ok := metadata["repository_id"].(string); ok {
-		repositoryID = found
-	} else {
-		err = errors.New("missing repository_id in metadata")
+	repositoryID, err = metadataStringValue(metadata, "repository_id")
+	if err != nil {
 		log.WithFields(f).WithError(err).Warnf("unable to get repository ID for user: %s", userID)
 		return "", err
 	}
@@
-	if found, ok := metadata["pull_request_id"].(string); ok {
-		pullRequestID = found
-	} else {
-		err = errors.New("missing pull_request_id in metadata")
+	pullRequestID, err = metadataStringValue(metadata, "pull_request_id")
+	if err != nil {
 		log.WithFields(f).WithError(err).Warnf("unable to get pull request ID for user: %s", userID)
 		return "", err
 	}

Also applies to: 1650-1667

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cla-backend-go/v2/sign/service.go` around lines 1588 - 1603, Replace the raw
type assertions for repository_id and merge_request_id with the shared helper
metadataStringValue so empty/whitespace/"<nil>" values are rejected
consistently: call metadataStringValue(metadata, "repository_id") and assign to
repositoryID (handle returned error like the existing branches), and likewise
call metadataStringValue(metadata, "merge_request_id") to set mergeRequestID;
update the error handling/logging to use the helper's error and keep the same
log.WithFields(f).WithError(err).Warnf("unable to get ... for user: %s", userID)
pattern (affecting the blocks that set repositoryID and mergeRequestID in
service.go, and the similar block around the other occurrence noted).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@cla-backend-go/v2/sign/service.go`:
- Around line 1588-1603: Replace the raw type assertions for repository_id and
merge_request_id with the shared helper metadataStringValue so
empty/whitespace/"<nil>" values are rejected consistently: call
metadataStringValue(metadata, "repository_id") and assign to repositoryID
(handle returned error like the existing branches), and likewise call
metadataStringValue(metadata, "merge_request_id") to set mergeRequestID; update
the error handling/logging to use the helper's error and keep the same
log.WithFields(f).WithError(err).Warnf("unable to get ... for user: %s", userID)
pattern (affecting the blocks that set repositoryID and mergeRequestID in
service.go, and the similar block around the other occurrence noted).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f29c5bbf-2416-49cb-94bf-8281aeaaa021

📥 Commits

Reviewing files that changed from the base of the PR and between f6aee7b and b009a3a.

📒 Files selected for processing (2)
  • cla-backend-go/github/github_repository.go
  • cla-backend-go/v2/sign/service.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • cla-backend-go/github/github_repository.go

Copy link
Copy Markdown
Member

@lukaszgryglicki lukaszgryglicki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. metadataStringValue (service.go:1554) is declared but never called — the four sites at service.go:1588, 1596, 1650, 1660 still use the old metadata["..."].(string) assertion. Wire the helper into those sites, or drop the helper.

  2. getInstallationIDFromRepositoryID error wording: revert

    - err = fmt.Errorf("installation ID missing for repository ID: %s", repositoryID)
    + err = fmt.Errorf("missing installation ID for repository ID in metadata: %s", repositoryID)

    PR #5058 just landed this line; rewording it is churn, and "in metadata" is misleading (value is missing from the GitHub org record, not metadata).

  3. GetReturnURL — drop the redundant inner nil checks. go-github's repo.GetOwner().GetLogin() and repo.GetName() (and pullRequest.GetHTMLURL()) are nil-safe and return "" on any nil in the chain, so the owner == "" || name == "" (resp. empty-HTML-URL) check already covers everything. Keep only if repo == nil / if pullRequest == nil plus the empty-string checks.

Signed-off-by: psrsingh <psr.singh336@gmail.com>
@psrsingh
Copy link
Copy Markdown
Contributor Author

@lukaszgryglicki Addressed the review comments and pushed the updates.

Copy link
Copy Markdown
Member

@lukaszgryglicki lukaszgryglicki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/lgtm

@lukaszgryglicki lukaszgryglicki merged commit a56985f into linuxfoundation:dev May 28, 2026
9 checks passed
@lukaszgryglicki
Copy link
Copy Markdown
Member

Lint failed - I will fix this small issue in post-merge.

@psrsingh
Copy link
Copy Markdown
Contributor Author

Lint failed - I will fix this small issue in post-merge.

Thanks a lot for the help and reviews, really appreciate it!

@lukaszgryglicki
Copy link
Copy Markdown
Member

The problem is that lint cannot run on PRs from forks, so I need to do minimal changes in post-merge, like this: #5077

@psrsingh
Copy link
Copy Markdown
Contributor Author

The problem is that lint cannot run on PRs from forks, so I need to do minimal changes in post-merge, like this: #5077

Understood, makes sense.

@lukaszgryglicki
Copy link
Copy Markdown
Member

This is now deployed to dev.
You can test whatever you need either using dev-deployed API or on the repo where dev app is enabled, for example here.

@psrsingh
Copy link
Copy Markdown
Contributor Author

This is now deployed to dev. You can test whatever you need either using dev-deployed API or on the repo where dev app is enabled, for example here.

Thanks! I'll test it and let you know if I find any issues.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

EasyCLA Bug Report - Blank screen after clicking URL from GitHub bot, appears to brick the EasyCLA login site for the browser

2 participants