21 KiB
21 KiB
Security Attack Vector Mapping
CODEX.md was not present in /workspace, so this review follows CLAUDE.md plus the repository conventions already in force. No fixes were made.
Scope
- Target: all non-test external input entry points in the library and
cmd/forgegen - Output format:
function,file:line,input source,what it flows into,current validation,potential attack vector - Line ranges below are grouped when a row covers multiple adjacent methods with the same behaviour
Cross-Cutting Sinks
These internal paths are reached by most public API methods and are the main places where untrusted data actually crosses the trust boundary.
| Function | File:line | Input source | What it flows into | Current validation | Potential attack vector |
|---|---|---|---|---|---|
Client.doJSON |
client.go:216-260 |
Caller-supplied path and body; remote Forgejo JSON/body/headers |
baseURL + path, json.Marshal(body), http.NewRequestWithContext, http.Client.Do, json.Decoder.Decode(out) |
Request method is fixed by caller; JSON marshal failure stops request; http.NewRequestWithContext validates URL syntax; no body-size cap; no response-size cap; no DisallowUnknownFields |
Arbitrary request path within configured host; query/fragment injection if upstream callers pass unescaped path segments; request/response memory exhaustion; malicious Forgejo can return oversized or schema-confusing JSON |
Client.parseError |
client.go:263-285 |
Remote error body | Up to 1024 bytes copied into APIError.Message |
Size capped at 1 KiB; best-effort JSON parse only | Server-controlled error text can propagate to logs/UI and support log-forging or misleading diagnostics |
Client.updateRateLimit |
client.go:287-297 |
Remote rate-limit headers | Client.rateLimit cache |
Parses with Atoi/ParseInt; conversion errors ignored |
Malicious or buggy server can poison rate-limit telemetry and mislead callers about throttling state |
Client.PostRaw |
client.go:137-176 |
Caller-supplied path and body; remote raw response body |
http.Client.Do then io.ReadAll(resp.Body) |
JSON marshal only; no response-size cap; no content-type check | Memory exhaustion on large responses; raw HTML/text returned to caller can become XSS/content-injection if rendered downstream |
Client.GetRaw |
client.go:180-208 |
Caller-supplied path; remote raw response body |
http.Client.Do then io.ReadAll(resp.Body) |
No response-size cap; no content-type check | Memory exhaustion on large file responses; untrusted bytes flow straight to caller |
ListPage |
pagination.go:32-70 |
Caller-supplied path, query, opts; remote X-Total-Count and JSON list body |
url.Parse(path), query encoding, Client.doJSON |
Page/Limit clamped to minimum 1; query values encoded with url.Values; no max page/limit cap |
Arbitrary in-host endpoint access if caller controls path; malicious server can lie in X-Total-Count and increase work or confuse pagination state |
ListAll, ListIter |
pagination.go:73-113 |
Remote pagination metadata and page contents | Repeated ListPage calls until HasMore == false |
No max page bound; loop stops only when server indicates completion or returns a short page | Request amplification / unbounded pagination against a malicious or buggy server |
ResolvePath |
params.go:13-17 |
Caller-supplied path template and placeholder values | strings.ReplaceAll with url.PathEscape(v) |
Placeholder values are path-escaped | Prevents slash/query breakout from placeholder values, but does not verify required placeholders are present or that the template itself is trusted |
Entry Points
| Function | File:line | Input source | What it flows into | Current validation | Potential attack vector |
|---|---|---|---|---|---|
ResolveConfig |
config.go:21-35 |
Environment variables FORGE_URL, FORGE_TOKEN; caller-supplied flag values |
Returned url/token consumed by NewForgeFromConfig/NewClient |
Priority only: flags override env, empty URL falls back to http://localhost:3000 |
If env/flags are attacker-controlled, requests and auth token can be redirected to an attacker-chosen Forgejo instance, including cleartext http:// |
NewForgeFromConfig, NewForge, NewClient |
config.go:39-48; forge.go:29-51; client.go:81-95 |
Caller-supplied url, token, opts |
Client.baseURL, Authorization header, service wiring |
NewForgeFromConfig only checks token is non-empty; NewClient trims trailing /; redirects disabled by default |
SSRF/token exfiltration to attacker-controlled host; accidental cleartext transport; malformed base URL causes runtime request failures instead of early rejection |
WithHTTPClient, WithUserAgent |
client.go:50-56 |
Caller-supplied *http.Client and User-Agent string |
Overrides transport/TLS/proxy behaviour and User-Agent header |
No validation | Untrusted caller can supply insecure transport settings or proxy all traffic; arbitrary UA content is forwarded to downstream logs/telemetry |
Client.Get, Client.Post, Client.Patch, Client.Put, Client.Delete, Client.DeleteWithBody |
client.go:99-130 |
Caller-supplied path and optional request body; remote JSON/error responses |
Thin wrappers around Client.doJSON |
No local path validation; body only has JSON-marshalling checks | Direct low-level escape hatch to any in-host API path; if upstream code passes unescaped path segments, this layer will forward them verbatim |
NewResource |
resource.go:20-30 |
Caller-supplied item path template | Stored path/collection later used by CRUD methods |
Only strips a trailing pure placeholder segment when deriving collection |
If a library consumer builds Resource from untrusted templates, later CRUD calls can target arbitrary API paths |
Resource.List, Resource.ListAll, Resource.Iter, Resource.Get, Resource.Create, Resource.Update, Resource.Delete as surfaced by RepoService, IssueService, PullService, OrgService, UserService, TeamService, BranchService, ReleaseService, WebhookService |
resource.go:34-77; repos.go:17-19; issues.go:18-20; pulls.go:18-20; orgs.go:18-20; users.go:18-20; teams.go:18-20; branches.go:18-20; releases.go:18-20; webhooks.go:19-20 |
Caller-supplied Params placeholder values and typed request bodies; remote JSON responses |
ResolvePath then ListPage/ListAll/ListIter or Client.Get/Post/Patch/Delete |
Placeholder values path-escaped; request bodies are type-shaped only; no required-param check; no semantic validation | Lower path-injection risk than the fmt.Sprintf call sites, but missing placeholders can still hit wrong paths; oversized/malformed bodies depend entirely on server-side validation; all response-size/pagination risks from the shared sinks still apply |
CommitService.List, CommitService.ListAll, CommitService.Iter, CommitService.Get |
commits.go:29-48 |
Caller-supplied Params (owner, repo, sha/ref) |
ResolvePath into ListPage/ListAll/ListIter/Client.Get |
Uses ResolvePath; no semantic validation of sha/ref |
Same as generic Resource methods: path breakout is mitigated, but placeholder omissions and unbounded remote responses remain possible |
AdminService.EditUser, AdminService.DeleteUser, AdminService.RenameUser, AdminService.RunCron, AdminService.AdoptRepo |
admin.go:41-57; admin.go:69-86 |
Caller-supplied username, newName, task, owner, repo, opts |
ResolvePath then Client.Patch/Post/Delete |
Path values escaped by ResolvePath; opts for EditUser is an unrestricted map[string]any |
Path breakout is mitigated, but EditUser forwards arbitrary JSON fields and all methods rely on server-side validation for semantics and size |
Fixed-path network fetch/update methods: AdminService.ListUsers, AdminService.IterUsers, AdminService.CreateUser, AdminService.ListOrgs, AdminService.IterOrgs, AdminService.ListCron, AdminService.IterCron, AdminService.GenerateRunnerToken, RepoService.ListUserRepos, RepoService.IterUserRepos, OrgService.ListMyOrgs, OrgService.IterMyOrgs, UserService.GetCurrent, NotificationService.List, NotificationService.Iter, NotificationService.MarkRead, NotificationService.GetThread, NotificationService.MarkThreadRead, MiscService.ListLicenses, MiscService.ListGitignoreTemplates, MiscService.GetNodeInfo, MiscService.GetVersion |
admin.go:22-39; admin.go:59-91; repos.go:34-39; orgs.go:61-66; users.go:25-31; notifications.go:22-59; misc.go:34-81 |
Remote Forgejo responses; for CreateUser, caller-supplied typed body; for thread methods, numeric IDs |
Fixed API paths or %d path formatting into Client or pagination helpers |
No local validation beyond Go types and integer formatting | Main risk is trust in remote server responses plus unbounded pagination/JSON decode; CreateUser also forwards request body without field-level validation |
MiscService.RenderMarkdown |
misc.go:24-31 |
Caller-supplied markdown text and mode; remote raw HTML response |
types.MarkdownOption -> Client.PostRaw -> raw string return |
Struct-shaped body only; no output sanitisation | If caller renders the returned HTML directly, untrusted markdown or compromised server output can become XSS/content injection; large responses can exhaust memory |
ActionsService.ListRepoSecrets, ActionsService.IterRepoSecrets, ActionsService.CreateRepoSecret, ActionsService.DeleteRepoSecret, ActionsService.ListRepoVariables, ActionsService.IterRepoVariables, ActionsService.CreateRepoVariable, ActionsService.DeleteRepoVariable, ActionsService.ListOrgSecrets, ActionsService.IterOrgSecrets, ActionsService.ListOrgVariables, ActionsService.IterOrgVariables, ActionsService.DispatchWorkflow |
actions.go:23-99 |
Caller-supplied owner, repo, org, name, workflow, data, value, opts; remote JSON responses |
fmt.Sprintf path building -> Client.Get/Put/Post/Delete or pagination helpers |
No path escaping; bodies are either simple structs/maps or raw strings | Path/endpoint confusion via /, ?, #, or encoded separators in names/orgs/repos/workflow IDs; DispatchWorkflow forwards arbitrary JSON fields via map[string]any; pagination/response risks still apply |
BranchService.ListBranchProtections, BranchService.IterBranchProtections, BranchService.GetBranchProtection, BranchService.CreateBranchProtection, BranchService.EditBranchProtection, BranchService.DeleteBranchProtection |
branches.go:25-67 |
Caller-supplied owner, repo, name, typed option structs; remote JSON responses |
fmt.Sprintf path building -> Client.Get/Post/Patch/Delete or pagination helpers |
No path escaping; body shape only | Unescaped branch-protection names, owners, or repos can alter the targeted API path; body content is unchecked locally |
CommitService.GetCombinedStatus, CommitService.ListStatuses, CommitService.CreateStatus, CommitService.GetNote |
commits.go:53-83 |
Caller-supplied owner, repo, ref/sha, typed status body; remote JSON responses |
fmt.Sprintf path building -> Client.Get/Post |
No path escaping; body shape only | Branch names/refs containing /, ?, or # can retarget requests or make legitimate refs unreachable; body fields rely on server validation |
ContentService.GetFile, ContentService.CreateFile, ContentService.UpdateFile, ContentService.DeleteFile, ContentService.GetRawFile |
contents.go:21-57 |
Caller-supplied owner, repo, filepath, typed file option structs; remote JSON/raw file responses |
fmt.Sprintf path building -> Client.Get/Post/Put/DeleteWithBody/GetRaw |
No path escaping; nested filepath is passed through verbatim |
filepath is intentionally free-form but reserved characters such as ?, #, and ambiguous percent-encoding can still alter routing/query semantics; raw file reads have no size cap |
IssueService.Pin, IssueService.Unpin, IssueService.SetDeadline, IssueService.AddReaction, IssueService.DeleteReaction, IssueService.StartStopwatch, IssueService.StopStopwatch, IssueService.AddLabels, IssueService.RemoveLabel, IssueService.ListComments, IssueService.IterComments, IssueService.CreateComment |
issues.go:25-102 |
Caller-supplied owner, repo, numeric IDs, deadline/reaction/body/label list; remote JSON responses |
fmt.Sprintf path building -> Client.* and pagination helpers |
No path escaping for owner/repo; request bodies are raw strings, slices, or simple structs |
Owner/repo path breakout; comment/reaction/deadline values go straight upstream with no local validation or size limits |
LabelService.ListRepoLabels, LabelService.IterRepoLabels, LabelService.GetRepoLabel, LabelService.CreateRepoLabel, LabelService.EditRepoLabel, LabelService.DeleteRepoLabel, LabelService.ListOrgLabels, LabelService.IterOrgLabels, LabelService.CreateOrgLabel |
labels.go:22-89 |
Caller-supplied owner, repo, org, numeric IDs, typed option structs; remote JSON responses |
fmt.Sprintf path building -> Client.* and pagination helpers |
No path escaping for string path segments; body shape only | Path/endpoint confusion through unescaped owner/repo/org values; no local validation on label metadata |
MilestoneService.ListAll, MilestoneService.Get, MilestoneService.Create |
milestones.go:20-44 |
Caller-supplied params["owner"], params["repo"], owner, repo, typed create body; remote JSON responses |
fmt.Sprintf path building -> pagination helpers or Client.Get/Post |
No path escaping; missing map keys become empty path segments | Path manipulation and silent empty-segment requests if owner/repo are absent or attacker-controlled; no local validation on milestone body |
MiscService.GetLicense, MiscService.GetGitignoreTemplate |
misc.go:43-69 |
Caller-supplied template name; remote JSON responses | fmt.Sprintf path building -> Client.Get |
No path escaping | Template names with reserved characters can alter the request path/query |
NotificationService.ListRepo, NotificationService.IterRepo |
notifications.go:32-40 |
Caller-supplied owner, repo; remote JSON responses |
fmt.Sprintf path building -> pagination helpers |
No path escaping | Owner/repo path breakout or query injection against repo-scoped notification endpoints |
OrgService.ListMembers, OrgService.IterMembers, OrgService.AddMember, OrgService.RemoveMember, OrgService.ListUserOrgs, OrgService.IterUserOrgs |
orgs.go:25-55 |
Caller-supplied org, username; remote JSON responses |
fmt.Sprintf path building -> Client.* and pagination helpers |
No path escaping | Unescaped org/user names can alter the intended endpoint path |
PackageService.List, PackageService.Iter, PackageService.Get, PackageService.Delete, PackageService.ListFiles, PackageService.IterFiles |
packages.go:22-61 |
Caller-supplied owner, pkgType, name, version; remote JSON responses |
fmt.Sprintf path building -> Client.* and pagination helpers |
No path escaping | Package coordinates with /, ?, #, or crafted percent-encoding can retarget the request or make legitimate versions unreachable |
PullService.Merge, PullService.Update, PullService.ListReviews, PullService.IterReviews, PullService.SubmitReview, PullService.DismissReview, PullService.UndismissReview |
pulls.go:25-69 |
Caller-supplied owner, repo, numeric IDs, method, review, msg; remote JSON responses |
fmt.Sprintf path building -> Client.* and pagination helpers |
No path escaping for string path segments; SubmitReview forwards unrestricted map[string]any |
Owner/repo path breakout; Merge method value is unchecked locally; SubmitReview can forward arbitrary JSON fields to the API |
RepoService.ListOrgRepos, RepoService.IterOrgRepos, RepoService.Fork, RepoService.Transfer, RepoService.AcceptTransfer, RepoService.RejectTransfer, RepoService.MirrorSync |
repos.go:24-73 |
Caller-supplied org, owner, repo, transfer opts; remote JSON responses |
String concatenation / map[string]string / map[string]any -> Client.* and pagination helpers |
No path escaping; Transfer body unrestricted; Fork only conditionally adds organization body field |
Owner/repo/org path breakout; Transfer is a direct arbitrary-JSON passthrough; body size/field semantics rely on server enforcement |
ReleaseService.GetByTag, ReleaseService.DeleteByTag, ReleaseService.ListAssets, ReleaseService.IterAssets, ReleaseService.GetAsset, ReleaseService.DeleteAsset |
releases.go:25-68 |
Caller-supplied owner, repo, tag, numeric IDs; remote JSON responses |
fmt.Sprintf path building -> Client.* and pagination helpers |
No path escaping for string segments | Tag names or repo identifiers with reserved characters can alter endpoint targeting |
TeamService.ListMembers, TeamService.IterMembers, TeamService.ListRepos, TeamService.IterRepos, TeamService.AddMember, TeamService.RemoveMember, TeamService.AddRepo, TeamService.RemoveRepo, TeamService.ListOrgTeams, TeamService.IterOrgTeams |
teams.go:25-79 |
Caller-supplied teamID, username, org, repo; remote JSON responses |
fmt.Sprintf path building -> Client.* and pagination helpers |
Numeric IDs are formatted safely; string path segments are unescaped | Numeric-only methods are lower risk, but the member/repo/org methods still allow path confusion through unescaped string segments |
UserService.ListFollowers, UserService.IterFollowers, UserService.ListFollowing, UserService.IterFollowing, UserService.Follow, UserService.Unfollow, UserService.ListStarred, UserService.IterStarred, UserService.Star, UserService.Unstar |
users.go:34-88 |
Caller-supplied username, owner, repo; remote JSON responses |
fmt.Sprintf path building -> Client.* and pagination helpers |
No path escaping | Username/owner/repo values can alter targeted endpoints or inject query/fragment data |
WebhookService.TestHook, WebhookService.ListOrgHooks, WebhookService.IterOrgHooks |
webhooks.go:26-40 |
Caller-supplied owner, repo, org, numeric id; remote JSON responses |
fmt.Sprintf path building -> Client.Post and pagination helpers |
Numeric ID formatting only for id; no path escaping for string segments |
Owner/repo/org values can alter endpoint targeting; organisation hook enumeration inherits pagination risks |
WikiService.ListPages, WikiService.GetPage, WikiService.CreatePage, WikiService.EditPage, WikiService.DeletePage |
wiki.go:21-61 |
Caller-supplied owner, repo, pageName, typed page body; remote JSON responses |
fmt.Sprintf path building -> Client.Get/Post/Patch/Delete |
No path escaping | pageName is free-form and may legitimately contain slashes, so reserved characters can change routing or query semantics; page content/body is forwarded without local validation |
main |
cmd/forgegen/main.go:9-30 |
CLI flags -spec, -out |
LoadSpec, ExtractTypes, DetectCRUDPairs, Generate |
Standard flag parsing only |
Untrusted operator input can point the generator at sensitive local files or arbitrary output directories |
LoadSpec |
cmd/forgegen/parser.go:73-84 |
Caller/CLI-supplied file path and file contents | coreio.Local.Read(path) then json.Unmarshal into Spec |
Parse failure only; entire file read into memory | Arbitrary local file read if path is attacker-controlled; large or malformed JSON can exhaust memory or CPU |
ExtractTypes, DetectCRUDPairs |
cmd/forgegen/parser.go:86-147 |
Parsed, potentially untrusted Swagger definitions and metadata | In-memory GoType/CRUDPair model used by Generate |
No schema hardening beyond basic JSON types; names/comments/enum values are accepted as-is | Malicious spec can create huge intermediate structures or feed unsafe identifiers/strings into code generation |
Generate |
cmd/forgegen/generator.go:206-263 |
Caller-supplied outDir; type names/comments/enum values derived from the spec |
EnsureDir(outDir), filepath.Join(outDir, file+".go"), Go source template, coreio.Local.Write |
Output file names are limited by classifyType; generated source content is mostly unsanitised spec data |
Arbitrary directory creation/file overwrite if outDir is attacker-controlled; malicious spec strings can break generated Go syntax or inject unexpected source content into generated files |
Highest-Risk Patterns
- Unescaped
fmt.Sprintf/string-concatenated paths are the dominant library issue pattern. Most custom service methods trust rawowner,repo,org,username,name,tag,workflow,pageName, and similar values. - The
Resource/ResolvePathpath family is materially safer because it usesurl.PathEscape, but it still performs no semantic validation and still inherits unbounded response/pagination behaviour. cmd/forgegenassumes the spec file is trusted. If the swagger input is attacker-controlled, the generator can read arbitrary files, write arbitrary directories, and emit attacker-influenced Go source.