Conceptio › Archive › arXiv CS
arXiv CSopen access

The Security Feature Location Problem

· arxiv_cs
arXiv CS · Papers · License: Open Access
Open Source ↗Direct PDF ↓
cryptographycybersecurityprivacysecurity
cryptography, security, privacy, cybersecurity

The Security Feature Location Problem Kevin Hermann

Sven Peldszus

Ruhr University Bochum Bochum, Germany

Chalmers | University of Gothenburg Gothenburg, Sweden

Thorsten Berger

Adam Shostack

Ruhr University Bochum Bochum, Germany Chalmers | University of Gothenburg Gothenburg, Sweden

University of Washington Seattle, USA Shostack + Associates Seattle, USA

arXiv:2609.04899v1 [cs.CR] 4 Sep 2026

Abstract Software security must be realized through security features such as authentication and encryption, but which features does a system implement, and where? We present security feature location: the task of relating code locations to security features, enabling developers to understand security implementations and assess whether intended security properties are enforced.

Introduction Security features are concrete code-level mechanisms that serve as essential building blocks of secure software systems [2, 5, 6, 8, 14]. Typical security features, such as authentication, authorization, cryptographic protection, key management, session handling, and incident logging must be correctly designed, implemented, configured, and maintained to enforce security properties, such as confidentiality, integrity, and availability. Many security vulnerabilities arise when security features are absent, incomplete, or incorrectly implemented. Security features are rarely confined to a single method, class, or library. Figure 1 illustrates their cross-cutting nature by showing how permission checks are distributed in the source code of Traccar, an open-source GPS tracking system. These checks enforce access control over sensitive operations, including retrieving location data and accessing resources, such as reports. Permission checks must be performed in all code paths that lead to sensitive operations, which are scattered across the codebase, and often not easy to identify. A check alone does not provide enough information to determine whether access control is consistently enforced. Even when using third-party libraries to realize security features, the mere presence of a vulnerable library does not imply exploitability. A system is only exploitable if it invokes the affected security feature in a way that triggers the vulnerability. For example, CVE-202223540 in jsonwebtoken affects JSON Web Token (JWT) verification: omitting an explicit algorithm definition in a call of jwt.verify() together with passing a false key can lead to bypassing the signature validation. Assessing exploitability requires locating not only the vulnerable library, but also the code that invokes it and the code that influences the arguments passed to it, as illustrated below. 1 2 3 4 5 6 7

// may or may not return '' undefined '' const key = getVerificationKey () ; // vulnerable : key may be false + no algorithms jwt . verify ( token , key ); // also problematic : false key + no algorithms jwt . verify ( token , "" , { }) ; // safer : explicit acceptable algorithm

8

jwt . verify ( token , key , { algorithms : [" RS256 " ]}) ;

Both examples raise questions that code does not answer directly: Is access control enforced on every path? Is the vulnerable library invoked in an exploitable way? Such questions are posed over security properties, but must be answered through implementation in code. However, a single statement is too narrow, since the call to jwt.verify() does not reveal whether the key is valid or invalid. Likewise, common higher-level abstractions, such as a software bill of materials (SBOM), are too broad, as they only record the presence of jsonwebtoken, but not how it is used. Answering questions on security properties requires an intermediate level—security features—that gathers the code, configuration, and invocation context that together realize one security mechanism. Consider current and upcoming security regulations, which increasingly mandate how software is built and maintained. The Cyber Resilience Act, for instance, obligates manufacturers to handle vulnerabilities throughout the intended lifespans of their products and to report exploited ones within days. An SBOM names the affected dependency, but not whether the vulnerable mechanism is invoked, how it is configured, or whether it is reachable, which determines whether the product is affected and what must be fixed. Consequently, engineers have to locate the security features in their products. Locating security features, however, is hard. It requires maintaining an overview over the codebase, which is difficult, especially in large and long-lived software systems. Over time, functionality is added or modified, the software is reused through cloning, branching, or forking, and development teams change. When the required knowledge is not recorded, developers need to recover security features and their locations in code, which is laborious and error-prone, especially when developers are not familiar with the codebase [11]. They may miss security-critical code, leaving the system vulnerable to attacks. We refer to this challenge as the security feature location problem: given a software system, determine which security features it implements and where the corresponding code is located. Solving this problem has direct security impact: it enables developers to assess vulnerability-specific conditions, such as those of CVE-202223540, check compliance with security policies and standards, and modify relevant code securely, avoiding unintended changes to security features while supporting deliberate migrations such as from quantum-vulnerable cryptography to post-quantum alternatives.

Kevin Hermann, Sven Peldszus, Thorsten Berger, and Adam Shostack PermissionService.java

LoginService.java

AttributeResource.java

DevicesReportProvider.java

