Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions cmd/api/src/database/migration/migrations/v8.7.0.sql
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,7 @@ CREATE TABLE IF NOT EXISTS schema_list_findings (

CREATE INDEX IF NOT EXISTS idx_schema_list_findings_extension_id ON schema_list_findings (schema_extension_id);
CREATE INDEX IF NOT EXISTS idx_schema_list_findings_environment_id ON schema_list_findings(environment_id);

-- Update the 'auth_tokens' Table adding Expiration Column
ALTER TABLE auth_tokens
ADD COLUMN IF NOT EXISTS expires_at timestamp with time zone;
Copy link
Contributor

Choose a reason for hiding this comment

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

We want to treat this as a nullable field, but this is not creating it as nullable

6 changes: 5 additions & 1 deletion cmd/api/src/model/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ type AuthToken struct {
Key string `json:"key,omitempty"`
HmacMethod string `json:"hmac_method"`
LastAccess time.Time `json:"last_access"`
Expiration null.Time `json:"expires_at"`
Copy link
Contributor

@coderabbitai coderabbitai bot Feb 25, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== AuthToken field/tag check =="
rg -n -C2 'type AuthToken struct|Expiration|null.Time|expires_at|gorm:"column:expires_at"' cmd/api/src/model/auth.go

echo
echo "== GORM naming strategy check (repo-wide) =="
rg -n -C3 'gorm\.Config|NamingStrategy|schema\.NamingStrategy|NameReplacer|ColumnName' --type=go cmd/api/src

echo
echo "== Column name references for auth_tokens =="
rg -n -C2 '\bauth_tokens\b|\bexpires_at\b|\bexpiration\b' cmd/api/src

Repository: SpecterOps/BloodHound

Length of output: 14522


Add explicit GORM column mapping to prevent mismatch with database schema.

At line 154, the Expiration field lacks a gorm:"column:expires_at" tag. GORM's default naming convention converts Expiration to expiration, but the migration creates the column as expires_at. This mismatch will cause reads and writes on this field to target the wrong column.

Fix
-	Expiration null.Time	 `json:"expires_at"`
+	Expiration null.Time   `json:"expires_at" gorm:"column:expires_at"`
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Expiration null.Time `json:"expires_at"`
Expiration null.Time `json:"expires_at" gorm:"column:expires_at"`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/api/src/model/auth.go` at line 154, The Expiration field in the auth
model is missing an explicit GORM column mapping which causes GORM to target
"expiration" instead of the DB column "expires_at"; update the struct field
Expiration (in cmd/api/src/model/auth.go) to include a gorm tag mapping to
column:expires_at (e.g., add gorm:"column:expires_at" alongside the existing
json tag) so reads/writes use the correct database column.

Copy link
Contributor

Choose a reason for hiding this comment

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

The other way to fix this is to use the same name: ExpiresAt instead of Expiration

Copy link
Contributor

Choose a reason for hiding this comment

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

@superlinkx You're absolutely right! Renaming the field to ExpiresAt is a better solution. It follows the existing convention in the codebase (e.g., AuthSecret.ExpiresAt) and is more idiomatic. GORM will automatically convert ExpiresAt to expires_at via snake_case, matching the database column.

This approach is cleaner and more consistent with the rest of the codebase. The StripKey() method would also need updating to use ExpiresAt instead of Expiration:

func (s AuthToken) StripKey() AuthToken {
	return AuthToken{
		UserID:     s.UserID,
		ClientID:   s.ClientID,
		Key:        "",
		HmacMethod: s.HmacMethod,
		LastAccess: s.LastAccess,
		Unique:     s.Unique,
		Name:       s.Name,
		ExpiresAt:  s.ExpiresAt,
	}
}

✏️ Learnings added
Learnt from: superlinkx
Repo: SpecterOps/BloodHound PR: 2421
File: cmd/api/src/model/auth.go:154-154
Timestamp: 2026-02-25T23:53:13.603Z
Learning: In the BloodHound codebase (SpecterOps/BloodHound), time-based expiration fields should be named `ExpiresAt` (not `Expiration`) to follow existing conventions (e.g., `AuthSecret.ExpiresAt`) and to allow GORM's automatic snake_case conversion to correctly map to `expires_at` database columns without requiring explicit `gorm:"column:expires_at"` tags.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: brandonshearin
Repo: SpecterOps/BloodHound PR: 2383
File: cmd/api/src/database/kind.go:61-69
Timestamp: 2026-02-25T20:04:47.906Z
Learning: In the BloodHound codebase (SpecterOps/BloodHound), GORM v1.25.x is used. For Raw queries with IN clauses that take slices, the correct syntax is `IN (?)` with parentheses (e.g., `WHERE id IN (?)`), not `IN ?`. This pattern is used consistently across the codebase in files like cmd/api/src/database/kind.go and cmd/api/src/database/sourcekinds.go.

Learnt from: superlinkx
Repo: SpecterOps/BloodHound PR: 1820
File: packages/go/stbernard/command/audit/audit.go:133-136
Timestamp: 2025-08-26T21:25:33.480Z
Learning: superlinkx prefers to receive feedback and identify issues as early as possible in the review process rather than later.

Learnt from: mvlipka
Repo: SpecterOps/BloodHound PR: 2382
File: cmd/api/src/api/v2/ad_related_entity.go:44-57
Timestamp: 2026-02-23T17:06:23.991Z
Learning: In BloodHound, AD nodes are represented by a base kind (ad.Entity) plus specific kinds (ad.User, ad.Computer, ad.Group, etc.), and Azure nodes by az.Entity. The query.KindIn function matches if a node has any of the specified kinds, so using the base kind (ad.Entity) will correctly include all AD nodes regardless of their specific subtype. During reviews, ensure code that filters or queries graph nodes via KindIn uses the base kind to capture all inherited subkinds for AD nodes, and similarly recognizes base kinds for Azure nodes. This design pattern should be consistently respected across files that model or query graph schema kinds.


Unique
}
Expand All @@ -174,6 +175,7 @@ func (s AuthToken) StripKey() AuthToken {
LastAccess: s.LastAccess,
Unique: s.Unique,
Name: s.Name,
Expiration: s.Expiration,
}
}

Expand All @@ -185,7 +187,8 @@ func (s AuthTokens) IsSortable(column string) bool {
"last_access",
"created_at",
"updated_at",
"deleted_at":
"deleted_at",
"expires_at":
return true
default:
return false
Expand All @@ -203,6 +206,7 @@ func (s AuthTokens) ValidFilters() map[string][]FilterOperator {
"created_at": {Equals, GreaterThan, GreaterThanOrEquals, LessThan, LessThanOrEquals, NotEquals},
"updated_at": {Equals, GreaterThan, GreaterThanOrEquals, LessThan, LessThanOrEquals, NotEquals},
"deleted_at": {Equals, GreaterThan, GreaterThanOrEquals, LessThan, LessThanOrEquals, NotEquals},
"expires_at": {Equals, GreaterThan, GreaterThanOrEquals, LessThan, LessThanOrEquals, NotEquals},
}
}

Expand Down
Loading