/* * Copyright 2022 - 2023 Anton Tananaev ([email protected]) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.traccar.api.security;

/* * Copyright 2022 - 2024 Anton Tananaev ([email protected]) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.traccar.api.security;

/* * Copyright 2016 - 2021 Anton Tananaev ([email protected]) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.traccar.api.resource;

/* * Copyright 2024 Anton Tananaev ([email protected]) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.traccar.reports;

import ...

import ...

import ...

import ...

// &begin[Permission_Definition] @RequestScoped public class PermissionsService {

// &begin[User_Login] @Singleton public class LoginService {

@Path("attributes/computed") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public class AttributeResource extends ExtendedObjectResource<Attribute> {

public class DevicesReportProvider {

private final Storage storage;

private final Config config; private final Storage storage; private final TokenManager tokenManager; private final LdapProvider ldapProvider; // &line[Ldap_Authentication]

private Server server; private User user; @Inject public PermissionsService(Storage storage) { this.storage = storage; }

public User getUser(long userId) throws StorageException { if (user == null && userId > 0) { if (userId == ServiceAccountUser.ID) { user = new ServiceAccountUser(); } else { user = storage.getObject( User.class, new Request(new Columns.All(), new Condition.Equals("id", userId))); } } return user; } // &begin[Role_Check] public boolean notAdmin(long userId) throws StorageException { return !getUser(userId).getAdministrator(); } public void checkAdmin(long userId) throws StorageException, SecurityException { if (!getUser(userId).getAdministrator()) { throw new SecurityException("Administrator access required"); } }

public interface CheckRestrictionCallback { boolean denied(UserRestrictions userRestrictions); } // &begin[Permission_Check] public void checkRestriction( long userId, CheckRestrictionCallback callback) throws StorageException, SecurityException { if (!getUser(userId).getAdministrator() // &line[Role_Check] && (callback.denied(getServer()) || callback.denied(getUser(userId)))) { throw new SecurityException("Operation restricted"); } }

public LoginResult login(String email, String password, Integer code) throws StorageException { // &line[Password] // &begin[OpenID_Authentication] if (forceOpenId) { return null; } // &end[OpenID_Authentication]

public void checkEdit( long userId, Class<?> clazz, boolean addition, boolean skipReadonly) throws StorageException, SecurityException { if (!getUser(userId).getAdministrator()) { // &line[Role_Check] boolean denied = false; if (!skipReadonly && (getServer().getReadonly() || getUser(userId).getReadonly())) { denied = true; } else if (clazz.equals(Device.class)) { denied = getServer().getDeviceReadonly() || getUser(userId).getDeviceReadonly() || addition && getUser(userId).getDeviceLimit() == 0; if (!denied && addition && getUser(userId).getDeviceLimit() > 0) { int deviceCount = storage.getObjects(Device.class, new Request( new Columns.Include("id"), new Condition.Permission(User.class, userId, Device.class))).size(); denied = deviceCount >= getUser(userId).getDeviceLimit(); } } else if (clazz.equals(Command.class)) { denied = getServer().getLimitCommands() || getUser(userId).getLimitCommands(); } if (denied) { throw new SecurityException("Write access denied"); } } }

public void checkUser(long userId, long managedUserId) throws StorageException, SecurityException { if (userId != managedUserId && !getUser(userId).getAdministrator()) { // &line[Role_Check] if (!getUser(userId).getManager() || storage.getPermissions(User.class, userId, ManagedUser.class, managedUserId).isEmpty()) { throw new SecurityException("User access denied"); } } } public void checkUserUpdate(long userId, User before, User after) throws StorageException, SecurityException { if (before.getAdministrator() != after.getAdministrator() // &line[Role_Check] || before.getDeviceLimit() != after.getDeviceLimit() || before.getUserLimit() != after.getUserLimit()) { checkAdmin(userId); // &line[Role_Check] } User user = userId > 0 ? getUser(userId) : null; if (user != null && user.getExpirationTime() != null && !Objects.equals(before.getExpirationTime(), after.getExpirationTime()) && (after.getExpirationTime() == null || user.getExpirationTime().compareTo(after.getExpirationTime()) < 0)) { checkAdmin(userId); // &line[Role_Check] } if (before.getReadonly() != after.getReadonly() || before.getDeviceReadonly() != after.getDeviceReadonly() || before.getDisabled() != after.getDisabled() || before.getLimitCommands() != after.getLimitCommands() || before.getDisableReports() != after.getDisableReports() || before.getFixedEmail() != after.getFixedEmail()) { if (userId == after.getId()) { checkAdmin(userId); // &line[Role_Check] } else if (after.getId() > 0) { checkUser(userId, after.getId()); } else { checkManager(userId); } } if (before.getFixedEmail() && !before.getEmail().equals(after.getEmail())) { checkAdmin(userId); // &line[Role_Check] } } public <T extends BaseModel> void checkPermission( Class<T> clazz, long userId, long objectId) throws StorageException, SecurityException { if (!getUser(userId).getAdministrator() && !(clazz.equals(User.class) && userId == objectId)) { // &line[Role_Check] var object = storage.getObject(clazz, new Request( new Columns.Include("id"), new Condition.And( new Condition.Equals("id", objectId), new Condition.Permission( User.class, userId, clazz.equals(User.class) ? ManagedUser.class : clazz)))); if (object == null) { throw new SecurityException(clazz.getSimpleName() + " access denied"); } } } // &end[Permission_Check]

}

email = email.trim(); User user = storage.getObject(User.class, new Request( new Columns.All(), new Condition.Or( new Condition.Equals("email", email), new Condition.Equals("login", email)))); // &begin[Ldap_Authentication] if (user != null) { if (ldapProvider != null && user.getLogin() != null && ldapProvider.login(user.getLogin(), password) || !forceLdap && user.isPasswordValid(password)) { // &line[Password_Validation] checkUserCode(user, code); checkUserEnabled(user); return new LoginResult(user); } } else { if (ldapProvider != null && ldapProvider.login(email, password)) { // &line[Password] user = ldapProvider.getUser(email); user.setId(storage.addObject(user, new Request(new Columns.Exclude("id")))); checkUserEnabled(user); return new LoginResult(user); } } // &end[Ldap_Authentication] return null;

var key = new Object(); try { cacheManager.addDevice(position.getDeviceId(), key); Object result = computedAttributesHandler.computeAttribute(entity, position); if (result != null) { return switch (entity.getType()) { case "number", "boolean" -> Response.ok(result).build(); default -> Response.ok(result.toString()).build(); }; } else { return Response.noContent().build(); } } finally { cacheManager.removeDevice(position.getDeviceId(), key); }

@POST public Response add(Attribute entity) throws Exception { permissionsService.checkAdmin(getUserId()); // &line[Role_Check] return super.add(entity); } @Path("{id}") @PUT public Response update(Attribute entity) throws Exception { permissionsService.checkAdmin(getUserId()); // &line[Role_Check] return super.update(entity); }

}

@Path("{id}") @DELETE public Response remove(@PathParam("id") long id) throws Exception { permissionsService.checkAdmin(getUserId()); // &line[Role_Check] return super.remove(id); }

}

}

@Inject private DevicesReportProvider devicesReportProvider; @Inject private ReportMailer reportMailer; public ReportResource() { super(Report.class, "description"); }

private Response executeReport(long userId, boolean mail, ReportExecutor executor) { if (mail) { reportMailer.sendAsync(userId, executor); return Response.noContent().build(); } else { StreamingOutput stream = output -> { try { executor.execute(output); } catch (StorageException e) { throw new WebApplicationException(e); } }; return Response.ok(stream) .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=report.xlsx").build(); } } @Path("combined") @GET public Collection<CombinedReportItem> getCombined( @QueryParam("deviceId") List<Long> deviceIds, @QueryParam("groupId") List<Long> groupIds, @QueryParam("from") Date from, @QueryParam("to") Date to) throws StorageException { permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports); // &line[Permission_Check] LogAction.report(getUserId(), false, "combined", from, to, deviceIds, groupIds); return combinedReportProvider.getObjects(getUserId(), deviceIds, groupIds, from, to); }

File file = Paths.get(config.getString(Keys.TEMPLATES_ROOT), "export", "devices.xlsx").toFile(); try (InputStream inputStream = new FileInputStream(file)) { var context = reportUtils.initializeContext(userId); context.putVar("items", getObjects(userId)); JxlsHelper.getInstance().setUseFastFormulaProcessor(false) .processTemplate(inputStream, outputStream, context); }

@Path("route") @GET public Collection<Position> getRoute( @QueryParam("deviceId") List<Long> deviceIds, @QueryParam("groupId") List<Long> groupIds, @QueryParam("from") Date from, @QueryParam("to") Date to) throws StorageException { permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports); // &line[Permission_Check] LogAction.report(getUserId(), false, "route", from, to, deviceIds, groupIds); return routeReportProvider.getObjects(getUserId(), deviceIds, groupIds, from, to); }

TaskReports.java /* * Copyright 2023 - 2024 Anton Tananaev ([email protected]) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.traccar.schedule;

@Path("route") @GET @Produces(EXCEL) public Response getRouteExcel( @QueryParam("deviceId") List<Long> deviceIds, @QueryParam("groupId") List<Long> groupIds, @QueryParam("from") Date from, @QueryParam("to") Date to, @QueryParam("mail") boolean mail) throws StorageException { permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports); // &line[Permission_Check] return executeReport(getUserId(), mail, stream -> { LogAction.report(getUserId(), false, "route", from, to, deviceIds, groupIds); routeReportProvider.getExcel(stream, getUserId(), deviceIds, groupIds, from, to); }); } @Path("route/{type:xlsx|mail}") @GET @Produces(EXCEL) public Response getRouteExcel( @QueryParam("deviceId") List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds, @QueryParam("from") Date from, @QueryParam("to") Date to, @PathParam("type") String type) throws StorageException { return getRouteExcel(deviceIds, groupIds, from, to, type.equals("mail")); }

import ...

private static final Logger LOGGER = LoggerFactory.getLogger(TaskReports.class);

private

private final Storage storage; private final Injector injector; @Inject public TaskReports(Storage storage, Injector injector) { this.storage = storage; this.injector = injector; }

@Override public void run() { Date currentCheck = new Date(); Date lastCheck = new Date(System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(CHECK_PERIOD_MINUTES)); try { for (Report report : storage.getObjects(Report.class, new Request(new Columns.All()))) { Calendar calendar = storage.getObject(Calendar.class, new Request( new Columns.All(), new Condition.Equals("id", report.getCalendarId())));

final String sortField;

Set<Period<Instant>> finishedEvents = new HashSet<>(lastEvents); finishedEvents.removeAll(currentEvents); for (Period<Instant> period : finishedEvents) { RequestScoper scope = ServletScopes.scopeRequest(Collections.emptyMap()); try (RequestScoper.CloseableScope ignored = scope.open()) { executeReport(report, Date.from(period.getStart()), Date.from(period.getEnd())); } }

var conditions = new LinkedList<Condition>(); if (all) { if (permissionsService.notAdmin(getUserId())) { // &line[Role_Check] conditions.add(new Condition.Permission(User.class, getUserId(), baseClass)); } } else { if (userId == 0) { conditions.add(new Condition.Permission(User.class, getUserId(), baseClass)); } else { permissionsService.checkUser(getUserId(), userId); // &line[Permission_Check] conditions.add(new Condition.Permission(User.class, userId, baseClass).excludeGroups()); } }

}

@Path("summary") @GET public Collection<SummaryReportItem> getSummary( @QueryParam("deviceId") List<Long> deviceIds, @QueryParam("groupId") List<Long> groupIds, @QueryParam("from") Date from, @QueryParam("to") Date to, @QueryParam("daily") boolean daily) throws StorageException { permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports); // &line[Permission_Check] LogAction.report(getUserId(), false, "summary", from, to, deviceIds, groupIds); return summaryReportProvider.getObjects(getUserId(), deviceIds, groupIds, from, to, daily); }

} } catch (Exception e) { LOGGER.warn("Scheduled reports error", e); }

private void executeReport(Report report, Date from, Date to) throws StorageException { var deviceIds = storage.getObjects(Device.class, new Request( new Columns.Include("id"), new Condition.Permission(Device.class, Report.class, report.getId()))) // &line[Permission_Check] .stream().map(BaseModel::getId).collect(Collectors.toList()); var groupIds = storage.getObjects(Group.class, new Request( new Columns.Include("id"), new Condition.Permission(Group.class, Report.class, report.getId()))) // &line[Permission_Check] .stream().map(BaseModel::getId).collect(Collectors.toList()); var users = storage.getObjects(User.class, new Request( new Columns.Include("id"), new Condition.Permission(User.class, Report.class, report.getId()))); // &line[Permission_Check]

if (groupId > 0) { permissionsService.checkPermission(Group.class, getUserId(), groupId); // &line[Permission_Check] conditions.add(new Condition.Permission(Group.class, groupId, baseClass).excludeGroups()); } if (deviceId > 0) { permissionsService.checkPermission(Device.class, getUserId(), deviceId); // &line[Permission_Check] conditions.add(new Condition.Permission(Device.class, deviceId, baseClass).excludeGroups()); }

@Path("summary") @GET @Produces(EXCEL) public Response getSummaryExcel( @QueryParam("deviceId") List<Long> deviceIds, @QueryParam("groupId") List<Long> groupIds, @QueryParam("from") Date from, @QueryParam("to") Date to, @QueryParam("daily") boolean daily, @QueryParam("mail") boolean mail) throws StorageException { permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports); // &line[Permission_Check] return executeReport(getUserId(), mail, stream -> { LogAction.report(getUserId(), false, "summary", from, to, deviceIds, groupIds); summaryReportProvider.getExcel(stream, getUserId(), deviceIds, groupIds, from, to, daily); }); }

ReportMailer reportMailer = injector.getInstance(ReportMailer.class);

return storage.getObjects(baseClass, new Request( new Columns.All(), Condition.merge(conditions), sortField != null ? new Order(sortField) : null));

}

/* * Copyright 2017 - 2024 Anton Tananaev ([email protected]) * Copyright 2017 Andrey Kunitsyn ([email protected]) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.traccar.api;

@Path("events/{type:xlsx|mail}") @GET @Produces(EXCEL) public Response getEventsExcel( @QueryParam("deviceId") List<Long> deviceIds, @QueryParam("groupId") List<Long> groupIds, @QueryParam("type") List<String> types, @QueryParam("alarm") List<String> alarms, @QueryParam("from") Date from, @QueryParam("to") Date to, @PathParam("type") String type) throws StorageException { return getEventsExcel(deviceIds, groupIds, types, alarms, from, to, type.equals("mail")); }

var lastEvents = calendar.findPeriods(lastCheck); var currentEvents = calendar.findPeriods(currentCheck);

@GET public Collection<T> get( @QueryParam("all") boolean all, @QueryParam("userId") long userId, @QueryParam("groupId") long groupId, @QueryParam("deviceId") long deviceId) throws StorageException {

SimpleObjectResource.java

@Path("events") @GET @Produces(EXCEL) public Response getEventsExcel( @QueryParam("deviceId") List<Long> deviceIds, @QueryParam("groupId") List<Long> groupIds, @QueryParam("type") List<String> types, @QueryParam("alarm") List<String> alarms, @QueryParam("from") Date from, @QueryParam("to") Date to, @QueryParam("mail") boolean mail) throws StorageException { permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports); // &line[Permission_Check] return executeReport(getUserId(), mail, stream -> { LogAction.report(getUserId(), false, "events", from, to, deviceIds, groupIds); eventsReportProvider.getExcel(stream, getUserId(), deviceIds, groupIds, types, alarms, from, to); }); }

@Override public void schedule(ScheduledExecutorService executor) { executor.scheduleAtFixedRate(this, CHECK_PERIOD_MINUTES, CHECK_PERIOD_MINUTES, TimeUnit.MINUTES); }

public ExtendedObjectResource(Class<T> baseClass, String sortField) { super(baseClass); this.sortField = sortField; }

}

@Path("events") @GET public Collection<Event> getEvents( @QueryParam("deviceId") List<Long> deviceIds, @QueryParam("groupId") List<Long> groupIds, @QueryParam("type") List<String> types, @QueryParam("alarm") List<String> alarms, @QueryParam("from") Date from, @QueryParam("to") Date to) throws StorageException { permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports); // &line[Permission_Check] LogAction.report(getUserId(), false, "events", from, to, deviceIds, groupIds); return eventsReportProvider.getObjects(getUserId(), deviceIds, groupIds, types, alarms, from, to); }

private static final long CHECK_PERIOD_MINUTES = 15;

import ...

/* * Copyright 2016 - 2021 Anton Tananaev ([email protected]) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.traccar.api.resource;

}

@Inject private TripsReportProvider tripsReportProvider;

public void getExcel(OutputStream outputStream, long userId) throws StorageException, IOException {

/* * Copyright 2017 - 2024 Anton Tananaev ([email protected]) * Copyright 2017 Andrey Kunitsyn ([email protected]) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.traccar.api;

EventResource.java

@Path("events") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public class EventResource extends BaseResource {

@Inject private SummaryReportProvider summaryReportProvider;

public class TaskReports extends SingleScheduleTask {

if (user == null) { user = new User(); UserUtil.setUserDefaults(user, config); user.setName(name); user.setEmail(email); user.setFixedEmail(true); user.setAdministrator(administrator); user.setId(storage.addObject(user, new Request(new Columns.Exclude("id")))); } checkUserEnabled(user); return new LoginResult(user);

import ...

@Inject private RouteReportProvider routeReportProvider; @Inject private StopsReportProvider stopsReportProvider;

}

public class ExtendedObjectResource<T extends BaseModel> extends BaseObjectResource<T> {

} // &end[User_Login]

@Inject private EventsReportProvider eventsReportProvider;

return storage.getObjects(Device.class, new Request( new Columns.All(), new Condition.Permission(User.class, userId, Device.class))).stream() // &line[Permission_Check] .map(device -> new DeviceReportItem(device, positions.get(device.getId()))) .toList();

ExtendedObjectResource.java

// &begin[Admin_Login] public LoginResult login(String email, String name, boolean administrator) throws StorageException { User user = storage.getObject(User.class, new Request( new Columns.All(), new Condition.Equals("email", email)));

} // &end[Admin_Login] // &begin[Permission_Check] private void checkUserEnabled(User user) throws SecurityException { if (user == null) { throw new SecurityException("Unknown account"); } user.checkDisabled(); } // &end[Permission_Check] // &begin[TOTP_Authentication] private void checkUserCode(User user, Integer code) throws SecurityException { String key = user.getTotpKey(); if (key != null && !key.isEmpty()) { if (code == null) { throw new CodeRequiredException(); } GoogleAuthenticator authenticator = new GoogleAuthenticator(); if (!authenticator.authorize(key, code)) { throw new SecurityException("User authorization failed"); } } } // &end[TOTP_Authentication]

@Inject private CombinedReportProvider combinedReportProvider;

var positions = PositionUtil.getLatestPositions(storage, userId).stream() .collect(Collectors.toMap(Message::getDeviceId, p -> p));

Position position = storage.getObject(Position.class, new Request( new Columns.All(), new Condition.LatestPositions(deviceId)));

}

private static final String EXCEL = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";

public Collection<DeviceReportItem> getObjects(long userId) throws StorageException {

@POST @Path("test") public Response test(@QueryParam("deviceId") long deviceId, Attribute entity) throws Exception { permissionsService.checkAdmin(getUserId()); // &line[Role_Check] permissionsService.checkPermission(Device.class, getUserId(), deviceId); // &line[Permission_Check]

// &begin[Token_Authentication] public LoginResult login(String token) throws StorageException, GeneralSecurityException, IOException { if (serviceAccountToken != null && serviceAccountToken.equals(token)) { return new LoginResult(new ServiceAccountUser()); } TokenManager.TokenData tokenData = tokenManager.verifyToken(token); // &line[Token_Validation] User user = storage.getObject(User.class, new Request( new Columns.All(), new Condition.Equals("id", tokenData.getUserId()))); if (user != null) { checkUserEnabled(user); } return new LoginResult(user, tokenData.getExpiration()); } // &end[Token_Authentication]

public void checkManager(long userId) throws StorageException, SecurityException { if (!getUser(userId).getAdministrator() && getUser(userId).getUserLimit() == 0) { throw new SecurityException("Manager access required"); } } // &end[Role_Check]

public void checkEdit( long userId, BaseModel object, boolean addition, boolean skipReadonly) throws StorageException, SecurityException { if (!getUser(userId).getAdministrator()) { // &line[Role_Check] checkEdit(userId, object.getClass(), addition, skipReadonly); if (object instanceof GroupedModel after) { if (after.getGroupId() > 0) { GroupedModel before = null; if (!addition) { before = storage.getObject(after.getClass(), new Request( new Columns.Include("groupId"), new Condition.Equals("id", after.getId()))); } if (before == null || before.getGroupId() != after.getGroupId()) { checkPermission(Group.class, userId, after.getGroupId()); } } } if (object instanceof Schedulable after) { if (after.getCalendarId() > 0) { Schedulable before = null; if (!addition) { before = storage.getObject(after.getClass(), new Request( new Columns.Include("calendarId"), new Condition.Equals("id", object.getId()))); } if (before == null || before.getCalendarId() != after.getCalendarId()) { checkPermission(Calendar.class, userId, after.getCalendarId()); } } } if (object instanceof Notification after) { if (after.getCommandId() > 0) { Notification before = null; if (!addition) { before = storage.getObject(after.getClass(), new Request( new Columns.Include("commandId"), new Condition.Equals("id", object.getId()))); } if (before == null || before.getCommandId() != after.getCommandId()) { checkPermission(Command.class, userId, after.getCommandId()); } } } } }

public AttributeResource() { super(Attribute.class, "description"); }

public LoginResult login( String scheme, String credentials) throws StorageException, GeneralSecurityException, IOException { switch (scheme.toLowerCase()) { // &begin[Bearer_Authentication] case "bearer": return login(credentials); // &end[Bearer_Authentication] // &begin[Basic_Authentication] case "basic": byte[] decodedBytes = DataConverter.parseBase64(credentials); String[] auth = new String(decodedBytes, StandardCharsets.US_ASCII).split(":", 2); return login(auth[0], auth[1], null); // &end[Basic_Authentication] default: throw new SecurityException("Unsupported authorization scheme"); } }

@Path("reports") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public class ReportResource extends SimpleObjectResource<Report> {

@Inject public DevicesReportProvider(Config config, ReportUtils reportUtils, Storage storage) { this.config = config; this.reportUtils = reportUtils; this.storage = storage; }

@Inject private ComputedAttributesHandler.Late computedAttributesHandler;

@Inject public LoginService( Config config, Storage storage, TokenManager tokenManager, @Nullable LdapProvider ldapProvider) { this.storage = storage; this.config = config; this.tokenManager = tokenManager; this.ldapProvider = ldapProvider; // &line[Ldap_Authentication] serviceAccountToken = config.getString(Keys.WEB_SERVICE_ACCOUNT_TOKEN); forceLdap = config.getBoolean(Keys.LDAP_FORCE); // &line[Ldap_Authentication] forceOpenId = config.getBoolean(Keys.OPENID_FORCE); // &line[OpenID_Authentication] }

public Server getServer() throws StorageException { if (server == null) { server = storage.getObject( Server.class, new Request(new Columns.All())); } return server; }

package org.traccar.api.resource; import ...

private final Config config; private final ReportUtils reportUtils; private final Storage storage;

@Inject private CacheManager cacheManager;

private final String serviceAccountToken; private final boolean forceLdap; // &line[Ldap_Authentication] private final boolean forceOpenId; // &line[OpenID_Authentication]

ReportResource.java

}

@Path("summary/{type:xlsx|mail}") @GET @Produces(EXCEL) public Response getSummaryExcel( @QueryParam("deviceId") List<Long> deviceIds, @QueryParam("groupId") List<Long> groupIds, @QueryParam("from") Date from, @QueryParam("to") Date to, @QueryParam("daily") boolean daily, @PathParam("type") String type) throws StorageException { return getSummaryExcel(deviceIds, groupIds, from, to, daily, type.equals("mail")); }

for (User user : users) { LogAction.report(user.getId(), true, report.getType(), from, to, deviceIds, groupIds); switch (report.getType()) { case "events" -> { var eventsReportProvider = injector.getInstance(EventsReportProvider.class); reportMailer.sendAsync(user.getId(), stream -> eventsReportProvider.getExcel( stream, user.getId(), deviceIds, groupIds, List.of(), List.of(), from, to)); } case "route" -> { var routeReportProvider = injector.getInstance(RouteReportProvider.class); reportMailer.sendAsync(user.getId(), stream -> routeReportProvider.getExcel( stream, user.getId(), deviceIds, groupIds, from, to)); } case "summary" -> { var summaryReportProvider = injector.getInstance(SummaryReportProvider.class); reportMailer.sendAsync(user.getId(), stream -> summaryReportProvider.getExcel( stream, user.getId(), deviceIds, groupIds, from, to, false)); } case "trips" -> { var tripsReportProvider = injector.getInstance(TripsReportProvider.class); reportMailer.sendAsync(user.getId(), stream -> tripsReportProvider.getExcel( stream, user.getId(), deviceIds, groupIds, from, to)); } case "stops" -> { var stopsReportProvider = injector.getInstance(StopsReportProvider.class); reportMailer.sendAsync(user.getId(), stream -> stopsReportProvider.getExcel( stream, user.getId(), deviceIds, groupIds, from, to)); } default -> LOGGER.warn("Unsupported report type {}", report.getType()); } }

@Path("trips") @GET public Collection<TripReportItem> getTrips( @QueryParam("deviceId") List<Long> deviceIds, @QueryParam("groupId") List<Long> groupIds, @QueryParam("from") Date from, @QueryParam("to") Date to) throws StorageException { permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports); // &line[Permission_Check] LogAction.report(getUserId(), false, "trips", from, to, deviceIds, groupIds); return tripsReportProvider.getObjects(getUserId(), deviceIds, groupIds, from, to); } @Path("trips") @GET @Produces(EXCEL) public Response getTripsExcel( @QueryParam("deviceId") List<Long> deviceIds, @QueryParam("groupId") List<Long> groupIds, @QueryParam("from") Date from, @QueryParam("to") Date to, @QueryParam("mail") boolean mail) throws StorageException { permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports); // &line[Permission_Check] return executeReport(getUserId(), mail, stream -> { LogAction.report(getUserId(), false, "trips", from, to, deviceIds, groupIds); tripsReportProvider.getExcel(stream, getUserId(), deviceIds, groupIds, from, to); }); } @Path("trips/{type:xlsx|mail}") @GET @Produces(EXCEL) public Response getTripsExcel( @QueryParam("deviceId") List<Long> deviceIds, @QueryParam("groupId") List<Long> groupIds, @QueryParam("from") Date from, @QueryParam("to") Date to, @PathParam("type") String type) throws StorageException { return getTripsExcel(deviceIds, groupIds, from, to, type.equals("mail")); }

}

import ... public class SimpleObjectResource<T extends BaseModel> extends BaseObjectResource<T> { private final String sortField;

@Path("{id}") @GET public Event get(@PathParam("id") long id) throws StorageException { Event event = storage.getObject(Event.class, new Request( new Columns.All(), new Condition.Equals("id", id))); if (event == null) { throw new WebApplicationException(Response.status(Response.Status.NOT_FOUND).build()); } permissionsService.checkPermission(Device.class, getUserId(), event.getDeviceId()); // &line[Permission_Check] return event; }

public SimpleObjectResource(Class<T> baseClass, String sortField) { super(baseClass); this.sortField = sortField; }

@Path("stops") @GET public Collection<StopReportItem> getStops( @QueryParam("deviceId") List<Long> deviceIds, @QueryParam("groupId") List<Long> groupIds, @QueryParam("from") Date from, @QueryParam("to") Date to) throws StorageException { permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports); // &line[Permission_Check] LogAction.report(getUserId(), false, "stops", from, to, deviceIds, groupIds); return stopsReportProvider.getObjects(getUserId(), deviceIds, groupIds, from, to); }

// &begin[Permission_Check] @GET public Collection<T> get( @QueryParam("all") boolean all, @QueryParam("userId") long userId) throws StorageException { var conditions = new LinkedList<Condition>();

@Path("stops") @GET @Produces(EXCEL) public Response getStopsExcel( @QueryParam("deviceId") List<Long> deviceIds, @QueryParam("groupId") List<Long> groupIds, @QueryParam("from") Date from, @QueryParam("to") Date to, @QueryParam("mail") boolean mail) throws StorageException { permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports); // &line[Permission_Check] return executeReport(getUserId(), mail, stream -> { LogAction.report(getUserId(), false, "stops", from, to, deviceIds, groupIds); stopsReportProvider.getExcel(stream, getUserId(), deviceIds, groupIds, from, to); }); }

if (all) { if (permissionsService.notAdmin(getUserId())) { // &line[Role_Check] conditions.add(new Condition.Permission(User.class, getUserId(), baseClass)); } } else { if (userId == 0) { userId = getUserId(); } else { permissionsService.checkUser(getUserId(), userId); } conditions.add(new Condition.Permission(User.class, userId, baseClass)); }

@Path("stops/{type:xlsx|mail}") @GET @Produces(EXCEL) public Response getStopsExcel( @QueryParam("deviceId") List<Long> deviceIds, @QueryParam("groupId") List<Long> groupIds, @QueryParam("from") Date from, @QueryParam("to") Date to, @PathParam("type") String type) throws StorageException { return getStopsExcel(deviceIds, groupIds, from, to, type.equals("mail")); }

return storage.getObjects(baseClass, new Request( new Columns.All(), Condition.merge(conditions), sortField != null ? new Order(sortField) : null)); } // &end[Permission_Check]

} // &end[Permission_Definition] }

@Path("devices/{type:xlsx|mail}") @GET @Produces(EXCEL) public Response getDevicesExcel( @PathParam("type") String type) throws StorageException { permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports); // &line[Permission_Check] return executeReport(getUserId(), type.equals("mail"), stream -> { devicesReportProvider.getExcel(stream, getUserId()); }); } }

Figure 1: Code of the Traccar GPS tracking software; code responsible for permission checks is highlighted In the age of AI, this challenge is amplified, since software is increasingly written by AI coding agents. Agents produce and change security-relevant code faster than it can be reviewed, and agents can easily remove parts or whole security features. The less code developers write, the more they depend on locating security features to assess their correctness and completeness. Yet, LLMs have a considerable potential to address this challenge. Agents could record security features as they are written. LLMs could also recognize security-relevant logic that no API signature reveals. Explicitly considering and representing security features is an open challenge for researchers and tool builders, especially while agentic practices are still forming and unstructured. In the remainder, we introduce and discuss the security feature location problem. We describe the notion of security feature, drawing on work in software engineering [4], feature-oriented development [3], and secure software engineering [6, 8]. We then define the security feature location problem, illustrate its relevance with examples, and outline research challenges towards novel methods and tools to address this problem.

Software, Features, and Security Software is commonly described in terms of the features it provides. When discussing software security, however, systems are often characterized by the properties they must enforce, such as confidentiality, integrity, and availability. These abstract properties must be implemented in code through concrete implementation mechanisms called security features.

Software Features A feature is a label that abstractly represents code [3] and that can be seen as a unit of end-to-end functionality or behavior. Software development processes, such as SCRUM or other agile methods, typically use the notion of feature to plan and manage software systems. Companies strive towards feature teams as opposed to component teams to shorten lead time and increase release frequency to become more agile. As such, features connect requirements, implementation, and maintenance. Developers reason about systems in terms of features when functionality is added, changed, removed, or assessed [3, 7]. To evolve and maintain features, they must understand and locate the implementation that realizes them, known as the feature location [11] or concept assignment problem [4].

The Security Feature Location Problem

Software Security Security engineering typically comprises defining the desired security properties of a system, such as confidentiality, integrity, or availability. Such properties need to be refined into project-specific expectations about system design and behavior [8], often in the form of security requirements. Methods such as secure design and threat modeling express such expectations in terms of components, trust boundaries, attacker assumptions, and permitted operations [1, 13]. Security principles further guide their realization in system design and code. For example, the principle complete mediation calls for permission checks on every relevant access path, whereas the principle least privilege limits the permissions granted to principals, and defense in depth calls for complementary layers of protection [12]. Ultimately, such security expectations are realized through concrete security features in code [2, 5, 6, 8, 14], whose placement, configuration, and interaction across a system affect their intended security effect.

Security Features Building on the security engineering literature and the general notion of feature in software systems [3], security features can be characterized as follows. Definition. A security feature is a label that represents a concrete security implementation technique in the codebase, spanning artifacts such as source code, configuration files, or deployment descriptors. A security feature has a name and is characterized by (i) an intent, such as “only authenticated users may access resource X,” (ii) implementation artifacts, such as code fragments, configuration entries, or infrastructure scripts, and (iii) assumptions about the environment and other features on which it relies. Examples include access control mechanisms, cryptographic data protection, and key management [2, 14]. Security features may also be composed of sub-features [6]: for example, access control may combine passwordbased login and session management. A security feature represents functionality whose absence would prevent, weaken, or undermine the enforcement of a security property [14] by introducing a vulnerability or weakness. It contributes to enforcing one or more high-level security properties [6]. For example, a property such as “only authorized users can update location data” may depend on authentication, authorization, identity propagation, and logging. Such properties emerge from the interaction of multiple features [9]. For example, authentication establishes an identity, identity propagation carries it through the system, authorization evaluates it against a policy, and logging records security-relevant actions. Assessing a security property therefore requires locating the relevant security features, their implementation, and their interactions. A library, algorithm, or platform mechanism is not, by itself, a security feature. It becomes part of one through its specific embedding in code, its configuration, invocation, surrounding logic, dependencies, and assumptions about other mechanisms and the environment [2, 5]. As such, security features provide an abstraction necessary to assess the correct implementation of security

in software systems. Security features shift attention from verifying individual components, such as a cryptographic library or access-control routine, in isolation to understanding how security mechanisms are embedded, configured, and combined across a system. Their locations and interactions provide the context and evidence needed to assess whether security properties are enforced.

What is Security Feature Location? “What security features are present in my software system? Where are they implemented in code, configuration, and deployment artifacts? How do they interact with other features? Do they collectively enforce the intended security properties?” These questions illustrate the need to know what security features exist and where they are implemented in a software system, as a prerequisite for assessing, maintaining, or improving its security. Security feature location is the task of identifying these features in code, specifically: Definition. Security feature location relates a security feature to the set of locations that implement it. A security feature may either be known in advance and need only be located, or first need to be identified within the system’s implementation. Security feature location inherits challenges from classic feature location [4, 11], but security features also have distinctive properties. Like classic features, they may be scattered and tangled with each other as well as with functional code. Unlike classic features, however, they are often tied to security-specific concepts such as permissions, identities, secrets, trust boundaries, and attacker assumptions. These concepts are realized through recognizable mechanisms, including security APIs, configuration options, and framework extension points [5, 6]. This combination makes the problem challenging, because the security role of a location is not always obvious from code alone, but it also suggests that security-specific cues can support automation more directly than in classic feature location. Security feature locations may include files, classes, functions, statements, configuration blocks, deployment descriptors, and other implementation artifacts. Even libraries might contain securityrelevant aspects that could be described in terms of security features. Security feature location may also describe whether a location is exclusive to one security feature or shared by several security features, the role it plays in enforcing or supporting a security property, the confidence with which it was identified, and its relationships to other feature locations. Different security tasks may require the locations of a security feature at different levels of granularity. For example, a developer conducting a security review might need to identify all lines of code where permission checks are implemented. However, during an incident response, they might only need to identify the files or functions involved in a security breach. Granularity also applies to the security features themselves. A broad assessment may only require locating a coarse-grained feature, such as access control, whereas a focused analysis may require distinguishing sub-features such as authentication and authorization, or even more specific mechanisms such as password-based or multifactor authentication. These different views of security features allow developers to reason about security in a modular way, focusing on the relevant security features and their locations for their tasks rather than being overwhelmed by the entire codebase.

Kevin Hermann, Sven Peldszus, Thorsten Berger, and Adam Shostack TaskReports.java

Security Features

Authentication

Password Validation

System State Validation

Keys.java

DevicesReportProvider.java

Secure Data Handling

UserResource.java

User.java

Disableable.java

DeviceResource.java

Imei Validation

Secure Storage

Memory Storage

NotificationResource.java

Device.java

Permission Check DatabaseStorage.java

User Logout

User Check

Password Reset

BaseObjectResource.java

Access Logging

Key Storage

Trusted Sources

Log Limit

SecurityRequestFilter.java

Logging Key Management

Action Logging

Sha1 Hashing

Secure Communication Session Timeout

Cookie Same Site

Password Password Definition

Permission Definition

Session Management

Signature

Salting

System State Protection

User Session

Credentials User Login User

Cryptographic Hashing

Invalidate Object

OpenID Authentication

Permission Invalidation

Single Sign On

Ldap Authentication

Permission Management

Permission Assignment

Asymmetric Key Cryptography

TOTP Authentication

Token Generation

Authenticate with Device

Token Expiration

AES Decryption

Checksum

ReportUtils.java

Database Storage

Password Update

Throttling Filter

Role Check

Token Authentication

Permission

Permission Check

Encryption

Authentication

Role Definition

Authorization Header

Security Request Filter

Authorization Role based Access Control

Security Monitoring

Cryptography

Access Control

CommandResource.java

SimpleObjectResource.java

Figure 2: Relative size of security features in the Traccar GPS system. AttributeResource.java

EventResource.java

PermissionsService.java

LoginService.java

ExtendedObjectResource.java

Security feature location can be performed using two strategies: proactive and retroactive location. Proactive location documents security features and their locations as the code is created. For example, teams may mark the jwt.verify() wrappers and permission checks that belong to a security feature using code annotations, feature flags, or a feature database. Such recorded locations can be precise when available, but they are often incomplete or absent in practice, decay as the system evolves, and require ongoing maintenance. Retroactive location recovers security features and their locations from the implementation itself, either manually or with tool support, such as information retrieval and static or dynamic analysis. Unlike proactive location, this strategy does not require previously recorded location information, but it can be laborious and may produce imprecise or incomplete results.

What Can Security Feature Location Enable? Security feature location helps to relate design-level concepts, such as security properties, threat assumptions, and security principles, to concrete code-level artifacts, such as API calls, configuration entries, and policy rules. This allows developers to reason about security in a modularized way and obtain code-level evidence for security claims, focusing on relevant features and their locations without being overwhelmed by the entire codebase. As such, security feature location is a prerequisite for many security-relevant tasks, including security reviews, compliance assessment, incident response, and secure software evolution. Beyond these tasks, security feature locations can also provide security-specific context to improve automated security analyses. Security Reviews and Compliance Assessment. Security reviews require confidence that all relevant security mechanisms have been identified and examined. Developers may know that a security property should hold, but lack the code-level evidence needed to assess whether it is actually enforced. Security feature location provides them with this code-level evidence to perform security reviews and assess compliance with security standards. In particular, it helps developers determine whether authorization is consistently

ReportResource.java PermissionsResource.java

PositionResource.java

MediaFilter.java

Figure 3: Scattering of permission checks in the Traccar GPS system.

enforced, whether sensitive data is protected along relevant execution paths, whether a disclosed vulnerability is reachable, and which locations must be modified when a security mechanism changes. Missing locations of security features, such as permission checks, which are often subtle and scattered, may cause reviewers to overlook critical vulnerabilities. Security feature location provides reviewers with an overview of security-relevant code, helping them focus their effort and increasing the coverage of manual review. Automated support for locating security features, potentially combined with control-flow, policy, or coverage analysis, can further highlight vulnerabilities or inconsistencies in the implementation, such as permission checks that are applied in some but not all code paths leading to a sensitive operation. Incident Response and Analysis. Responding to security incidents and newly discovered threats is often time-critical. Developers must rapidly identify the relevant security features and their implementation locations to assess the situation and determine whether remediation is necessary. When a security-relevant issue is observed, such as unexpected or unauthorized access to a resource, developers may need to trace the relevant permission checks, authentication mechanisms, and access-control data to understand its cause, assess whether it has been exploited, and determine what must be fixed. In other situations, new external security information may trigger the analysis. For example, when a vulnerability in a library, such as the one in jsonwebtoken, is disclosed, developers must first determine whether their system uses the affected security feature in a vulnerable way. If so, they must then identify the code that needs to be changed. Both kinds of analyses require knowledge of the relationships among security features and their

The Security Feature Location Problem Secure Storage

PBKDF2 Key Generation

Password Reset Password Update Sha1 Hashing

Salting

Password Definition

Token Management

Password Validation

Secure Random

Token Validation

ECDSA Signature Verification Password

Admin Login

User Authentication PKCS8 Key Generation

Token Authentication

Key Storage

Sha256 Hashing

Ldap Authentication

Action Logging TOTP Authentication User Session

ECDSA Signature Signing

X509 Key Generation

Token Generation

Asymmetric Key Cryptography

User Login Key Generation

Throttling Filter

Token Expiration

Authentication Logging Session Timeout

Role Check

User Logout Basic Authentication

Security Request Filter

OpenID Authentication

Database Storage

Permission Check

Bearer Authentication Permission Definition User Based Request Authorization

Permission Assignment Role Definition

Access Logging

Permission Invalidation

Permission Management Memory Storage

Checksum Imei Validation

Permission Logging

Permission Type Validation Permission Change Logging

Authorization Header

Invalidate Object

Figure 4: Tangling and interaction between security features in the Traccar GPS system. Each circle is a security feature, its area is proportional to the feature’s size (annotated lines of code), and the thickness of a connecting line grows with the number of code lines in which the two features are tangled. Circles with the same color belong to the same security feature category. implementation locations. Making this information explicitly available can accelerate the analysis, help developers understand how a security issue relates to existing security mechanisms, and identify the necessary changes to prevent exploitation or recurrence. Secure Software Evolution. Software systems continuously evolve. Changes to functionality, dependencies, or configuration may invalidate security assumptions or cause noncompliance with security policies or standards. During evolution, a system may remain functionally correct while becoming vulnerable because changes invalidate security assumptions or properties, or introduce new attack vectors [5]. Making security features and their locations explicit helps developers prevent accidental changes to security-critical code when evolving a system. Security feature location also supports impact analysis and regression checks by identifying the code and configuration artifacts relevant to a change. For example, when migrating from an ellipticcurve-based cryptographic mechanism to a post-quantum alternative, developers need to identify all locations in which the affected

mechanism is used. This may require considering parameters and configuration in addition to API calls, since the relevant cryptographic operation or concrete algorithm may not be evident from the call itself. The same Bouncy Castle init() call, for instance, can realize encryption or decryption depending on a mode parameter, while concrete algorithms may be specified separately in configuration files [6]. Similarly, when tightening access-control policies, developers can use the locations of permission checks to identify those that depend on changed roles or attributes. Security feature location further supports migration to new standards or platforms. Porting a system to a new identity provider, encryption standard, or logging infrastructure requires identifying all code and configuration that currently implement the relevant security features. Without support, such migrations are error-prone and may leave vulnerable legacy mechanisms behind.

Kevin Hermann, Sven Peldszus, Thorsten Berger, and Adam Shostack

Enhancing Static Analysis. Beyond directly supporting developers in security tasks, security feature location can improve codelevel security analysis by connecting security expectations to implementation locations. Prior work shows that security analysis can be optimized when information about security features is available, for example, to check whether planned mechanisms are present in code, or whether confidential data is actually encrypted as specified in a threat model [15]. It can also help triage static-analysis findings by identifying which files, methods, or configuration fragments implement security-critical functionality [10]. More broadly, security feature location can provide entry points or scopes for analyses and tests that otherwise struggle with scale and precision, including information-flow analysis, access-control checks, and fuzzing of paths that interact with security features.

Why is Security Feature Location a Problem? We illustrate the security feature location problem using the opensource GPS tracking system Traccar. Figure 2 shows its security features, their relative sizes, and their division into five categories: access control (blue), cryptography (green), secure data handling (purple), system state protection (red), and security monitoring (orange). Overall, Traccar’s codebase comprises 77,051 lines of Java code, of which 4,702 (6.1%) contribute to security features. Recall the feature permission check from Figure 1. The feature is scattered across 66 distinct locations and 24 files, illustrated in Figure 3. For example, when a user attempts to access a resource, such as a GPS device or a location history, the code must check whether the user is authenticated and holds the required permissions. Although the checks themselves are relatively small, they are widely scattered and easy to miss, and being able to locate them is important for developers to know whether access control policies are correctly enforced, or to change them when needed. Recovering these permission checks could be done by developers as follows. First, they need to identify an entry point, such as the usage of a security library or a configuration parameter. Such an entry point may be found through documentation, knowledge of the codebase, or by using information retrieval techniques to search for keywords in code, configuration, and deployment artifacts. From there, they have to trace the control flow to identify surrounding code, functions, classes, or other artifacts that contribute to the security feature. They then need to identify where these artifacts are used in the system and repeat the process until they have recovered the feature’s implementation to the extent the security-related task requires. This process, however, is laborious and error-prone. Documentation of security features is often incomplete or outdated, development teams change often, and information retrieval techniques return too many false positives or miss important locations to be useful in practice. The lack of effective techniques puts developers, who are rarely security experts [5], in a difficult position: they lack a clear understanding of how security features are realized in the system, which makes it hard for them to assess whether security properties are consistently enforced, or where vulnerabilities may arise. Security features are not only scattered across many locations, but they are also tangled with one another, as illustrated in Figure 4.

Although they primarily interact with security features within their own category, such as cryptography, authentication, or authorization, they frequently also interact with security features in other categories. For example, cryptographic operations are used to hash user passwords, permission checks depend on the authentication state, and security logging is used across all features. This forces developers in yet another difficult position: they must understand how security features interact with one another, as making changes to one feature may affect the security properties of another [9]. For example, changing the password hashing algorithm to comply with new security standards may affect the security of authentication in many ways: it requires updates to the login workflow to use the new algorithm, password reset functionality to re-hash existing passwords, and the authentication state to be invalidated for all users until the migration is complete. Scattering and interaction also mean that there is no single view of a security feature that is appropriate for every security task. As discussed above, a security review may require a broad view of permission checks to identify missing or inconsistent checks. This view includes their implementation locations, the operations they protect, and the policies they enforce. By contrast, incident response may require a narrower view, focusing on the permission checks and interacting security features involved in a particular unauthorized access. Supporting such task-specific views is challenging because security feature location must determine not only the relevant locations, but also the appropriate level of granularity for the task, which may only become clear as the analysis proceeds. Security features are often realized at multiple levels of granularity, from low-level permission checks and cryptographic operations to high-level access control policies. Implementations are both scattered (spread across many files and components) and tangled (interleaved with one another). As software evolves over time, development teams change, and knowledge about security features fades, security feature location becomes increasingly difficult. Why Security Feature Location is a Problem

Research Challenges So far, we presented the security feature location problem conceptually and illustrated its relevance and opportunities with concrete examples. But how can we actually solve it? Despite its importance and opportunities for automation, security feature location has received little attention as a problem in its own right. The following research challenges outline what is needed to turn the security feature location problem into practical methods and tools for developers and security engineers and to make systems more secure.

Nature of Security Features Before we can locate security features, we need to improve our empirical understanding of them in real software systems. That is, we need to determine syntactic and semantic characteristics, as well as their relation to other common security abstractions, such as security properties.

The Security Feature Location Problem

C1.1: What constitutes a security feature? Security features can be based on a wide range of security mechanisms, including authentication, authorization, session handling, incident logging, and data protection. However, they are typically used in combination with non-security features. For example, while session timeouts may be used to prevent session hijacking, they also serve to free up resources in a web application. But where does functionality, which provides value to stakeholders in software systems, end and where does security begin? Studies should investigate the nature of security features, how they realize security properties, how security features can be distinguished from non-security functionality, and how their boundaries can be characterized. C1.2: What are the characteristics of security features? Security features may be implemented through code, configuration, or deployment artifacts. They are often cross-cutting, scattered across multiple locations, and may be realized through different implementation techniques using different frameworks or libraries. To understand how security features enforce security properties and how they can be located, we need to characterize how they manifest in real systems. Which implementation characteristics indicate the presence of a security feature, and how do these characteristics relate to the security properties it helps enforce? Studies should identify recurring implementation patterns, such as security APIs, configuration entries, data flows, or deployment descriptors, characterize them across systems, and investigate how they contribute to enforcing security properties. Such knowledge can reveal which cues are useful for locating security features and understanding their role in enforcing security properties. C1.3: How do security features interact? To enforce security properties, security features often interact with each other [9]. For example, a permission check depends on authentication to establish the identity on which an authorization decision is based. Such interactions can determine whether a security property is actually enforced. Incorrect, incomplete, or unexpected interactions between security features may weaken security properties or introduce vulnerabilities even when the individual features are implemented correctly. Security features may also interact with functional features, for example when permission checks guard security-sensitive operations. But how do security features interact, how do these interactions affect security properties, and how can they be captured? Studies should characterize recurring interaction patterns between security features across systems and investigate how they contribute to, weaken, or violate security properties. Understanding these patterns can support locating related security features and identifying interactions that may introduce vulnerabilities.

Methods and Tools With a better empirical understanding of security features, effective methods and tools can be built. C2.1: How can we represent security features? Security features provide an abstraction between security properties, threat models, and security principles on the one hand and concrete code-level implementations on the other. The former are typically expressed at design time, whereas the latter evolve through code, configuration,

and dependencies during implementation and evolution. Security properties rarely map one-to-one to security features, but emerge from the composition of multiple security features. But how do we connect these design-level security properties to the concrete artifacts that implement them? A research challenge is to design a representation of security features as a means to connect security requirements, assumptions, and guarantees to their concrete implementations within a system. Such a representation should also characterize the security role of a location, such as checking permissions, validating input, propagating identity, encrypting data, or logging a security event. C2.2: How can we proactively record and maintain security feature locations? Security-relevant information is often available when a security feature is introduced or modified. During development, engineers may know the security purpose of a change, the security feature it affects, and the implementation artifacts used to realize it. Instead of losing this information and recovering it later, techniques could proactively record and continuously maintain information about security features and their implementation locations. Recorded security feature locations can in turn serve as input to subsequent development activities, informing developers and tools about which artifacts are security-relevant and what features they contribute to. Agentic development may provide an additional opportunity for proactive recording. Security-related prompts already express the intent of a change, while coding agents have access to the artifacts they modify to realize it. This information could be used to record and update security feature locations during development, while existing feature locations could in turn be provided as context for subsequent changes, helping agents account for and preserve existing security mechanisms as the system evolves. A central challenge is deciding at what granularity security feature locations should be recorded. This concerns both the granularity of the security features themselves and the granularity of their implementation locations. Recording only coarse-grained features or file-level locations may be inexpensive but insufficient for later development or security tasks, while continuously recording finegrained sub-features or individual statements, configuration values, and interactions may impose unnecessary overhead and be difficult to maintain. But how can security features be recorded with little additional developer effort, and at what levels of granularity in the security feature hierarchy (e.g., cryptography, encryption, AES) and in the code structure (e.g., statements, lines, functions, classes, namespaces)? Studies should investigate techniques that derive, use, and update security feature locations throughout development and security engineering. This includes determining how information about security features can be captured with little additional effort during conventional development, how coding agents can use existing security feature information as context and update it automatically, how appropriate feature and location granularity can be determined, and how recorded locations can be kept consistent with the evolving implementation. C2.3: How can we retroactively obtain the security feature locations needed for security tasks? Proactively recorded security feature locations may provide a useful baseline, but they may

Kevin Hermann, Sven Peldszus, Thorsten Berger, and Adam Shostack

not contain all information required for a particular security task. Incident response may require tracing the locations and interactions surrounding a particular permission check, while assessing a newly disclosed vulnerability may require identifying uses of specific APIs, parameters, or configuration values associated with a specific security feature or sub-feature. Such information may be too detailed or task-specific to maintain continuously, especially when future security tasks cannot be anticipated. Retroactive recovery is also necessary for existing systems for which no security feature information has been recorded. Security features are often implemented using API calls and configuration parameters of security frameworks and libraries [5, 6]. Such cues can provide entry points from which relevant code locations can be traced through the system. However, security features are rarely confined to recognizable entry points and may be scattered across code and configuration or realized partly or entirely through custom application code. The challenge is to determine which artifacts jointly constitute a security feature, how far its implementation extends beyond identifiable entry points, and at what granularity it needs to be recovered for a particular security task. Moreover, when recovering feature locations from scratch, it may be difficult to determine whether all relevant locations have been found. But how can we determine how far a security feature’s implementation extends, and when all of its locations have been found? Existing feature-location research remains general-purpose and does not address security-specific concerns [4, 11]. Studies should investigate techniques such as static and dynamic analysis, information retrieval, and LLM-based approaches for recovering security feature locations. In particular, studies should investigate to what extent LLMs can identify security-relevant code from its semantics and surrounding context, including custom implementations for which no known security API provides an entry point. LLM-based techniques could also be combined with security-specific cues and program analyses to identify and propagate feature locations. Existing proactively recorded locations can provide additional input, allowing such techniques to refine the existing mapping or recover finer-grained and task-specific information when needed. Studies should investigate how these techniques can be combined and evaluate their precision, recall, scalability, and ability to recover sufficiently complete feature locations for concrete security tasks. C2.4: How can we contextualize security feature locations for security tasks? Identifying security feature locations alone does not reveal how these locations work together or how interactions between security features affect the enforcement of security properties. As illustrated in Figures 2 to 4, existing feature-location and visualization techniques provide a step in this direction by exposing properties such as scattering, tangling, and relationships between feature locations. However, such representations are not necessarily tailored to security engineering. For security tasks, developers may need additional context about the security role of particular locations, the security properties they contribute to, the assumptions they rely on, and the relationships among interacting features. But which aspects of this security-specific context are relevant to a particular task, and how should it be made accessible to developers?

Future work should investigate how representations and visualizations of feature locations can be contextualized for security tasks. This includes determining which information about security properties, assumptions, and interactions should be exposed for a particular task and how developers can navigate between this context and the corresponding implementation locations. Such techniques should enable developers to interpret the security relevance of scattered and tangled implementations without requiring them to manually reconstruct this context.

Overhead and Benefits Once we have developed methods and tools for security feature location, we need to investigate to what extent they increase the security of a system and whether the benefits outweigh the overhead they impose. C3.1: What are the benefits of security feature location? The value of security feature location ultimately depends on whether its results improve concrete security tasks. We need evaluation methods that measure its impact on tasks such as security review, incident response, and compliance assessment. But what are appropriate evaluation methods and metrics for such tasks, and how can we assess whether improvements in task performance translate into better security outcomes? Security feature location research needs to develop task-specific evaluation methods and metrics, as well as ground-truth datasets of security feature locations in real systems. Evaluations should establish whether and under which conditions security feature location leads to better security-relevant decisions and outcomes. C3.2: What is the overhead of locating security features? Although locating security features can help developers understand and maintain a system’s security, it also incurs overhead. Developers may need to learn new tools, adapt their workflows, maintain proactively recorded feature locations, or inspect results produced by retroactive techniques. We need to understand the overhead of security feature location in terms of time, effort, and cognitive load. But how can we measure the overhead of security feature location? Studies should therefore evaluate not only the accuracy and effectiveness of security feature location techniques, but also the costs they impose on developers and security engineers. Understanding these costs is essential for assessing their practical usefulness and likelihood of adoption. C3.3: What level of granularity provides the best trade-off for different security tasks? Different security tasks may require reasoning about security features at different levels of granularity. A security review may require a fine-grained understanding of how security features enforce security properties, whereas some secure software evolution tasks may require only a coarse-grained understanding of which artifacts realize a security feature. Moreover, the same task may be performed at different levels of granularity, creating a trade-off between effort and effectiveness. A finer-grained representation may provide more precise guidance but require more effort to recover, maintain, and understand, while a coarser-grained representation may be cheaper to obtain but insufficient for some security decisions. But what level of granularity provides the best balance for a given security task, and how does this affect the overhead and benefits of security feature location?

The Security Feature Location Problem

Studies with practitioners should compare different granularities for the same security tasks and measure their effects on task performance and security-relevant outcomes. They should also investigate when maintaining fine-grained security feature location information continuously provides sufficient benefit to justify its overhead and when additional detail can instead be recovered on demand.

Conclusion Effective security review, maintenance, and evolution require knowing which security features a system implements and where they are located. We advocate making the notion of security features more explicit and argue that security features are useful abstractions for security engineering. We defined security feature location as the task of identifying the security features a system implements and where they reside in its implementation. We discussed why it is a problem in practice. Specifically, security features realize security properties in software, but the knowledge on their implementation is quickly lost in a large and evolving codebase. Security feature location connects design-level security expectations to the concrete artifacts that realize them. This connection helps developers to assess whether security properties are enforced and to securely change the relevant code without accidentally weakening it. Establishing this connection requires that practitioners explicitly consider, represent, and maintain security features and that researchers develop effective methods and tools for security feature location.

Acknowledgements The authors used GPT (OpenAI) and Claude Opus/Sonnet (Anthropic) models for language-related support, including proofreading and clarifying arguments. The models were also used to assist in the visualization of data for Figures 2–4. All content was reviewed by the authors, who take full responsibility for the manuscript.

References [1] David A. Basin, Jürgen Doser, and Torsten Lodderstedt. 2006. Model driven security: From UML models to access control infrastructures. TOSEM 15, 1 (2006), 39–91. doi:10.1145/1125808.1125810 [2] Lotfi ben Othmane, Pelin Angin, and Bharat Bhargava. 2014. Using assurance cases to develop iteratively security features using scrum. In 2014 Ninth International Conference on Availability, Reliability and Security. IEEE, 490–497. [3] Thorsten Berger, Daniela Lettner, Julia Rubin, Paul Grünbacher, Adeline Silva, Martin Becker, Marsha Chechik, and Krzysztof Czarnecki. 2015. What is a Feature? A Qualitative Study of Features in Industrial Software Product Lines. In SPLC. [4] Ted J. Biggerstaff, Bharat G. Mitbander, and Dallas Webster. 1993. The Concept Assignment Problem in Program Understanding. In ICSE. [5] Kevin Hermann, Sven Peldszus, Jan-Philipp Steghöfer, and Thorsten Berger. 2025. An Exploratory Study on the Engineering of Security Features. In ICSE. [6] Kevin Hermann, Simon Schneider, Catherine Tony, Asli Yardim, Sven Peldszus, Thorsten Berger, Riccardo Scandariato, M.Ãngela Sasse, and Alena Naiakshina. 2025. A Taxonomy of Functional Security Features and How They Can Be Located. EMSE 30, 5 (2025), 117. doi:10.1007/s10664-025-10649-7 [7] Jia Liu, Don S Batory, and Srinivas Nedunuri. 2005. Modeling Interactions in Feature Oriented Software Designs.. In FIW. 178–197. [8] Gary McGraw. 2004. Software security. IEEE Security & Privacy 2, 2 (2004), 80–83. [9] Armstrong Nhlabatsi, Robin Laney, and Bashar Nuseibeh. 2008. Feature interaction: The security threat from within software systems. Progress in Informatics 5, 75 (2008), 1. [10] Sven Peldszus, Katharina Großer, Marco Konersmann, Wasja Brunotte, Maike Ahrens, Kurt Schneider, and Jan Jürjens. 2026. Too Many Issues: Automatically

Prioritizing Analyzer Findings by Tracing Security Importance. TOSEM 35, 3 (2026), 72:1–72:41. doi:10.1145/3744708 [11] Julia Rubin and Marsha Chechik. 2013. A Survey of Feature Location Techniques. In Domain Engineering: Product Lines, Languages, and Conceptual Models. Springer, 29–58. [12] Jerome H. Saltzer and Michael D. Schroeder. 1975. The Protection of Information in Computer Systems. Proc. IEEE 63, 9 (1975), 1278–1308. [13] Adam Shostack. 2014. Threat Modeling: Designing for Security. Wiley. [14] K. Tsipenyuk, B. Chess, and G. McGraw. 2005. Seven Pernicious Kingdoms: A Taxonomy of Software Security Errors. IEEE Security & Privacy 3, 6 (2005), 81–84. doi:10.1109/MSP.2005.159 [15] Katja Tuma, Sven Peldszus, Daniel Strüber, Riccardo Scandariato, and Jan Jürjens. 2023. Checking Security Compliance Between Models and Code. SoSyM 22, 1 (2023), 273–296. doi:10.1007/s10270-022-00991-5

Record · ID 660749 · SHA-256 6e47a2d34e21c4ef
Retrieved via Conceptio — every document is proof-bundled with source, license, and retrieval metadata.